Repository navigation
fix(metadata-protocol)!: two packages' declared overrides of one flow or action name each serve in their own package's slot, in any registration order (ADR-0150 D4) - #33126
Conversation
…, not per deployment (ADR-0150 child B, part 1 of the webhook family) WIP: the switch, the platform-requested auth.* names, the bundle-addition rule, the package-scoped /meta read and the send's holder-filtered bundle. Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…latform-requested auth names and the bundle-addition rule Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…rrowing Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…nd a pair's member as the package's own A read naming a dependent whose only member of a bundled name is an addition (a locale its base does not ship) was refused not-held-by-qualifier once email_template joined the package-scoped readers, and a pair's added member on its base's address was replaced by the registry's item. The qualifier now asks the holders as the caller it names, and keeps an item of a pair of the admitted package. The co-owner dogfood case asserts the ADR-0146 D2 refusal a co-owner meets first at install-local. Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…he email template's narrowing; changeset Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…inst its base Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…ule top Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…s base's address for a bundled type only Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…rowing; pin the qualifier's holder ask for the nine Claude-Session: https://claude.ai/code/session_01ADHkmTMDJkEC5D92VHoU5v Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…mplate send resolves its name from the caller's package (ADR-0150 D3) The flow notify node records the package whose own flow body is armed beside its template reference; the email and inbox channels hand it to sendTemplate/renderTemplate as callerPackageId; the registry loader resolves the name from it and, for a name two packages hold, reads the resolved holder's own address. A send that names no package keeps the bare-name meaning. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…is refused on both exact reads and listed by the upgrade report Two packages of one App share the App's customization pair, so a locale row there with no overrides declaration cannot say whose template it extends: each holder's exact read refuses it naming every holder, the list scoped to a holder leaves it out, and os migrate stored-references lists it under pairMembers. One holder, the App's own address and a row that declares its base keep being served. Pins for F1 (loader rungs, service ladder, notify node, channels, dogfood) and F2; changeset. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…otype.at, which the package's lib target does not declare Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…) for email_template, beside api Two packages of two namespaces may now each hold one email template name, so the second package's item no longer overwrites the first's in MetadataManager's in-memory store: a read naming a package answers that package's own, and a read naming none of a two-holder name is the D3 refusal (#31527 B-shape item 1). Pins that email_template has no metadata redactor, so a save never takes the carry-forward's code-layer read for it (item 4). Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/objectql/src/package-scoped-e8-readers.test.ts # packages/objectql/src/package-scoped-read.ts
…-3giljf # Conflicts: # packages/cli/src/utils/stored-package-references.ts # packages/metadata/src/package-slotted-store.ts
…es hold is no holder's own A package-less row (the environment's item, or a sys_metadata row bound to no package) of a name two or more packages hold is the customization of one holder and cannot say which (ADR-0150 D4). Each holder's exact read now refuses it 409 ambiguous-in-closure naming every holder, the list scoped to a holder (which a send made from it reads) leaves it out, and os migrate stored-references lists it under packageLessRows. One holder, the _env address and the platform-requested auth mails are unchanged. The rule sits in the exact qualifier, so it covers every type read by package (epic ruling 6761802819 on #31699). Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/objectql/src/package-scoped-e8-readers.test.ts # packages/objectql/src/package-scoped-read.ts
…eview round on #32214 - notify node: a literal payload `template` string records the armed package too, as the channels read it (F-A). - the pair-row owner check is one self-contained function, pairRowOnAddress, which the exact read and the scoped list both ask: the declared base first (F-B), the App's own pair, two owners refused, a sole owner that is not one of the address's bases not served (S2). - SchemaRegistry.getPackageScopedItem skips a pair row that declares another holder as its base, so the App's address answers its own item. - stored-references hints, its module doc and the changeset say truthfully that no door binds an added member to one base today (#28777) (F-C). - changeset: Metadata-protocol narrowed; slotted set lists api, agent, skill and email_template; the package-less rule names PACKAGE_SCOPED_READ_TYPES; the pair row's sole-owner and App-slot rules. - email-service docs page: the new callerPackageId member. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/cli/src/commands/migrate/stored-references.integration.test.ts # packages/metadata/src/package-slotted-store.ts # packages/objectql/src/bare-name-one-holder.ts # packages/objectql/src/registry-bare-name-one-holder-ai-family.test.ts # packages/objectql/src/registry.ts # packages/qa/dogfood/test/bare-name-one-holder-flow-family.dogfood.test.ts # packages/services/service-automation/src/engine.ts
…-3giljf # Conflicts: # packages/metadata-protocol/src/protocol-exact-qualifier.ts # packages/objectql/src/bare-name-one-holder.ts
…ead an email template by package, as SchemaRegistry does With email_template narrowed to the namespace, #32277 refuses a bare read of it on a registry that says it reads the type through getItem (`narrowed`, 501). SchemaRegistry reads it by package since this change, so the four doubles that bare-read an email template say so and resolve it through their own item. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/metadata-protocol/src/index.ts # packages/metadata-protocol/src/protocol-exact-qualifier.ts # packages/objectql/src/bare-name-one-holder.ts # packages/objectql/src/package-scoped-exact-qualifier.test.ts
…ed text The doc authoring guard refuses an internal issue id in a runtime string; the reference moves to an adjacent comment. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
…-3giljf # Conflicts: # packages/metadata/src/package-slotted-store.ts # packages/objectql/src/package-scoped-e8-readers.test.ts # packages/objectql/src/package-scoped-exact-qualifier.test.ts # packages/objectql/src/package-scoped-read.ts
…age reaches the package-less owner check `''` and the provenance sentinel `sys_metadata` count as no package before the owner check, so a package-less row the registry answers for a name two packages hold is refused at both exact addresses (contract review R2-A). Pins the exact read and the list of a member the holder itself ships beside such a row (R2-B); the changeset states the mail consequence and the review's prose nits. Claude-Session: https://claude.ai/code/session_01WGufzgX7CHbaN1f2ZtiAiS Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSeKYm8sWjp2Pk76warN7w Co-authored-by: Claude <noreply@anthropic.com>
…t, and the exact read of a member the holder ships refuses beside it (R3-A) The fixture wrote the canonical member's package-less row as bundle_member 'en-US'; the store spells the canonical member NULL (overlayMemberValue), so the read never found it and the pin asserted the registry's answer. Written as stored, ?package=A for a member A ships answers 409 ambiguous-in-closure naming both holders, while A's list and the send's read serve A's own (epic ruling 6792134290). Claude-Session: https://claude.ai/code/session_01QSeKYm8sWjp2Pk76warN7w Co-authored-by: Claude <noreply@anthropic.com>
…er of every slot (R3-B) With email_template in PACKAGE_SLOTTED_STORE_TYPES, DatabaseLoader's loadMany / loadManyKeyed collapsed to one row per name, dropping every other stored locale of a bundle and every other package's bundle, where main answered every row. A bundled type now keeps one row per (package, name, member), the canonical member spelled null as the store's bundle_member column spells it; MemoryLoader answers every slot of a bundled type alike. A rollback by name of a one-slot name restores the row of the member its body declares (epic ruling 6796068023). Claude-Session: https://claude.ai/code/session_01QSeKYm8sWjp2Pk76warN7w Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check13 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ef1fd06e9b0f8e4b98997b50758b3874068f70a5 && git checkout ef1fd06e9b0f8e4b98997b50758b3874068f70a5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a83c366702b26d653a975d533dde4dabb30030e1 616dbbda179dbafe42143eb1f748e9a85a91c94f && git checkout -B drift-repro a83c366702b26d653a975d533dde4dabb30030e1 && git merge --no-ff 616dbbda179dbafe42143eb1f748e9a85a91c94f
node scripts/docs-audit/affected-docs.mjs --json a83c366702b26d653a975d533dde4dabb30030e1 |
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head c0b7cb1 · 2026-11-13T16:06Z |
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract reviewServed-tier: First round, read at 2026-11-13T16:12Z. Inputs: card #32613 with claim 6855902049 and report 6858019637, the PR body, the diff against merge base ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Findings:
M1 is the ground of this verdict. The delta on the next head is this seat's. Implemented-by: VERDICT: FAIL |
…ion, and pin the mixed case The every-shipper judgment of a declared override moves the action only; the six sealed types keep the single-owner judgment. The env-wide list drops a two-shipper override's pair slot for flow and action only; every other type keeps the pair's entry. Pins two holders where only one has a declared override, in both registration orders. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cwJFqk2CMKeRryL5aB
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head 616dbbd · 2026-11-13T17:35Z Delta of FAIL 6858448985, after knock 6860158958. |
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract reviewServed-tier: Delta review of FAIL 6858448985, read at 2026-11-13T17:54Z. Inputs: the interdiff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
S2. Carried by name: the PR body, the changeset and #33131's item 3 name the action's registry face, with the measured case. Findings:
No must-fix. M1, S1 and n1 of record 6858448985 are closed on this head; S2 is carried by name. Implemented-by: VERDICT: PASS |
|
epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW ACCEPT · 2026-11-13T17:55Z · head
|
…-h61law Brings in #33126 (#32613), which serves two holders' declared overrides per shipper for a flow and an action only; the census follows it next. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cyN75XjF6J8fLtD21r
Requested by Elon · project thread
Fixes #32613
Clause-②: no (narrowing)
Metadata-protocol: none
维护者速读
.custom里声明了覆盖时,以前全环境列表(GET /meta/flow、GET /meta/action、执行视图getMetaItemsForExecution)会把其中一个包的覆盖再列一遍、挂在另一个包名下,结果还随包的注册先后而变;动作还会把第二个包的覆盖整个丢掉,改跑它的出厂版本;启动拉取在kernel:ready前也可能先挂上出厂流程。现在每个包的覆盖只出现一次、只挂在自己包下,注册顺序不影响答案:恰好alpha:OVR-A和beta:OVR-B。packages/metadata-protocol/src/protocol-lock-state.ts(servedOverrideOfAShipper,动作改为对每个发布包判定);packages/metadata-protocol/src/protocol-meta-list-reads.ts(多发布包时丢掉 pair 槽里的重复条目);packages/metadata-protocol/src/protocol-registry-hydration.ts+stored-flow-slot.ts(启动拉取按全部发布包判定覆盖)。pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/protocol-flow-custom-override.test.ts -t 32613,预期全部通过(两种注册顺序 × flow/action,外加停用/重启用与两条对照)。What changes
Two packages, alpha and beta, ship a flow or an action of the same name
F, and each has a declared.customoverride bound to its own package (OVR-A, OVR-B). Before this PR the answer depended on registration order (ADR-0150 D4, epic #31527 red line 2). Three places are fixed, all inpackages/metadata-protocol, forflowandactiononly (contract review 6858448985, M1 and S1):readFlattenedMetaItems, which servesGET /meta/flow,GET /meta/actionwith no package, andgetMetaItemsForExecution). The served-override filter knew only a SOLE shipper; with two shippers it kept the override row's pair slot, whichlookupArtifactItemenveloped with the name's first shipper. Now, for a flow or action name two or more packages ship, an item bound to a pair that declares any shipper's override is dropped from the pair slot (it is already served in its shipper's slot). Every other type keeps the pair's entry, as onmain(TWO_HOLDER_PAIR_SLOT_TYPES). Judged by the one served-override predicate (isServedOverrideRowOf) against each shipper. A one-shipper name is narrowed exactly as before.declinesStoredRowinprotocol-lock-state.ts). An action row was judged as a declared override againstpackagedArtifactOwneronly, the first holder to register, so the second holder's override was declined at its own address (?package=, plain and layered), in its served-overrides list, in the served list and in the execution view. It is now judged against every shipper, through the same helper the flow branch already used (servedOverrideOfAShipper). The six sealed types the same branch served (page,app,dataset,doc,book,mapping) keepmain's single-owner judgment: the code says so with its own return.hydrateOverlayIntoRegistry+incomingTakesStoredFlowSlot). Which stored row holds a flow name's bare registry slot was settled against the bare-name owner only, so a package-less row read after the second holder's override took the slot and dropped it; the automation pull armed that holder's shipped body untilkernel:readyre-armed.incomingTakesStoredFlowSlotnow takes the set of shipping packages (the owner plusshippedArtifactsOf), and a row is an override when it declares any of them.FROM → TO
main)alpha:OVR-A×2,alpha:OVR-B,beta:OVR-Balpha:OVR-A,beta:OVR-Balpha:OVR-A,beta:OVR-A,beta:OVR-B×2alpha:OVR-A,beta:OVR-Balpha:OVR-A×2,beta:SHIPPED-betaalpha:OVR-A,beta:OVR-Balpha:SHIPPED-alpha,beta:OVR-B×2alpha:OVR-A,beta:OVR-B?package=, plain and layered) and its served-overrides listkernel:readyUnchanged, pinned as CONTROLs: one package ships the name with its declared override (listed once, as before); two packages ship the name and neither is overridden (each shipped body once); a pair row declaring a package that does not ship the name is no override. The flow's by-name reads (
?package=) already answered each package's own override and are unchanged.Clause-②:
no (narrowing): the env-wide lists stop serving the extra misattributed entries; for an action, the decline this replaces was never the contract (ADR-0150 D4 binds each.customoverride to the package it customizes). First-party callers that read those entries: 0. Metadata-protocol:none: no authorable key, value, type or stored shape moves.Carried, not here
getArtifactItem) gets the first shipper's composite. Measured with the mixed pin: alpha registered first, only beta overridden, the action lists showalpha:OVR-Bbesidebeta:OVR-B. Withmain's owner-only action judgment put back, they showalpha:OVR-Bbesidebeta:SHIPPED-beta, so the leak into the first holder's slot ismain's, not this change's. This change fixes the overridden holder's own slot and address. The seat recorded the case on metadata-protocol: a second holder's declared .custom override is still judged against the first shipper only, for the sealed served-override types and the overlay list filter (ADR-0150 D4 follow-ups of #32613) #33131.Tests
packages/objectql/src/protocol-flow-custom-override.test.ts,[#32613]describe: both registration orders × flow (served list and execution view), action (served list, execution view and each package's address), and one holder disabled then re-enabled (each holder in turn), all expecting exactlyalpha:OVR-Aandbeta:OVR-B(or the enabled holder's alone); CONTROLs: one shipper with its override; two shippers, no override.packages/metadata-protocol/src/stored-flow-slot.test.ts,[#32613]describe (4): the second holder's override keeps the slot over a package-less row read after it and carries it; FROM pin (judged against the first holder alone, the package-less row took the slot); two holders' overrides in either order; CONTROL: a pair row declaring a non-shipper is no override.packages/services/service-automation/src/flow-declared-override-first-boot-pull.integration.test.ts:[#32613]rows in each order, the second package registered in each position: each holder's own body at the first pull.Ablations, read on round-1 head c0b7cb1; the mutated lines keep their purpose on this head (fix committed first; scripts/ablation-replace.mjs; each leg rebuilt @objectstack/metadata-protocol; each restore verified: blob equal to HEAD, git diff HEAD empty)
On head
c0b7cb13:protocol-lock-state.ts: an action row judged againstpackagedArtifactOwneralone (the FROM): objectql[#32613]4 failed, 4 passed (every action case, both orders, incl. disable/re-enable; the flow cases and CONTROLs stay green). Quote:served: expected [...] to deeply equal [...]- "com.probe.beta:OVR-B"+ "com.probe.beta:SHIPPED-beta".protocol-meta-list-reads.ts: keep the pair slot under two shippers (return true || ...): 6 failed, 2 passed (every two-holder case; the two CONTROLs stay green). Quote:served: expected [ 'com.probe.alpha:OVR-A', …(3) ] to deeply equal [ 'com.probe.alpha:OVR-A', …(1) ]+ "com.probe.alpha:OVR-A"+ "com.probe.alpha:OVR-B".protocol-registry-hydration.ts: the slot settled against the owner alone (shippingPackageIds(...)emptied): service-automation[#32613]2 failed, 4 passed (exactly the two package-less-row-after cases). Quote:- "com.acme.other": "OTHER-OVERRIDE"+ "com.acme.other": "OTHER".ablation-dist-preflight --absentexit 0 for all three markers; objectql[#32613]8 passed, service-automation[#32613]6 passed.dist/; the red after the rebuild is what shows the mutated code ran. C's marker was read (exit 0).Acceptance notes
claude/project-thread-70eqr9(server seat), continued onclaude/project-thread-63cajz, and resumes here from63cajz@b777b1b5; every step mergedorigin/main, none rebased. Earlier branches are left as pushed.protocol-registry-hydration.tsand runs after this lands.Verification
On head
616dbbda(branchclaude/project-thread-3itv9w; round 1c0b7cb13=claude/project-thread-63cajz@b777b1b5withorigin/main2c98485dmerged once, no conflict; one commit since, answering contract review 6858448985: M1, S1, n1, and S2 carried to #33131).Local runs on 616dbbd
pnpm exec turbo run build --concurrency=2(whole tree, throughscripts/pm/os-verify-lock.sh): VERDICT command-exit 0.node scripts/pm/dispatch-gates.mjs --commands: the same 72 families as onc0b7cb13, every one run on616dbbda, all exit 0.--ranreconciliation: 72 derived, 72 run, 0 NOT-MEASURED (a derived zero).pnpm --filter @objectstack/metadata-protocol test: Test Files 337 passed, 7 skipped (344); Tests 22803 passed, 36 skipped.pnpm --filter @objectstack/objectql test: Test Files 594 passed (594), Tests 12794 passed (12794).src/protocol-flow-custom-override.test.ts(objectql): 25 passed;src/flow-declared-override-first-boot-pull.integration.test.ts(service-automation): 27 passed.pnpm --filter @objectstack/metadata-protocol typecheck,pnpm --filter @objectstack/objectql typecheck: VERDICT command-exit 0.pnpm lint: exit 0.!title and the(narrowing)arm), on616dbbda: VERDICT command-exit 0; Test Files 460 passed, 3 skipped (463); Tests 3874 passed, 42 skipped (3916); no failure.CI on this head: see the check runs.
Human Minimum
packages/specholds no runtime logic (not touched).pnpm lintpasses.mainmerged, never rebased).@objectstack/metadata-protocolpatch), and both declaration lines are filled in here and in the changeset.packages/specnot touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cwJFqk2CMKeRryL5aB
Generated by Claude Code