Repository navigation
Commit 8caa131
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)
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 no
`packages/spec` edit, no loader edit, no `rest-server.ts` edit and no
governed path.
## What changes
- **The copy's names.** `expandRuntimeViewContainer` gives the loaders'
arm to a stored copy of a container its own package ships, bound to the
same object (`copiesOwnShippedViewContainer`). Each member is served as
`OBJECT.KEY`, in the copying package's own slot, which the list and the
by-name read already select per package (`servedViewExpansion`).
- Every other container on another package's object keeps the own-name
arm.
- A copy on another package's object still declares no default view (the
seat's answer on the own-name arm, unchanged).
- The loaders' names do not change.
- "The copying package ships this container" is read through the one
existing lookup, now a helper `shippedViewContainerOf` that
`overlaidShippedContainerViewNames` shares (no behaviour change there).
The object binding is read by `runtimeViewContainerObject`, which is the
base derivation chain extracted unchanged.
- **The unscoped registry hydration** (OQ1 A).
`hydrateExpandedViewItems` no longer registers an expansion under the
bare name when another package ships that name (`shippedArtifactsOf`).
- `SchemaRegistry.getItem` answers 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.
- This predates the copy's new names. It happens when two packages ship
one container and one of them stores a copy (measured on base
`protocol.ts`).
- Every kernel's by-name read already answers such an expansion from its
stored row, ahead of the registry (`resolveRowlessExpandedView`).
- **Changeset:** `.changeset/21980-copy-overlays-shipped-names.md`,
`@objectstack/metadata-protocol` `minor`. It carries `Clause-②: no
(narrowing)`, a BREAKING line naming the three save-door shapes with
their remedy, and the ADR-0087 `not-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:
- the copy adds a bare `list` whose loader name, `OBJECT.default`, only
the other package ships;
- the copy adds a keyed member whose loader name only the other package
ships (measured with a `formViews` key);
- another stored container, under a different row name, already expands
a name the copy now expands. This is reachable only by install order.
Unchanged:
- a copy with only its shipped members;
- the package's own view item row of a name the copy expands, which
keeps its slot.
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 `getMetaItem` with no `packageId`:
- on both kernels and in both registry orders;
- for each member, with the object owned by the other package or by
none;
- at three points: base `aa09db58c9`, the direction `b7a2a8f547`, and
the hydration line `2322b5bb04`. Each point was a trap-guarded swap of
`protocol.ts`, and each restore was proven by blob equality and an empty
`git diff HEAD`.
Results:
- **No read answers nothing**, anywhere.
- **The hydration line leaves this read unchanged** on both kernels: the
direction's rows and head's rows are identical.
- **Which body the read answers is not owner-stable, and it was not at
base either.**
- At base it answered whichever package registered first. That was the
owner's shipped item in one order and the copying package's in the
other.
- From the direction on, it answers the copy's body, which is the last
expansion of the name (`servedViewExpansion` naming no package).
- When the other package registered first, that answer carries the other
package's `_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`:
- No non-test file calls `getItem`, `listItems` or `getArtifactItem` on
`view` directly.
- `MetadataManager.getViewsByObject` reads its own loader store.
- The objectql facade's generic `get` and `list` are `getMetaItem`'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.
- **(f) The family's enumeration**, derived as a cross product: who owns
the object (the copying package, another package, none), whether the
copying package ships the container, and the member (bare list, keyed
member, default form). Both kernels. Each placement asserts:
- reach on both read doors;
- the default the copy declares;
- no name that only another package ships;
- the owner's names intact on both read doors;
- for a form, the withdrawal shuts the anonymous doors, with a control:
saved open, the form is served.
- **(g) The earlier no-owner case.** Two packages ship container `task`,
and either one stores a copy. The by-name read naming each package
answers that package's own item, on both kernels.
- **(h)** The condition above.
## Measured
- **Base reading** of (f) at `5de8db7939`: 10 red / 51 green. The reds
are exactly the placements where the copying package ships the container
on another package's object.
- **Full `@objectstack/metadata-protocol` suite** at `ca60b61d7b`
(merged `origin/main` at `803764a36f`): 218 files and 28127 tests
passed, 19 skipped (`os-verify-lock` VERDICT command-exit 0). Typecheck
is green, and the pin file is in the program.
- **Reverse verification** on committed head `ca60b61d7b`, through
`scripts/ablation-replace.mjs`. Each anchor hit once and the blob
changed; each restore was proven (blob equals HEAD `400cd431ef84`, `git
diff HEAD` empty). The subject is imported from source, so there is no
dist leg. Predictions were written first.
- The hydration line taken out: predicted 9 red / 162 green, measured 9
/ 162. The reds are (f)'s 3 unscoped owner reads and (g)'s 6 unscoped
cases; (h) stays green, as the condition measurement predicted.
- The own-name arm restored for the copy: predicted 10 red / 161 green,
measured 10 / 161.
- **Clause-②**, measured against the built entry declarations:
`dist/index.d.ts` and `dist/index.d.cts` were built from `origin/main`'s
`protocol.ts` and from head, then diffed. Apart from comments, the only
difference is three untyped private member lines on
`ObjectStackProtocolImplementation`; no exported signature moves. The
narrowing arm is for the save door's accept set (above).
- **Gates:**
- `dispatch-gates --commands` (no paths) derives 64; all 64 exit 0, and
`--ran` reports 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.
- The artifact-roster block: 51 of 54 exit 0 before this PR existed. The
other three need a PR's context (`check-closing-target-claim`,
`check-partof-closing-keyword`, `check-single-claim-paths`).
- The four symbol-anchor sweeps are green.
- Lint, narrowed: the population is read from `eslint.config.mjs` (its
TS glob covers both `.ts` files; 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
- **The by-name read naming no package, for a name two packages ship**,
answers the copy's body wearing the envelope of whichever package
registered first. `getMetaItem` (`:10431`) grafts
`lookupArtifactItem(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.
- The loaders keep `isDefault` on 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](https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9a0401f commit 8caa131
3 files changed
Lines changed: 370 additions & 10 deletions
File tree
- .changeset
- packages/metadata-protocol/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
0 commit comments