Skip to content

feat(plugin-webhooks): 18.x step 1 (8b-0g′): declared webhooks are read from the registry, the seeder retires, expand-first #24529

Closed

Description

@objectstack-fleet

Filing gate ③: a task the maintainer directed. These are steps 1–3 of the 18.x line, under ruling records 6266750393 and 6267391172 on #15194. The second records the maintainer's 「用量很多,不用节约」: steps 1–3 start now, in parallel.

Part of #15212 (the C8 card), from decision #24435 Q2 A (ruling 6259141885) · Step 1: 8b-0g′, declared webhooks read from the registry · ⛔ Not an 18.0 gate.

The line's rule (binding)

Once merged, this PR leaves main releasable:

Expand first. This step only adds: the new write shape, plus reads that accept both the 18.0 shape and the new one. It never moves or deletes an 18.0 row and never drops a column. Each such removal is written down for the step-5 ceremony.

Merge gate. The PR stays a draft until #24522's harness has merged. It lands after it has merged main and is green on R1–R4.

Scope

Acceptance

  • The new shape is pinned on a fresh install. The dual read is pinned on an 18.0-shaped row.
  • The harness stays green.
  • The report lists every removal the step-5 ceremony owes, table by table.
  • Measure first. If expand-first is impossible or unsafe for any part, stop and report that part with the alternative. Do not build a destructive change.

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 10 · 2026-10-19T12:40Z
    Session: session_01Y5583bs2RDd53MH6kM1ca3
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-24529-8b0g-webhooks-registry. If the dev splits this step into several independently releasable PRs, each PR's branch is claude/issue-24529-8b0g-webhooks-registry-<part> and carries Part of #24529.
    Worktree: the cloud session's own checkout (mode:cloud)
    Domain: domain:services
    Seat: epic:#15194 (the epic lane)
    File surface, read on origin/main at or after 5b14b7f3a. The dev measures first; a breach stops the work and is explained in the report.

  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24529,
      "status": "done",
      "branch": "claude/issue-24529-8b0g-webhooks-registry",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24588",
      "session": "session_01PDSxBZE561TFe1N2iEjKXZ",
      "premise_still_valid": true,
      "summary": "Claim verified: the newest Claim on #24529 is 6267446928 and it names this branch. The boot seeder bootstrapDeclaredWebhooks is deleted, and a fresh boot writes zero sys_webhook rows for a declared webhook. The dispatcher reads declarations from the registry: DeclaredWebhookSource in plugin-webhooks, bound as the declaredWebhooks service. The dispatcher projects each one into its subscription cache (ref_id declared:NAME), and its secret and headers are held in memory and never stored. A declared webhook is switched under regime C, through sys_metadata_activation rows of type webhook, by the runtime catalog activation door POST /security/_activation/webhook/:name, the sharing_rule door with webhook admitted. The ledger overrides isActive in both directions, and a ledger write refreshes the dispatcher in the same request. Dual read on an 18.0 DB: the seeder's rows stay. A pristine mirror (managed_by package, not customized) is ignored in favour of its declaration, so there is no double fire. A customized seeded row, or an admin row under the name, answers as itself and the declaration is withheld, which keeps an admin's active:false. Measured on a fresh main showcase: the 18.0 seeded row carries the Default Organization and delivers under single. So under a posture with no wall, a declared webhook is deployment-wide: it takes every organization's events and stamps the event's organization on each delivery. Under a wall the org-less refusal stands. The redelivery veto judges declared:NAME deliveries, and pristine-mirror replays, by the declaration.",
      "tests": "At head 6b3042a8. plugin-webhooks vitest: 19 files, 212 passed. typecheck exit 0 (check:test-typecheck OK). runtime full suite: 422 files, 6652 passed, 19 skipped; typecheck exit 0; catalog-activation-door.test.ts +7 webhook pins. Dogfood webhook-materialization.dogfood.test.ts: 1/1 (no sys_webhook row; dispatch through the bound declaredWebhooks service, object to object_name). check:liveness exit 0. The 18.0-shaped fixture: seeded18_0Row (fake engine) and seed18_0Rows (real ObjectQL, encrypted columns) reproduce the 18.0 seeder's mapWebhookToRow plus provenance; the dual read is pinned by 7 cases against them. Ablations through scripts/ablation-replace.mjs, each restored with blob == HEAD and git diff HEAD empty (tests import src relatively, so no dist leg): mirror skip 3 red; withheld rule 3; ledger override 6; ledger self-heal 1; deploymentWide 2; plugin posture read 1; guard declared branch 2; guard mirror branch 1; door declaredWebhooks lookup 5. Fresh showcase boot at 6b3042a8 (single): sys_webhook 0 rows; door enabled:true returns 200 and one ledger row (package com.example.showcase); undeclared name 404; a task create gives a sys_http_delivery row with ref_id declared:showcase_task_changed and Default Organization; enabled:false and a second create add no delivery. The same steps on fresh main: seeded row org-stamped, delivery ref_id whk_ in the same org.",
      "gates": "Local, head 6b3042a8: dispatch-gates --commands --repo objectstack-ai/objectstack gave 130 families, plus the 50-family artifact-roster block (STDERR): 178 commands, exit codes written to a file before any pipe. 173 exit 0. NOT MEASURED, 5: check-closing-target-claim, check-single-claim-paths and check-governed-queue-guard (exit 2/2/1: no PR or event context, CI runs them); check-partof-closing-keyword (no context in the sweep; rerun with PR_BODY = the PR body: exit 0, as is check-closing-keyword-parity); check-plugin-teardown-shape --self-test (exit 3: its pinned fixture needs an unshallowed clone; checker health only). --ran: 130 derived, 130 run, 0 NOT-MEASURED. Real findings fixed during the sweep: tenant-audit census 231 to 229 (regenerated with the tool, prose figures corrected); objectql-double-limit and where-matcher on the renamed test double (made drivable and conforming, baseline entry dropped); spec liveness state-counts regenerated with check:generated --fix. CI check-runs on #24588: in_progress at report time.",
      "mcp_calls": "3 MCP reads, 0 writes: issue_read get on #24529, issue_read get_comments on #24529, pull_request_read get on #24588. Ruling records read with gh api GET (reads only).",
      "api_writes": "2 relay strokes as objectstack-fleet[bot] via with-fleet.sh --via dispatch: (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /issues/24588/assignees [os-steve]; (2) this comment, POST /repos/objectstack-ai/objectstack/issues/24529/comments. Plus git pushes to the branch (not REST).",
      "ceremony_owes": [
        "sys_webhook: delete the pristine declared mirrors (managed_by package or platform, customized not true, name still declared).",
        "sys_webhook: settle each customized seeded row the way the sharing-rule ceremony settles its customised copies: a copy under a new name, plus the declared webhook switched off in sys_metadata_activation.",
        "sys_webhook inventory: in packages/cli/src/utils/organization-ownership-inventory.ts, turn the row's declared category into a mirror-deletion departure (ADR-0131 D10 fate 2). Not edited here, because the apply executes departures and so it is ceremony behaviour.",
        "sys_http_delivery: re-source the deleted mirrors' tails, ref_id MIRROR_ROW_ID to declared:NAME, so history and the redelivery veto follow the declaration.",
        "sys_secret: delete the cipher rows the deleted mirrors' signing_secret and headers_secret referenced (orphans after deletion).",
        "sys_webhook columns: customized, and the package/platform values of managed_by, describe nothing once no mirror exists. Retire them in the ceremony or at step 7. Their help text still mentions boot seeding, left unedited so the i18n bundles are not churned.",
        "Code owed at step 7: classifyDeclaredNameHolder, the mirror skip in AutoEnqueuer.doRefresh, the redeliver guard's pristine-mirror branch, bindWebhookProvenanceStamp, and migrateLegacyWebhookSecrets for package rows."
      ],
      "open_questions": [
        {
          "question": "How is a sys_webhook row holding a declared name read on an 18.0 DB?",
          "options": [
            "A recognise a pristine seeded row and ignore it in favour of the registry entry; let any edited or admin row answer and withhold the declaration",
            "B always ignore seeded rows",
            "C always defer the declaration to any same-name row"
          ],
          "recommendation": "A, decided and built: it reproduces exactly what the 18.0 seeder did on each boot (it overwrote pristine rows and never touched edited or admin ones). So there is no double fire, an admin's active:false survives, and a changed declaration still propagates."
        },
        {
          "question": "Organization dimension of a declared webhook (measured: the 18.0 seeded row carries the Default Organization and delivers under single)",
          "options": [
            "A deployment-wide when the posture builds no wall (deliveries stamped with the event's organization); org-less refusal under a wall",
            "B org-less everywhere (silently delivers nothing under single, a regression from 18.0)"
          ],
          "recommendation": "A, decided and built. It keeps R2 behaviour and follows the sharing plugin's declared-rule scope reading of the posture."
        },
        {
          "question": "Refuse a new sys_webhook row under a declared name with 409, as sharing_rule's guard does?",
          "options": [
            "A not in this expand step (the row answers and the declaration is withheld, as under 18.0)",
            "B add it now"
          ],
          "recommendation": "A, decided: expand-first adds no new refusal. Take it up at step 7, or as its own card, when the mirrors are gone."
        },
        {
          "question": "Files edited outside the claim's surface list",
          "options": [
            "A accept them as necessary consequences",
            "B split them out"
          ],
          "recommendation": "A. Deleting the seeder forced these edits: spec liveness rows and comments; the dogfood webhook proof and fixture (not #24522's harness); the platform checklist item; content/docs webhooks guide; tenant-audit census regenerated; the showcase docblock; four scripts/ ledgers re-keyed or shrunk. No ADR, no releases page, no #24522 harness file."
        }
      ],
      "out_of_scope_findings": [
        "class: none filed. label and description on WebhookSchema are now unread: the seeded Setup row was their only reader. The liveness ledger marks them dead, owed enforce-or-remove (ADR-0049): a Setup surface that lists declared webhooks, or retirement. carrier: none; noted in the PR's Acceptance notes. Dedupe words: webhook label dead, declared webhook display, enforce-or-remove webhook",
        "class: none filed. AutoEnqueuer.admitsOrganization's docblock says a single-posture deployment stamps nothing on events; measured on the showcase, single-posture record events carry the Default Organization (since 8b). The comment is stale; its behaviour is unchanged here. carrier: none; noted only."
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report addendum

    {
      "issue": 24529,
      "addendum_to": "os-dev-report 6270150096",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24588",
      "head": "9bce0aaf",
      "base_merged": "origin/main 5a871e4e (carries the harness ec16dbe2e: merge-base --is-ancestor exit 0)",
      "test_core_2_of_6": "Root cause, reproduced locally: packages/lint/src/lint-liveness-properties.test.ts 'does not warn on an authored webhook' pinned zero findings against the real webhook.json, and the PR's dead flip of label/description made it emit liveness-dead-property for description. This is the contract review's B1; main is green on that shard (ci.yml on main at 2667e2402: success). Fix, keeping the verdict: (1) the two dead rows gain an authorHint; (2) the guard now expects exactly two liveness-dead-property findings, for label and description, with that hint, and a new case pins silence for a declaration that authors only what is delivered on; (3) the showcase declaration drops label and description. pnpm --filter @objectstack/lint test: 148 files, 6924 passed, exit 0. The test:repo projects of spec/core/objectql/rest/runtime were also run locally and are all green (1162/82/5/278/808).",
      "r2_harness": "pnpm --filter @objectstack/dogfood exec vitest run test/upgrade-18x-harness.dogfood.test.ts under os-verify-lock at 9bce0aaf: 6 passed (6), exit 0. Added case 2b, 'the fixture's customized declared webhook dispatches exactly once on a task change'. The 18.0 mirror is still there and customized:true. The declaration is switched ON through POST /security/_activation/webhook/showcase_task_changed (200). One task create then yields exactly one new sys_http_delivery row, ref_id = the mirror row id. Ablation (withheld-declaration rule mutated with a marker that survives bundling; dist preflight: marker present in 2 built files) made 2b red, received an extra 'declared:showcase_task_changed'. Restored: blob == HEAD, dist preflight --absent exit 0, tree clean. A first ablation leg without the switch-on stayed green, because the shipped declaration is isActive:false. That is why the case now switches it on. Recorded, not hidden.",
      "also_fixed": "docs/qa/platform-checklist integration-system.json revision 8, fixtures.requires[0] and steps[1]: a declared webhook is deployment-wide under single and delivers once switched on (to its placeholder url); refused org-less only under a wall. The AutoEnqueuer.admitsOrganization docblock: the stale 'single stamps nothing' cell is corrected, and the deployment-wide check is documented.",
      "checks_at_head": "exit 0 each: plugin-webhooks test (212) and typecheck; runtime catalog-activation-door (64) and typecheck; dogfood webhook-materialization (1); check-tenant-audit-census; check:engine-double-contract; check:objectql-double-limit; check:where-matcher; check-platform-checklist; check-adr-0087-registration and check-changeset-no-major --base origin/main; spec check:liveness. The full dispatch-gates union was NOT re-run on this head; CI runs it.",
      "line_count": "git diff --shortstat origin/main...HEAD: 42 files, +1960 / -1146 = 3106 changed lines, over 3,000. No test was trimmed. Per the seat, the PR goes to the maintainer as Tier H.",
      "api_writes": "1 relay stroke: POST /repos/objectstack-ai/objectstack/issues/24529/comments (this addendum). Plus git pushes.",
      "mcp_calls": "reads only: pull_request_read get_check_runs, get_job_logs (twice), actions_list list_workflow_runs; 0 writes"
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report addendum

    {
      "issue": 24529,
      "addendum_to": "os-dev-report 6270150096 (second addendum, after 6271777060)",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24588",
      "head": "fa6a837e",
      "merge_commits": "7e580dc7 merges origin/main c3954057 (carries #24587, fbafb8533); fa6a837e merges origin/main 8fbbd508. Both are merge commits: no rebase, no force-push. mergeable_state read back after the push: blocked (no longer dirty).",
      "conflicts": "Two, both regenerable: content/docs/permissions/tenant-audit-census.mdx and docs/audits/2026-08-tenant-audit-write-call-sites.counts.md. Resolved by taking main's side and regenerating with node scripts/tenant-audit-census.mjs --write. The hand-written prose figures the gate holds to the census were then brought to it: 231 to 229 sites (#24587's sites in, the seeder's two out); 157 to 155 decidable; 51 to 49 const-named; 52 to 50, 60 to 58, 31 to 29, 95 to 93. No checklist item conflicted: platform-core.json and integration-system.json auto-merged, and integration-system.json stays at revision 8 with main's history intact.",
      "checks_at_head": "All exit 0, run after the second merge at fa6a837e, with the dogfood and runtime closures rebuilt first. upgrade-18x-harness.dogfood.test.ts: 6 passed (6), case 2b included. plugin-webhooks test: 212 passed. runtime catalog-activation-door.test.ts: 64 passed. lint lint-liveness-properties.test.ts: 103 passed. spec check:liveness, check-tenant-audit-census, check-platform-checklist. The same set was also green at 7e580dc7.",
      "main_after_push": "origin/main moved to 65a8eeb7 (#24620, plugin-approvals only) after the push. A trial merge was clean with no overlapping file, so it was not merged in. #24590 has not landed.",
      "line_count": "git diff --shortstat origin/main...HEAD: 42 files, +1958 / -1144 = 3102 changed lines. The PR page reads 1958 / 1144 over 42 files.",
      "api_writes": "1 relay stroke: POST /repos/objectstack-ai/objectstack/issues/24529/comments (this addendum). Plus git push.",
      "mcp_calls": "reads only: pull_request_read get on #24588; 0 writes"
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report addendum

    Third addendum to 6270150096. #24588 head is now b4ae8e68. That is merge commit b4ae8e68, which merges origin/main b12e16a5 (it carries #24590) as a merge commit, with no rebase and no force-push. The branch is up to date with main at the push. The only conflicts were the same regenerable pair, content/docs/permissions/tenant-audit-census.mdx and docs/audits/2026-08-tenant-audit-write-call-sites.counts.md. I took main's side, regenerated them with node scripts/tenant-audit-census.mjs --write, and brought the hand-written figures the gate holds to the census: 232 to 230 write call sites (#24590's site is in, the seeder's two are out); 158 of 232 to 156 of 230 decidable; 129 of 230 (56%) elevated; and 52 to 50, 51 to 49, 60 to 58, 31 to 29, 95 to 93. All exit 0 at b4ae8e68, with the dogfood and runtime closures rebuilt: upgrade-18x-harness.dogfood.test.ts 6/6 (case 2b included); the plugin-webhooks suite (212); runtime catalog-activation-door.test.ts (64); lint lint-liveness-properties.test.ts (103); check-tenant-audit-census; check-platform-checklist; spec check:liveness. Changed lines vs main are now +1958 / -1144 over 42 files, 3102. The PR is left ready, with no auto-merge.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and closed · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T23:00Z. PR #24588 (18.x step 1, 8b-0g′: declared webhooks are read from the registry, the seeder retires, and the activation ledger switches them) has merged. Steps 1–3 of the 18.x line are now all on main.

    reading value
    PR #24588 merged: true; merge commit 191e0430e; approved by os-zhuang (Tier H by size)
    contract review FAIL 6271423990 at 6b3042a8 (B1), then the seat re-review at 9bce0aaf, PASS
    R2 harness 6/6 including case 2b, on each merged tree (addenda 6271777060, 6273179278 and 6275343210, the last at b4ae8e68 after #24590)

    Delivered.

    • A fresh boot writes no sys_webhook row for a declared webhook. The dispatcher reads the declarations from the registry, and the activation ledger switches them through POST /api/v1/security/_activation/webhook/:name.
    • On an 18.0 database, a pristine seeded mirror defers to its declaration, so there is no double delivery. A customized seeded row, or an admin's row under the declared name, answers as itself.

    Carried.

    The card closes completed. The dev session is archived.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions