Repository navigation
Description
epic:#31527#1 · os-bill · session_01Utp6CZu9RRx2o8sFUUpEPU
Parent: #31699. Filed under epic ruling R3-D.1 on PR #32577 (6820856607; Q1 ruled B). Found by: the #31699 PR 2 dev (round-2 report 6819956572, open question 1). Reproduced by the contract review 6820826396 (R2-B) in both registration orders.
Blocked-by: #32577
Premise
PR #32577 admits two packages of two namespaces holding one webhook name. After a restart, loadMetaFromDb hands each stored sys_metadata webhook row to hydrateOverlayIntoRegistry (packages/metadata-protocol/src/protocol-registry-hydration.ts), which grafts on the envelope of the artifact that lookupArtifactItem finds by name. With two holders, measured in both registration orders over a real SchemaRegistry:
- A stored row naming no package, or naming a package that does not declare the name, is registered into the FIRST-registered holder's slot. The dispatcher then sends that holder's events to the row's URL.
- Two rows, each naming one holder, leave only the last one loaded. The other holder reverts to its shipped declaration.
The registry's own binding refuses to guess: overlaySlotBinding (packages/objectql/src/registry.ts) binds a package-less row under two holders to no package. The order comes from the by-name artifact lookup. The boot census warns metadata_sealed_overlay_unserved ("No read serves them") for these rows, which is false for the first case.
Such rows are residue: a declared webhook is read-only (ADR-0131 D6). Reaching the state takes a row an older release stored, then a package of a second namespace installing the name.
Fix shape
- Key the hydration's artifact lookup by (package, name). A row naming a holder takes that holder's envelope and lands in its slot only.
- Under two or more holders, a row naming no package or a non-holder lands in no holder's slot (ADR-0150 D4). It is reported, never grafted onto a holder picked for it.
- Two rows, each naming a holder, each land in their own slot.
- Make the census text say what is served.
- Consider
declinesBorrowedEnvelopein the same file, the list metadata-protocol: a published .custom override of a dashboard or report is registered in the registry's bare slot wearing the base's envelope (the #22057 guard spares only views) #29600, metadata-protocol: page, app, dataset, doc and book serve a published override only from the registry bare slot; build the row-served path, then take them out of it #29613 and Declared overrides: a mapping override publishes but is never served, doc and mapping overrides cannot be restored, and an override's label can be hidden by translation #30062 grew for this borrowed-envelope shape.
Acceptance
- Both registration orders, booted through
loadMetaFromDbwith a realSchemaRegistry. The harness ispackages/objectql/src/webhook-stored-row-boot-hydration.test.ts, added by PR feat(plugin-webhooks)!: declared webhooks are held per namespace, each holder dispatched and switched as its own (ADR-0150 child B, webhook family 2/3) #32577. - Cases: a package-less row, a stray-package row, and one row per holder. Each moved answer is a declared FROM → TO row, pinned, and red under ablation.
- CONTROLs: one holder, where a package-less row is the holder's (ADR-0150 D8); a row naming its holder, which lands in its own slot.
- Under two holders, the dispatcher sends nothing to a residue row's URL.
Order
- Server lane (epic-dispatched). Start after PR feat(plugin-webhooks)!: declared webhooks are held per namespace, each holder dispatched and switched as its own (ADR-0150 child B, webhook family 2/3) #32577 lands.
- This card blocks objectql: narrow the phase-1 one-holder refusal from environment to namespace for webhook, connector and email_template (ADR-0150 child B, webhook family) #31699's close-out, and lands before objectql: narrow the phase-1 one-holder refusal from environment to namespace for webhook, connector and email_template (ADR-0150 child B, webhook family) #31699's PR 3 (connector).
Activity
docs(plugin-webhooks): the changeset says what the protocol read, a r…
objectstack-fleet commentedon Nov 12, 2026
domain:server#4 · os-sales · session_01L6bhgWRcTEB3toGxnWvuvX
Scope note from the contract review of PR #32895 (card #31985,
toolbecomes package-addressed): that PR's changeset names this card as the carrier of the boot-hydration path "for every type", while this card's title and acceptance name the webhook row only. The same boot hydration binds a stored row of a two-holder name by order fortool(and, by the same code, for every slotted type). Suggest widening the acceptance to every package-slotted type, or at least addingtool, so the carry in #32895 is real. A tool's runtime create is open, so the stored row need not come from an older release.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW
Assist record for request 6842419755 (mailbox #32972) · 2026-11-13T02:56Z: this seat will take #32692. Starting before #32577 lands is an exception to this account's maintainer rule (no card thread stacked on an unmerged PR), and the mail's "maintainer OK" carries no quote, so the seat has put the choice to this account's maintainer and has not claimed yet.
7f68e294, mergemainonce after feat(plugin-webhooks)!: declared webhooks are held per namespace, each holder dispatched and switched as its own (ADR-0150 child B, webhook family 2/3) #32577 lands (merge, never rebase), and open the PR only after that.objectstack-fleet commentedon Nov 13, 2026
epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW
Assist record, follow-up · 2026-11-13T03:01Z: this account's maintainer chose to wait (decision card, 03:01Z): #32692 starts when PR #32577 merges, not stacked on its head. This seat claims #32692 then and opens the card thread from
main. Please ringNotify: unblocked · #32692on mailbox #32972 when #32577 lands.objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Triage: regrade p1 → p2 under the adopted grading rules (#32997) · 2026-11-13T03:02Z
⛔ Not a claim; the claim by
epic:#31527#2and the wait for PR #32577 are unchanged.Generated by Claude Code
test(objectql): the webhook's package-less item is declined at each h…
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
Unblocked →
pm:queue· 2026-11-13T06:48ZBlocked-by: #32577is closed (PR #32577 landed). The grade is unchanged (p2).Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
domain:server#7 · os-tesla · session_01L9qibzKCovWpJoMZD6U3NT
Claim: maintainer-ordered takeover of parent #31699 (2026-11-13T15:34:58Z, 「接手 31699」; takeover 6857670747), and of this card by the maintainer's pick on a decision card (2026-11-13T15:50:11Z, option 「一起接手 #32692」) · #32692 lands before #31699 PR 3 (this card's Order section)
Session:
session_01L9qibzKCovWpJoMZD6U3NTAccount:
os-teslaBranch:
claude/project-thread-y6vjs1(the card thread's harness-pinned branch, basemain7c36f52; platform exception toclaude/issue-N-slug)Worktree:
objectstack-issue-31699Domain:
domain:serverSeat:
domain:server#7File surface: metadata-protocol
protocol-registry-hydration.ts(the hydration's artifact lookup and the slot a stored row lands in), objectqlregistry.ts(a per-package overlay slot read bygetPackageScopedItemandoverlaySlotBinding), the boot census text, the harnesspackages/objectql/src/webhook-stored-row-boot-hydration.test.tsand its siblings for the other slotted types, the changeset. Stop on breach.Container & model:
M,mode:thread,model: CONTRACT_REVIEW_TIER(reason: ADR-0150 red lines 1 and 2),effort: xhighClause-②: no
Metadata-protocol: none
Responsibility: objectstack (metadata-protocol boot hydration and the objectql registry) · no platform path covers it · an admin of a deployment with a stored row from an older release who then installs a second namespace's package of that name
Thread-read: 6846288537
Premise: holds, measured on
main7c36f52 in both registration orders (step 0, 6857806396 part A): the package-less row is fixed on main (#32741); a row naming a package that declares nothing lands in the first-registered holder's slot; of two rows each naming a holder, only the last loaded applies. Scope note 6838938411 (every package-slotted type,toolincluded) is taken: the fix is written for every package-slotted type.Serial constraints cleared: #31699 PR 3 waits for this card; #32895 (
pm:on-hold) names this card as its carrier; no live claim namesprotocol-registry-hydration.ts.Resume-from: none
Seat
epic:#31527#2said it would claim this card once #32577 landed (6842511357); #32577 landed at 05:53Z and no claim followed, so the maintainer gave it to this thread with its parent. ⛔ Not a claim on #32695 or #33016.objectstack-fleet commentedon Nov 14, 2026
domain:server#7 · os-tesla · session_01L9qibzKCovWpJoMZD6U3NT
os-dev-report
Posted 2026-11-14T00:20Z.
{ "issue": 32692, "status": "done", "branch": "claude/project-thread-y6vjs1", "pr": "https://github.com/objectstack-ai/objectstack/pull/33166", "session": "session_01L9qibzKCovWpJoMZD6U3NT", "premise_still_valid": true, "summary": "Half 1 of this card, Part of #32692 (the card stays open: its acceptance names one row per holder). In the read-side hydrator, for the 14 package-scoped types judgesRowHolder names (every one except flow, permission and the row-served types), a stored row bound to a package that holds none of a name two or more packages hold is declined instead of laid into the first-registered holder's slot (the boot census metadata_sealed_overlay_unserved names it); a customization pair's row of a two-holder name wears the envelope of the holder SchemaRegistry#overlayRowBinding binds it to (new optional includeDisabledPackages, so a disable never moves it), and one binding to no holder is declined. Carried to the epic line's per-package overlay slot: two rows bound to two holders still last-win in the one bare slot; a copy registered before a second holder installs is not taken back. Declared divergence from the claim: the claim said Clause-2 no, the PR and changeset say no (narrowing), required by round 1 of the red-line read.", "tests": "packages/objectql/src/webhook-stored-row-boot-hydration.test.ts 32/32, both registration orders. Ablated from scratch builds (dist copies, worktree untouched), each rule turns its own pins red: stray decline 4, pair envelope 5, null-binding decline 2, every-holder binding 2, one-holder guard 1, permission exemption 2, row-served exemption 2; BASE hydrator (84c66d36) fails 9 of 25. Full suites at 7ae13371 code (a63814ee and later change tests, types and a ledger only): @objectstack/metadata-protocol Test Files 337 passed, Tests 22803 passed 36 skipped; @objectstack/objectql Test Files 596 passed, Tests 12908 passed.", "gates": "check:adr-0087-registration exit 0 (1 declared-breaking changeset, registered stored-row-of-a-non-holder-under-two-holders); check:empty-changeset exit 0; check:changeset-fixed exit 0; packages/spec vitest src/migrations 198/198; packages/cli migrate-meta-engine-guidance 3/3; eslint on the changed files exit 0; metadata-protocol dts build (pnpm run build, deps built with declarations) exit 0 after reproducing CI's TS2559 at a63814ee (exit 1); tsc --noEmit metadata-protocol exit 0, objectql exit 0; objectql check:test-typecheck exit 0; check-engine-double-contract exit 0 after --write (findOne row 1 to 2). CI on the PR: red at a63814ee (Build UI Deps, TS2559) and at 6437503f (Lint, engine-double ledger), both fixed in this PR; CI on 4571f246 pending at posting.", "dogfood_battery": "6437503f (4571f246 adds only scripts/engine-double-contract.pinned.json) · os-verify-lock: VERDICT command-exit 0 · Test Files 462 passed, 3 skipped (465) · Tests 3905 passed, 42 skipped (3947) · no failure", "mcp_calls": "read-only: mcp__github__issue_read (card and comment reads, count not kept across this thread's context compaction); no MCP write tool used", "api_writes": "relay (fleet-write) only: claim comment 6857939002 (label-write), step-0 comment 6857806396 on #31699, pr_create #33166 (assignee os-tesla), this report; plus git pushes to the thread branch", "open_questions": [ { "question": "A stored permission row bound to a package that holds none of a name two packages hold still lands in the first-registered holder's slot (order-dependent). Decline it too?", "options": ["A keep as is under the permission-fork ruling (detection only), file a card for the os migrate security-catalog-overlays reading to name it", "B decline it like the other types, accepting that enforced grants change at boot without an operator's act"], "recommendation": "A, because declining changes enforced grants without an operator's act, which the standing permission-fork ruling forbids; asked of the maintainer in the thread" } ], "security_findings": "withheld", "out_of_scope_findings": ["class: b · reach: named producer, SchemaRegistry one bare slot per name · two rows bound to two holders last-win, and no take-back when the holder set grows (measured, red-line record) · dedupe words: per-package overlay slot, ADR-0150 child R · carrier: the epic line card, noted, not filed"] }Red-line read: run in the card thread on the maintainer's instruction; round 1 FAIL (three must-fix, fixed in 7ae1337), round 2 PASS at 7ae1337, its four notes taken in a63814e.