Repository navigation
fix(metadata-protocol)!: a package's stored copy of a container it ships overlays its shipped views, so a withdrawal saved in the copy holds at the anonymous form doors - #22023
Conversation
… view container can take The withdrawal-reach family's closing enumeration, committed red against the base: who owns the object (the copying package, another package, none) x whether the copying package ships the container x the member the copy changes (the bare list, a keyed member, the default form), on both kernels. Base reading: 10 red / 51 green, the reds exactly the placements where the copying package ships the container on another package's object. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
…ps expands to the loaders' names Triage's direction for a stored copy of a view container its own package ships: on another package's object it expands as the source loaders expand the shipped container (`<object>.<key>`, in the copying package's own slot), not under the own-name arm. Any other container on another package's object keeps the own-name arm, and no copy declares the object's default. - `copiesOwnShippedViewContainer`: the copying package ships a view container under the copy's name, bound to the same object. - `shippedViewContainerOf`: the one artifact lookup for "the container a package ships under a name", now shared with `overlaidShippedContainerViewNames` (no behaviour change there). - `runtimeViewContainerObject`: the object derivation chain, extracted unchanged so the shipped container's binding is read the same way. - The enumeration pin asserts the default each placement declares. Measured: the pin file is 144 green / 3 red. The three reds are the unscoped kernel's by-name read naming the object's owning package, for a name the copy now shares with it: the registry hydration of the copy's expansion under the bare name answers it (the same mechanism already answers that read on main when two packages ship one name and only one has a stored copy). Closing it needs a change beyond the per-package keying, so the card returns to triage as needs_decision. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
…rs a view expansion under a name another package ships The maintainer's ruling on OQ1 (A). On an unscoped kernel, hydrateExpandedViewItems registered every expansion of a stored view container under the bare name, and SchemaRegistry.getItem answers the bare slot ahead of any package's own entry. So the by-name read naming another package that ships the same name served this container's view, while the list served that package's own item in its slot. It predates the copy's new names (two packages ship one container, one stores a copy), and the copy's new names reached it on another package's object. An expansion whose name another package ships (shippedArtifactsOf) is no longer registered there. Every kernel's by-name read answers it from its stored row ahead of the registry (resolveRowlessExpandedView), and the list expands the row itself. Pins: block (f)'s owner reads on the unscoped kernel are green, and block (g) pins the earlier case: two packages ship the container, either one stores a copy, and the by-name read naming each package answers that package's own item on both kernels. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
… name two packages ship, on both kernels The ruling's condition on the hydration line: for a name two packages ship, once one of them stores a copy, the by-name read that names no package answers on both kernels (never an absence), both kernels answer the same body, and that body is one the env-wide list serves under the name. Block (h) runs it for each member, with the object owned by the other package or by none, in both registry orders. Measured before writing it, over base aa09db5, the direction b7a2a8f and 2322b5b: no read answered nothing anywhere, and the hydration line leaves this read unchanged on both kernels. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
…ips overlays its shipped views; the save door refuses three copies it accepted The changeset for this card, `@objectstack/metadata-protocol` minor, carrying `Clause-②: no (narrowing)` per the maintainer's ruling on OQ2. A package's stored copy of a view container it ships overlays that package's shipped views, so a withdrawal saved in the copy holds at the anonymous form endpoints. Judged at its new names, the save door now refuses three copies it accepted before: an added bare list or keyed member whose name only another package ships, and a copy expanding a name another stored container already expands. The BREAKING line names the three shapes and their remedy, and the ADR-0087 marker is not-required (no-migration-prescription), stating that no census of the writers of such copies was taken. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 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 2770d9991c93b923511cabca7d08a547899840f0 && git checkout 2770d9991c93b923511cabca7d08a547899840f0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b220e0943e87d26256316387e3c484f3f50b8416 03f727651ed15fc6c1795fe80c18b139ade6b91b && git checkout -B drift-repro b220e0943e87d26256316387e3c484f3f50b8416 && git merge --no-ff 03f727651ed15fc6c1795fe80c18b139ade6b91b
node scripts/docs-audit/affected-docs.mjs --json b220e0943e87d26256316387e3c484f3f50b8416
|
|
CI:
Update, 2026-10-07T01:06Z: green. The maintainer flagged the red, so the seat did not wait on the ruling for the base merge.
Generated by Claude Code |
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
ACCEPT (seat review) — PR #22023 at head
|
Fixes #21980
Clause-②: no (narrowing)
A package's stored copy of a view container it ships now overlays that package's shipped views, so a withdrawal saved in the copy holds at the anonymous form doors.
This is triage's direction for the card (6014736043, unlocked by 6016790773). It lands under the maintainer's ruling on the two questions the first round returned: batch #282 item 1, decision card #22004 (record 6020103367, pointer 6020226416 on the card), 「同意」 at 2026-10-06T15:59Z. OQ1 is answered A: the unscoped hydration line, under one measurement condition (below). OQ2 is answered A: the narrowing arm,
minor.All source edits are in
@objectstack/metadata-protocol(packages/metadata-protocol/src/protocol.ts). There is nopackages/specedit, no loader edit, norest-server.tsedit and no governed path.What changes
expandRuntimeViewContainergives the loaders' arm to a stored copy of a container its own package ships, bound to the same object (copiesOwnShippedViewContainer). Each member is served asOBJECT.KEY, in the copying package's own slot, which the list and the by-name read already select per package (servedViewExpansion).shippedViewContainerOfthatoverlaidShippedContainerViewNamesshares (no behaviour change there). The object binding is read byruntimeViewContainerObject, which is the base derivation chain extracted unchanged.hydrateExpandedViewItemsno longer registers an expansion under the bare name when another package ships that name (shippedArtifactsOf).SchemaRegistry.getItemanswers the bare slot ahead of any package's own entry, so on an unscoped kernel the by-name read naming the other package used to serve this container's view.protocol.ts).resolveRowlessExpandedView)..changeset/21980-copy-overlays-shipped-names.md,@objectstack/metadata-protocolminor. It carriesClause-②: no (narrowing), a BREAKING line naming the three save-door shapes with their remedy, and the ADR-0087not-required (no-migration-prescription)marker in the shape of the earlier save-door narrowing. The marker states that no census of the writers of such copies was taken.The save door narrows in three shapes
The collision predicate is unchanged; it judges the copy at its new names. Each shape is a package's copy of a container it ships on another package's object. Each was accepted on base and is refused on head with
VALIDATION_ERROR/ 400, measured on both kernels:listwhose loader name,OBJECT.default, only the other package ships;formViewskey);Unchanged:
A package-less copy whose package is resolved by registry order now takes the loaders' names in both orders. At base it took them in one order only.
The ruling's condition, measured before opening this
The condition: on an unscoped kernel, for a name two packages share, the by-name read naming NO package must answer, never an absence.
It was measured through
getMetaItemwith nopackageId:aa09db58c9, the directionb7a2a8f547, and the hydration line2322b5bb04. Each point was a trap-guarded swap ofprotocol.ts, and each restore was proven by blob equality and an emptygit diff HEAD.Results:
servedViewExpansionnaming no package)._packageId. The same mislabel happens on base in the no-owner case. See the Acceptance notes.This is pinned as block (h): an answer on both kernels, the same body on both, and a body the env-wide list serves under the name.
Census of the readers of the hydrated bare entry outside
metadata-protocol:getItem,listItemsorgetArtifactItemonviewdirectly.MetadataManager.getViewsByObjectreads its own loader store.getandlistaregetMetaItem's step 2, which runs after step 1b has answered the expansion from its row.Pins
All pins are in
packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts, inside #21967's block, reusing its registry double and its composition of the doors.task, and either one stores a copy. The by-name read naming each package answers that package's own item, on both kernels.Measured
5de8db7939: 10 red / 51 green. The reds are exactly the placements where the copying package ships the container on another package's object.@objectstack/metadata-protocolsuite atca60b61d7b(mergedorigin/mainat803764a36f): 218 files and 28127 tests passed, 19 skipped (os-verify-lockVERDICT command-exit 0). Typecheck is green, and the pin file is in the program.ca60b61d7b, throughscripts/ablation-replace.mjs. Each anchor hit once and the blob changed; each restore was proven (blob equals HEAD400cd431ef84,git diff HEADempty). The subject is imported from source, so there is no dist leg. Predictions were written first.dist/index.d.tsanddist/index.d.ctswere built fromorigin/main'sprotocol.tsand from head, then diffed. Apart from comments, the only difference is three untyped private member lines onObjectStackProtocolImplementation; no exported signature moves. The narrowing arm is for the save door's accept set (above).dispatch-gates --commands(no paths) derives 64; all 64 exit 0, and--ranreports 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.check-closing-target-claim,check-partof-closing-keyword,check-single-claim-paths).eslint.config.mjs(its TS glob covers both.tsfiles; no glob matches.md). The JSON count is 2 files, 0 errors, 0 warnings. There is no typed linting and the config's only disk reads are two untouched baselines, so the diff cannot move an untouched file's verdict.Acceptance notes
getMetaItem(:10431) graftslookupArtifactItem(type, name)with no package onto the body it serves. On base this happens when two packages ship one container on an object nobody owns and one stores a copy. From this PR on, it also happens when the object's owner is the other package. Not changed here: it is a by-name read change outside the ruling. Reported to the seat with evidence; the pin (h) does not pin which package's body or stamp that read answers.isDefaulton the default list of a container a package ships on another package's object, while that package's stored copy of it declares no default (the seat's earlier answer, kept). Read only, not measured on a door.Generated by Claude Code