Repository navigation
finding(metadata-protocol): a stored copy of a view container a package ships on another package's object expands under its own name (#21334's arm), so a form withdrawn in that copy does not reach the package's shipped form of that name #21980
Description
Activity
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsPath: ② the capabilities an end user meets in the app — public forms | 缺项 (no item asserts a withdrawal saved in a package's stored copy reaches that package's shipped form) | P2
Triage: first grade —
bug·security·priority:p2·domain:engine·area:access·pm:blocked(findingremoved). A stored copy of a container its package ships expands to the names the loaders gave that package. This amends my #21334 ruling5946423948Blocked-by: #21967
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T10:54Z. ⛔ Not a claim, ⛔ not a dispatch. Classes, positions and functions only.Triage: lands in
packages/metadata-protocol/src/protocol.ts(expandRuntimeViewContainer's cross-package branch, about:18316, andexpandUnderOwnName, about:18419) ⇒domain:engine; rationale: the runtime expansion names a package's stored copy differently from the loaders' names for the same package's shipped container.Verified on
main(dcf3eb494a):- Both loaders expand a shipped container with
expandViewContainerunder the object's name, whichever package owns the object:objectql/src/engine.tsabout:7159–:7165, andmetadata/src/plugin.tsabout:1198–:1210. - The runtime expansion takes the own-name arm whenever
isAnotherPackagesObjectholds. So a package's stored copy of its own shipped container overlays none of that package's shipped views.
This amends my ruling
5946423948on #21334. The ruling said a container on another package's object expands under its own name, so that it never writes a name another package owns. It did not separate a container the copying package itself ships. For that container, the loaders have already published the names, keyed to that package, and the copy must overlay them. The ruling's reason still holds: no expansion writes a name owned by another package. It is triage's ruling, so triage refines it; the maintainer can still overturn it.Direction:
- A container the copying package ships: its stored copy expands to the same names the loaders gave that package's shipped views, keyed to that package. It relies on the per-package keying finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967 is landing.
- Any other container on another package's object: it keeps the own-name arm, unchanged.
- The seat's
isDefaultanswer on metadata: a view container with a bare list on another package's object silently replaces that object's packaged default view on GET /meta/view?object= — while the by-name read still serves the original #21334 (5955628428, OQ1) is unchanged. - ⛔ The loaders' names do not change: a shipped view's name is published, and renaming it is a migration.
- ⛔ No new key.
Stop condition: if making the copy overlay its shipped names needs anything beyond #21967's per-package keying (a different by-name read, or a change to what the loaders register), the dev returns
needs_decision, and the card goes to the maintainer's box.Pins (this card closes the family; see below):
- An enumeration pin over every placement a stored copy can take. The object is owned by the same package, by another package, or by none. The container is shipped by the copying package or not. The member is the bare list, a keyed member, or a form.
- For each placement, a withdrawal saved in the copy is honoured at both anonymous doors, and the expansion never writes another package's name.
- metadata: a view container with a bare list on another package's object silently replaces that object's packaged default view on GET /meta/view?object= — while the by-name read still serves the original #21334's pins and finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967's pins stay green.
Why
securityand p2: a form that a package withdraws in its stored copy can still be served on the anonymous doors. Reaching it needs that package's stored copy, which is an administrator's act. That is the same reach as #21967, so the same grade.Family. This is the withdrawal-reach family after #21835, #21934 and #21967. A third occurrence gets the closing card, so this card carries the enumeration pin above, not one case.
Why blocked: PR #21979 (#21967) edits the same list read and lands the per-package keying this direction relies on.
Prior rulings read: view container, expansion, own name, package, withdrawal → triage's #21334 ruling
5946423948(amended above), the seat's5955628428(kept), ADR-0005's name-keyed overlays, ADR-0126 (no silent override); thread: #21334, #21639, #21967.
Generated by Claude Code
- Both loaders expand a shipped container with
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 6, 2026 objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsPath: ② the capabilities an end user meets in the app — public forms | 缺项 | P2
Unlock:
pm:blocked→pm:queue. #21967 landed the per-package keying this direction relies onTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T12:59Z. ⛔ Not a claim, ⛔ not a dispatch. Classes, positions and functions only.- The blocker, verified: PR fix(metadata-protocol): another package's withdrawal of a form holds at the anonymous form endpoints, whatever packages' copies of a view container are saved #21979 merged as
db87a025df, and finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967 closedcompleted(landing record6015752210). The env-wide view list now serves each name-and-package slot through one selection,servedViewExpansion.resolveRowlessExpandedViewselects through the same function. - The direction stands as graded (
6014736043):- a package's stored copy of a container it ships expands to the names the loaders gave that package, keyed to it, so
servedViewExpansionsees it in that package's own slot; - any other container on another package's object keeps the own-name arm;
- ⛔ the loaders' names do not change.
- a package's stored copy of a container it ships expands to the names the loaders gave that package, keyed to it, so
- The stop condition is unchanged: anything beyond the per-package keying that is now on
mainreturnsneeds_decision. - The enumeration pin over every placement is unchanged, and this card still closes the family.
Generated by Claude Code
- The blocker, verified: PR fix(metadata-protocol): another package's withdrawal of a form holds at the anonymous form endpoints, whatever packages' copies of a view container are saved #21979 merged as
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsClaim: PM loop round 46 · 2026-10-06T13:05Z
Session:session_017ErfyP2Rx7XWHJA27QjyUi
Account:os-project-manager(the seat's linked user, asGET /useranswers it; always the card's assignee)
Branch:claude/issue-21980-copy-overlays-shipped-names
Worktree:objectstack-issue-21980
Domain:domain:engine
Seat:domain:engine#1
Provenance: triage graded this card (6014736043), amending its own #21334 ruling5946423948. It unlocked the card topm:queue(6016790773) once #21967 landed through PR #21979 (db87a025df). It is the only eligiblepm:queuecard in this lane. The lane runs three concurrent claims (the maintainer, verbatim: 「并发3」), and no dev slot is in use: PR #21990 (#21978) waits on CI. This seat filed the card from #21967's dev report, and it carries no withheld detail.
File surface (atorigin/mainaa09db58c9), per triage's direction 6014736043:packages/metadata-protocol/src/protocol.ts:expandRuntimeViewContainer's cross-package branch (about:18540–:18568,isAnotherPackagesObjectat:18641).expandUnderOwnName(about:18669).
- A container the copying package ships: its stored copy expands to the same names the loaders gave that package's shipped views (
expandViewContainerunder the object's name,objectql/src/engine.ts:7159–:7165,metadata/src/plugin.ts:1198–:1210), keyed to that package, soservedViewExpansionsees it in that package's own slot. - Any other container on another package's object: it keeps the own-name arm, unchanged.
- ⛔ The loaders' names do not change. ⛔ No new key. The seat's
isDefaultanswer on metadata: a view container with a bare list on another package's object silently replaces that object's packaged default view on GET /meta/view?object= — while the by-name read still serves the original #21334 (5955628428) is unchanged. - Stop condition (triage): if the copy overlaying its shipped names needs anything beyond finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967's per-package keying (a different by-name read, or a change to what the loaders register), the dev returns
needs_decision. - Pins (this card closes the withdrawal-reach family):
- An enumeration pin over every placement a stored copy can take:
- the object is owned by the same package, by another package, or by none;
- the container is shipped by the copying package or not;
- the member is the bare list, a keyed member, or a form.
- For each placement, a withdrawal saved in the copy is honoured at both anonymous doors, and the expansion never writes another package's name.
- metadata: a view container with a bare list on another package's object silently replaces that object's packaged default view on GET /meta/view?object= — while the by-name read still serves the original #21334's and finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967's pins stay green.
- Unit pins in
metadata-protocol. One public dogfood case underpackages/qa/dogfood/test/only if a door-level pin is measured necessary, declared on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024.
- An enumeration pin over every placement a stored copy can take:
.changeset/21980-*.md(@objectstack/metadata-protocolpatch).
Container & model:M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate).
Clause-②: no (narrowing)- Revised by the seat at 2026-10-06T16:29Z on the maintainer's ruling: batch 🔗 Broken links detected in documentation #282 item 1, decision card decision: #21980 OQ1 — the unscoped kernel's bare-name registration lets one package's stored copy answer another package's by-name read: fix it in #21980, split it out, or fix it first? #22004, pointer 6020226416, OQ2 → A. The claim first read
no. - The copy's new names narrow what the save door accepts in three edge shapes (the dev's report 6018023897, H3 a/b/c), each refused with
400 VALIDATION_ERRORwheremainaccepted it. - BREAKING: at least
minor, a!subject, and an ADR-0087not-required (no-migration-prescription)marker, Two stored view containers of one object still displace each other's views: a container bound elsewhere or unbound under a sibling's expanded name, and a second container's bare list taking the first's<object>.default, are accepted with no diagnostic #21639's shape. The changeset notes that no census of the writers of such copies was taken. No exported declaration moves. - The file surface also gains
hydrateExpandedViewItems(OQ1 → A): it stops registering the bare name of an expansion whose name another package ships.
Thread-read: 6016790773
Serial constraints cleared: at 2026-10-06T13:05Z: - Triage's serial finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967 closed
completedthrough PR fix(metadata-protocol): another package's withdrawal of a form holds at the anonymous form endpoints, whatever packages' copies of a view container are saved #21979. - Open PRs (fix(metadata-protocol): put and delete accept the version a checksum-less sys_metadata row is served as #21990, chore: version packages #21988, feat(spec)!: the build doors refuse a builtin node config value its executor contract refuses, with its location #21974), each file list read by
filename: none touchesprotocol.ts. This lane's PR fix(metadata-protocol): put and delete accept the version a checksum-less sys_metadata row is served as #21990 (finding(metadata-protocol): a sys_metadata row with no checksum cannot be edited or removed through the metadata door: the read hands out a computed version, and put / delete compare it against the raw null column (409 METADATA_CONFLICT) #21978) touchessys-metadata-repository.tsonly. - [finding]
sys_member.add_memberis offered to every organization member, owners and admins included, but its door admits only a platform admin #21886 (sys-member.object.ts, decision box) shares no file with this card.
4 remaining items
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsPath: ② the capabilities an end user meets in the app — public forms | 缺项 | P2
Triage: the stop condition fired, so this goes to the maintainer's box, as the grade promised. The claim stands, and
pm:retriageis removed while the question is with the maintainerTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T14:56Z. ⛔ Not a claim, ⛔ not a dispatch. Classes, positions and functions only.- What fired: my grade (
6014736043) said that anything beyond finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967's per-package keying returnsneeds_decisionand goes to the maintainer's box. The dev's report (6018023897) and the seat's retriage (6018073961) show the unscoped kernel's hydration path is that "beyond".- The bare-name registration in
hydrateExpandedViewItems, answered first bygetMetaItem's registry fall-through, serves another package's copy to the owner's by-name read. - It predates this card, as the no-owner case on base shows.
- The bare-name registration in
- Put to the maintainer in the triage seat's chat at this write, in the director format:
- OQ1: the hydration fix, in this card (A), split out (B), or first (C);
- OQ2: the clause-② disposition of the three save-door narrowings.
- Triage's own reading, for the record and not a ruling: OQ1 A, on one condition. Before it lands, the dev measures the readers of the hydrated bare entry outside
metadata-protocol(view-authoring-live: the documented view-container authoring path is inert at runtime — the container is stored but never served #7736, rest/meta: getViewsByObject / GET /meta/view?object= omits a runtime-authored view CONTAINER — #7163/#7736 expansion fix does not cover this path #13407). If any reads it, A stops and C applies. OQ2 A, the Two stored view containers of one object still displace each other's views: a container bound elsewhere or unbound under a sibling's expanded name, and a second container's bare list taking the first's<object>.default, are accepted with no diagnostic #21639 shape. - Nothing moves until the answer is recorded here. The branch (
b7a2a8f547, pin5de8db7939) stays as pushed.
Labels:
pm:retriageis removed;pm:dispatchedand the claim stay.
Generated by Claude Code
- What fired: my grade (
- removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Oct 6, 2026 objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsRuling pointer: batch #282 item 1 (decision card #22004) · OQ1 → A with one measurement condition · OQ2 → A as execution · maintainer 「同意」 2026-10-06T15:59Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(via the relay). The record is 6020103367 on #22004, which is closed; this card's claim (6016913296) andpm:dispatchedstand. Thread-read: 6019010019.- OQ1 → A. The unscoped kernel's hole closes here:
hydrateExpandedViewItemsstops registering the bare name of an expansion whose name another package ships, with a pin for the pre-existing no-owner case, landed with the pushed direction (b7a2a8f547, pin5de8db7939). - The condition before the merge. On an unscoped kernel, for a name two packages share, a by-name read with NO package named must answer the owning package's shipped item after the change as it does before, never an absence. The seat's reading on
origin/mainf7b8a593: no non-test file outsidemetadata-protocolcallsgetMetaItem('view', …)directly, so that generic read (the REST meta route, the registry fall-through) is the reader to measure. If the read turns into an absence, A stops and the order is C: the hydration fix on its own card first (Blocked-by), then the direction unchanged. B is not taken. - OQ2 → A, execution:
Clause-②: no (narrowing), aminorchangeset with a BREAKING line and the ADR-0087no-migration-prescriptionmarker (Two stored view containers of one object still displace each other's views: a container bound elsewhere or unbound under a sibling's expanded name, and a second container's bare list taking the first's<object>.default, are accepted with no diagnostic #21639's shape); the changeset notes that no census of the writers of such copies was taken. - Sequencing: a 17.x fix; it is on the list of the last 17.x release (decision: before the v18 line opens, does main publish one last 17.x release to npm? #22009's ruling). The
domain:engineseat lets the same dev continue.
Generated by Claude Code
- OQ1 → A. The unscoped kernel's hole closes here:
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21980, "status": "done", "branch": "claude/issue-21980-copy-overlays-shipped-names", "pr": "https://github.com/objectstack-ai/objectstack/pull/22023", "session": "session_017ErfyP2Rx7XWHJA27QjyUi (mode:subagent, the PM's session; identity is the branch). Resumed on the maintainer's ruling: batch #282 item 1, decision card #22004, record 6020103367, pointer 6020226416 (read in full). The claim 6016913296 now reads 'Clause-②: no (narrowing)'.", "premise_still_valid": true, "summary": "Draft PR #22023 lands the card under the ruling. A package's stored copy of a container it ships overlays that package's shipped views, so a withdrawal saved in the copy holds at the anonymous form doors, on both kernels; the direction is pushed unchanged at b7a2a8f547. OQ1 A: hydrateExpandedViewItems (protocol.ts:18878-18891) no longer registers, under the bare name, an expansion whose name another package ships (shippedArtifactsOf). This closes the unscoped by-name hole, including the pre-existing no-owner case, which pin (g) covers. The ruling's condition was measured before opening the PR. The by-name read naming no package answers everywhere: never an absence, at base, at the direction and at head, on both kernels and in both registry orders. The hydration line leaves that read unchanged; pin (h) covers it. One difference from the condition's wording is open in OQ1 below. OQ2 A: a minor changeset with '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, which says no census of the writers of such copies was taken. The commit carrying it is 'fix(metadata-protocol)!:', and so is the PR title.", "H1": "CONFIRMED (unchanged from round 1). At base aa09db58c9, protocol.ts:18566-18569 took expandUnderOwnName (:18669) whenever isAnotherPackagesObject (:18641) held, naming members OBJECT.CONTAINER.KEY. The loaders name a shipped container's members OBJECT.KEY for the shipping package, whichever package owns the object (objectql/src/engine.ts:7159-7165, metadata/src/plugin.ts:1198-1210). 'The copying package ships this container' is read through the one existing lookup, now the helper shippedViewContainerOf (head :18641), which overlaidShippedContainerViewNames shares. The object binding is read by runtimeViewContainerObject (head :18627), the base chain extracted unchanged.", "H2": "PARTLY FALSIFIED in round 1, resolved by the ruling's OQ1 A. The copying package's slot needed nothing beyond #21967's keying: the list's servedViewExpansion and resolveRowlessExpandedView both serve the copy there. The other package's by-name read on an unscoped kernel needed the hydration line. hydrateExpandedViewItems registered every expansion under the bare name, and getMetaItem's registry fall-through (SchemaRegistry.getItem, objectql/src/registry.ts:3957) answers the bare key first. With the line, pin (f)'s 3 unscoped owner reads and pin (g)'s 6 unscoped cases are green.", "H3": "UNCHANGED from round 1. The save door narrows in three shapes, each accepted on base and refused on head with VALIDATION_ERROR/400 on both kernels: (a) an added bare list whose loader name only the other package ships; (b) an added keyed member whose loader name only the other package ships; (c) a sibling stored container under another row name already expanding the name, reachable only by install order. Unchanged: (d) the package's own view item row. (e) A package-less copy resolved by registry order now takes the loaders' names in both orders. The collision predicate is not edited.", "condition": "MEASURED; the stop criterion (an absence) did not fire. Measured through getMetaItem with no packageId, on both kernels and in both registry orders, for each of the 3 members, with the object owned by the other package or by none. Three points: base aa09db58c9 (blob 177e9170), the direction b7a2a8f547 (blob 269a8e6b) and head 2322b5bb04 (blob 400cd431). Each was a trap-guarded swap of protocol.ts, with the restore proven by blob equality for both swapped files and an empty git diff HEAD. Results: 24 cells per point, 72 reads, every one answers an item. The hydration line leaves this read unchanged: the direction's 24 rows equal head's 24 rows on both kernels. The answer is not owner-stable, and it was not at base either. At base it answered whichever package registered first (with the owner first, the owner's shipped item; with the copying package first, that package's shipped item). From the direction on it answers the copy's body, the last expansion of the name (servedViewExpansion naming no package). When the other package registered first, that answer carries the other package's _packageId: getMetaItem :10431 grafts lookupArtifactItem(type, name) with no package. On base the same mislabel already happens in the no-owner case, e.g. 'no-pkg after copy: Intake (pkg_b copy) [pkg_a]'. Pin (h) asserts what the ruling guards: an answer on both kernels, the same body on both, and a body the env-wide list serves under the name. It deliberately does not pin which package's body or stamp. Census of the hydrated bare entry's readers outside metadata-protocol, at the merged head: 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, after step 1b has answered the expansion from its row.", "placement_table": [ "(f), 18 placements x 2 kernels. Owner = the copying package, shipped or not; owner = none, shipped or not: the copy is served under OBJECT.KEY (task.default, task.intake_form, task.form). Green at base and at head.", "Owner = another package, not shipped: the own-name arm, OBJECT.CONTAINER.KEY (task.task, task.task.intake_form, task.task.form). Green at base and at head.", "Owner = another package, shipped by the copying package: OBJECT.KEY. Red at base (10 cases); at the direction, 3 unscoped owner by-name reads red; at head all green on both kernels.", "(g), 3 members x 2 copying packages x 2 kernels = 12 cases: two packages ship container task, one stores a copy, and the by-name read naming each package answers its own item. Green at head; the 6 unscoped cases are red with the hydration line taken out.", "(h), 3 members x 2 owners (the other package, none) x 2 registry orders = 12 cases, each over both kernels: the no-package by-name read answers, with the same body on both kernels, a body the list serves. Green at head and with the hydration line taken out.", "Pin file totals at head: 171 cases, all green." ], "tests": "Head ca60b61d7b (merged origin/main at 803764a36f). Full @objectstack/metadata-protocol suite under os-verify-lock, VERDICT command-exit 0: 218 files passed, 3 skipped; 28127 tests passed, 19 skipped. Pin file: 171/171. Typecheck: pnpm --filter @objectstack/metadata-protocol typecheck exits 0 (exit captured before any pipe); the pin file is in the program (--listFiles hit in round 1, same tsconfig). REVERSE VERIFICATION on committed head ca60b61d7b through scripts/ablation-replace.mjs, predictions written into the driver scripts first. Each anchor hit once and the blob changed on disk; each restore was proven: blob == HEAD 400cd431ef84, git diff HEAD empty, git status clean. (1) The hydration line taken out (anchor 'if (anotherShips) continue;', blob 400cd431ef84 -> cb58d51e1385): predicted 9 red / 162 green, measured 9 / 162. The reds: (f)'s 3 unscoped owner reads and (g)'s 6 unscoped cases; (h) green, as the condition measurement predicted. (2) The own-name arm restored for the copy (anchor '&& !this.copiesOwnShippedViewContainer(...)', blob -> 1074fe2c933b): predicted 10 red / 161 green, measured 10 / 161, all in the shipped-on-another-package's-object placements. The subject is imported from source (./protocol.js), so there is no dist leg. The round-1 measurements (base reading 10 red / 51 green, H3 probes) stand as reported in 6018023897.", "gates": "At head ca60b61d7b. node scripts/pm/dispatch-gates.mjs --commands (no paths, --repo objectstack-ai/objectstack) derived 64 commands from 3 paths (the changeset, the pin file, protocol.ts); all 64 exit 0. --ran with exit-coded lines reads '64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)'. Three first answered PREREQUISITE NOT MET (exit 3, unbuilt workspace): check:dual-build-cjs-loads, check:lean-entry-closure and check:published-readme-exports. check:type-check-debt ran under the lock (VERDICT command-exit 0, 339s) and built the workspace, and all three reran green. The artifact-roster block outside the total (54 commands, 37 verdict + 17 self-test) is 54 exit 0. check-closing-target-claim, check-single-claim-paths and check-partof-closing-keyword were run with PR #22023's context after it opened. The ADR-0087 gate shows 1 declared-breaking changeset with its not-required (no-migration-prescription) disposition. The no-major gate finds no major. The 4 symbol-anchor sweeps (check:adr-symbol-anchors, check:scripts-symbol-anchors, check:spec-docblock-symbol-anchors, check:adr-anchors) are green. LINT, narrowed with all three proofs: (1) the population is read from eslint.config.mjs: its TS glob covers both .ts files, and no files glob matches .md; (2) eslint --no-inline-config --format json counts 2 files, 0 errors, 0 warnings; (3) invariance: no parserOptions.project or projectService, and the config's only disk reads are scripts/slot-lookup-baseline.json and scripts/query-options-erasure-baseline.json, neither touched, so the diff cannot move an untouched file's verdict. Control bytes: grep -P over the 3 paths gives 0 lines; check:nul-bytes is green. origin/main moved by one commit after the merge (1fb274e61c, #22001, which also edits protocol.ts); git merge-tree --write-tree HEAD origin/main is clean (exit 0), and protocol.ts carries no custom merge driver. It is not re-merged; CI runs on the merge ref. CI is not awaited.", "line_budget": "n/a", "deviations": [ "The ruling's condition says the no-package read 'answers the owning package's shipped item before the change and still answers it after'. Measured: it never answers nothing (the stop criterion), and the hydration line does not change it. But it was not owner-stable at base (registry order decided), and from the direction on it answers the copy's body. Pin (h) asserts the guarded half (an answer, the same on both kernels, a body the list serves), not the owner. The PR was opened because the stated stop criterion did not fire; the difference is OQ1 for the seat before merge.", "Block (h) and block (g) live inside block (f), so they reuse its harness. The no-package read pin derives its placement from PLACEMENTS rather than restating it.", "origin/main moved by one commit after the merge commit. It merges cleanly and is not re-merged, so the gate union stands on ca60b61d7b.", "A gate run was moved to the background by the foreground cap (check:type-check-debt under the lock). It was waited on in the foreground with tail --pid and read from its own log within this turn.", "Attribution: commits carry AGENTS.md's model-free trailer pair, and the PR body ends with AGENTS.md's session-URL footer, not the harness reminder's model-named forms. AGENTS.md takes precedence, and the pre-push hook refuses a model id." ], "files_changed": [ "packages/metadata-protocol/src/protocol.ts (+104/-10 vs main): copiesOwnShippedViewContainer, shippedViewContainerOf (shared with overlaidShippedContainerViewNames), runtimeViewContainerObject (extracted unchanged), the expandRuntimeViewContainer arm, the hydrateExpandedViewItems line, and docblocks", "packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts (+239/-0): blocks (f), (g) and (h)", ".changeset/21980-copy-overlays-shipped-names.md (+27/-0, new): @objectstack/metadata-protocol minor" ], "clause_2": "no (narrowing), as revised in the claim. Declarations: dist/index.d.ts and dist/index.d.cts were built from origin/main's protocol.ts (blob 177e9170, trap-guarded swap, restore proven) and from head ca60b61d7b, then diffed (11544 -> 11609 lines). The non-comment difference is exactly three untyped private member lines on ObjectStackProtocolImplementation: private runtimeViewContainerObject; private shippedViewContainerOf; private copiesOwnShippedViewContainer. No exported signature moves. Accept set: the save door now refuses three shapes it accepted (H3 a, b, c); that is the narrowing arm, carried in the changeset and on line 2 of the PR body.", "mcp_calls": "0", "api_writes": "3 relay strokes this round, each one POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create, executed as POST /repos/objectstack-ai/objectstack/pulls: PR #22023, draft, run 37502124560, 9708 bytes sent and stored identical, read back; (2) label-write assignee, executed as POST /repos/objectstack-ai/objectstack/issues/22023/assignees: run 37502244926, read back assignee os-project-manager; no label written, and size/m was added by another actor; (3) this os-dev-report comment, executed as POST /repos/objectstack-ai/objectstack/issues/21980/comments. Plus git push (not REST): 3 fast-forward pushes this round (2322b5bb04; 7a799e8c7e + bcd4915012; the merge ca60b61d7b). No pushed commit was amended. Round 1 wrote 1 comment (6018023897) and 3 pushes.", "open_questions": [ { "question": "OQ1 (before merge, for the seat). The ruling's condition expects the no-package by-name read to answer 'the owning package's shipped item before the change and still after'. Measured: it never answers nothing, and the hydration line leaves it unchanged. But for a name two packages ship it was never owner-stable: at base, registry order decided. From the direction on it answers the copy's body (servedViewExpansion naming no package, #21967's rule), and when the other package registered first that answer wears the other package's _packageId (getMetaItem :10431). Is that acceptable for merge, with the stamp filed as its own card?", "options": [ "A: merge as is, and file the no-package envelope graft as its own card (class a, the by-name provenance family). Business need: the p2 withdrawal fix lands on the last 17.x list. Long-term: the graft predates this card (the no-owner case on base) and needs its own by-name read change. AI-error: an AI reading a no-package by-name answer can be told the wrong package until that card lands. Startup focus: no extra scope here.", "B: hold the merge until the graft is closed, by a by-name read change that grafts the served expansion's own package's artifact. Business need: delays a p2 security fix. Long-term: the same end state. Startup focus: widens this card past the ruling's scope." ], "recommendation": "A. The ruling's stop criterion is an absence, and none was measured. The hydration line, the ruling's subject, is neutral for this read. The owner-instability and the stamp predate the direction (base: registry order, and the no-owner graft), so they belong to the by-name read's own card, not to this family's closing card." } ], "out_of_scope_findings": [ "class: a · reach: in-process getMetaItem with no packageId (the method GET /api/v1/meta/view/NAME calls when no package is named), measured on both kernels at base aa09db58c9 and at head; not over HTTP. Evidence: two packages ship container task (no code package owns the object) and pkg_b stores a copy. With pkg_a registered first, getMetaItem with no package answers 'Intake (pkg_b copy)' carrying _packageId pkg_a, on base and on head, on both kernels. From this PR on, the same happens when pkg_a owns the object. Mechanism: getMetaItem :10431 merges lookupArtifactItem(type, name) with no package (the first composite, the first-registered package) over the body step 1b served (the last expansion, servedViewExpansion naming none). Family: by-name read provenance (the envelope a served item wears). It is not this withdrawal-reach family, so it is not folded here. Dedupe words: by-name read naming no package grafts first registered package envelope onto another package's expansion · getMetaItem no packageId _packageId mislabel view expansion · lookupArtifactItem without package wrong provenance served view", "carrier: none · noted, not filed (PR Acceptance notes). 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
objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsMerge condition measured: no absence, but the no-package answer is not the owner's. Confirmation requested before landing PR #22023 ·
pm:retriagedomain:engine#1·session_017ErfyP2Rx7XWHJA27QjyUi· 2026-10-06T17:22Z. The dev's report is 6021602255. PR #22023 is a draft atca60b61d7b(Fixes #21980,Clause-②: no (narrowing)). ⛔ The claim (6016913296) andpm:dispatchedstay. The seat does not land the PR until this is answered.The ruling's condition (6020226416, verbatim): "On an unscoped kernel, for a name two packages share, a by-name read with NO package named must answer the owning package's shipped item after the change as it does before, never an absence. … If the read turns into an absence, A stops and the order is C."
What the dev measured. The read is
getMetaItemwith nopackageId, on both kernels and in both registry orders, for each of the 3 members, with the object owned by the other package or by none. That is 24 cells at each of three points: baseaa09db58c9, the directionb7a2a8f547, and headca60b61d7b.- No absence anywhere. All 72 reads answer an item, so the stop criterion did not fire.
- The hydration line (OQ1 A) is neutral for this read. The direction's 24 answers equal head's 24.
- The answer is not the owner's shipped item in every cell, and it was not at base either:
- At base, registry order decided: the owner's shipped item when the owner registered first, otherwise the copying package's shipped item.
- From the direction on, the read answers the copy's body. That is finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967's no-package "any expansion" rule, applied to a copy that now carries the loaders' names. It is ADR-0005's read order, where a stored overlay of a name wins.
- So in the owner-registered-first cells, base answered the owner's shipped item and head answers the copy.
- Pin (h) asserts what the stop criterion guards: an answer, the same body on both kernels, and a body the env-wide list serves. It does not pin which package's body.
- The envelope. In the cells where the other package registered first, that answer carries the other package's
_packageId. That graft predates this card (the no-owner case on base) and is filed as finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024.
The seat's read.
- The operative stop criterion is an absence, and none occurs. The change of answer comes from the direction the ruling itself took (a copy that overlays its own package's shipped names), not from the hydration line the condition was set for.
- Before this card the answer was already order-dependent, so "as it does before" was never owner-stable.
- The seat reads PR 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 as within the ruling, with finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024 carrying the envelope. But the condition's words name "the owning package's shipped item", which the head does not answer in the owner-first cells. That interpretation is the maintainer's, not the seat's.
Asked (director and triage, for the maintainer):
- A (the dev's and the seat's recommendation). The condition is met: no absence, the hydration line is neutral, and a no-package read of a shared name answers the stored overlay. Land PR 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 on the last-17.x list, and handle finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024 on its own.
- B. The no-package read must answer the owner's shipped item. Hold PR 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 until a by-name read change makes that so. That is new scope beyond this ruling.
Meanwhile the seat takes the PR's CI and its landing readings, so that A lands without a further round.
Generated by Claude Code
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Oct 6, 2026 objectstack-fleet commented
on Oct 6, 2026 ContributorAuthorMore actionsTriage: the seat's question about the merge condition goes to the box as decision card #22027. The claim stands, and
pm:retriageis removed while the question is openTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T17:53Z. ⛔ Not a claim, ⛔ not a dispatch. Classes, positions and functions only.- The question (
6021658627): does the ruling's merge condition (6020226416) hold?- Measured: none of the 72 no-package by-name reads answers an absence.
- But head answers the stored copy, not the owner's shipped item, in the owner-registered-first cells. At base, the answer already depended on registry order.
- Why the box, not triage: the condition's own words name "the owning package's shipped item". Whether that or the absence criterion is operative is a reading of the maintainer's text. No prior ruling answers it;
check-prior-rulingsnamed ADR-0005's layered order, which bears on the options, not on the wording. - decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027 recommends A, with B as the fallback:
- A: the condition is met. Land PR 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 on the last-17.x list; finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024 carries the envelope.
- B: hold for an owner-first no-package read. That is new scope.
- Until it is answered: PR 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 stays a draft, and the seat may keep taking its CI readings, as it said it would. finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024 is graded on its own card, independent of this answer.
Labels:
pm:retriageis removed.pm:dispatchedand the claim stay.- The question (
- removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Oct 6, 2026 objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsRuling pointer: batch #283 item 2 (decision card #22027) · A · maintainer 「其他同意」 2026-10-07T01:26Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(via the relay). The record is 6028832449 on #22027, which is closed; this card's claim andpm:dispatchedstand. Thread-read: 6022209890.- The merge condition is met. It is read by what it guards against: a no-package by-name read of a shared name must never turn into an absence. The seat's measurement (6021602255, 72 reads across two kernels and two registration orders) found none. The condition's "the owning package's shipped item" was never a fixed baseline (registration order decided it before the change); after the change the stored copy answers, which is ADR-0005's layered order and this card's direction. B, an "owner wins when no package is named" rule, is not taken.
- Package identity in the answer (the
_packageIdenvelope) stays with finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024,pm:blockedp3; it does not hold this merge. - Before the merge: on the PR's head
ca60b61d7b,TypeScript Type Checkis red (itsType Check · source gatesleg cancelled; 29 green, 3 skipped). Thedomain:engineseat has the dev fix it and push; PR 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 merges when every check on the head is green, into the last 17.x release (decision: before the v18 line opens, does main publish one last 17.x release to npm? #22009's ruling: fixes only).
Generated by Claude Code
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsLanded: PR #22023 →
8caa131e52onmain. It merged through the merge queue at 2026-10-07T01:57Z, after entering the queue at 2026-10-07T01:34Z. Verified at 2026-10-07T01:57Z.domain:engine#1·session_017ErfyP2Rx7XWHJA27QjyUi.- The squash. It is on
origin/mainas a single-parent commit (parent9a0401fdd3). Its diffstat is the reviewed one: 3 files, +370/-10. - What is on
main.- A package's stored copy of a view container that package ships, bound to the same object, expands as the loaders expand the shipped container (
OBJECT.KEY). It lands in the copying package's own slot, so it overlays that package's shipped views, and a withdrawal saved in the copy holds at the anonymous form doors on both kernels. - Every other container on another package's object keeps the own-name arm, and the copy still declares no default.
- On an unscoped kernel,
hydrateExpandedViewItemsno longer registers, under the bare name, an expansion whose name another package ships (ruling decision: #21980 OQ1 — the unscoped kernel's bare-name registration lets one package's stored copy answer another package's by-name read: fix it in #21980, split it out, or fix it first? #22004, OQ1 → A). - The save door judges a copy at the names it now expands to, so three shapes it accepted are refused with
400 VALIDATION_ERROR(the changeset names them and their remedy).
- A package's stored copy of a view container that package ships, bound to the same object, expands as the loaders expand the shipped container (
- The card.
Fixes #21980closed this card ascompleted.pm:dispatchedis removed in this act. No other card was closed by the body. - From this release (
@objectstack/metadata-protocolminor, BREAKING,Clause-②: no (narrowing), ADR-0087not-required (no-migration-prescription)): a form withdrawn in a package's stored copy of a container it ships no longer stays open at the anonymous form doors through the package's shipped form of that name. - The rulings it landed under: decision: #21980 OQ1 — the unscoped kernel's bare-name registration lets one package's stored copy answer another package's by-name read: fix it in #21980, split it out, or fix it first? #22004 (6020226416) and decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027 (6028897060: A, the merge condition is met). The seat's ACCEPT is on PR 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 (6028943297).
- CI note. The first head's red was a cancelled cache restore, not the PR's code. The base merge
03f727651ere-ran it green (6021895922). - Follow-up on file: finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024 (
pm:blockedp3): a no-package by-name read grafts the first-registered package's_packageIdenvelope onto another package's served expansion. It does not hold this card. - This card closes the withdrawal-reach family (metadata: a view container with a bare list on another package's object silently replaces that object's packaged default view on GET /meta/view?object= — while the by-name read still serves the original #21334 → finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967 → finding(metadata-protocol): a stored copy of a view container a package ships on another package's object expands under its own name (#21334's arm), so a form withdrawn in that copy does not reach the package's shipped form of that name #21980).
Generated by Claude Code
- The squash. It is on
- added 3 commits that reference this issue
on Oct 7, 2026
Filing gate: ① a reproducible defect, class (a). Filed from #21967's dev report (PR #21979,
out_of_scope_findings[0]) bydomain:engineseat 1 (seat post #6367,session_017ErfyP2Rx7XWHJA27QjyUi). ⛔ Not graded or routed here. ⛔ Not a claim. Classes and positions only.What is measured (by #21967's dev, at PR #21979's head
dc853419db)Measured in-process, on both kernels, through
saveMetaItem(the methodPUT /api/v1/meta/view/NAMEcalls) and the env-widegetMetaItemsview list. Not measured over HTTP. PR #21979 does not touch it.Shipped views keep their plain names. The source loaders register a shipped container's views under
OBJECT.KEYfor the shipping package, whichever package owns the object:packages/objectql/src/engine.ts:7159–:7165andpackages/metadata/src/plugin.ts:1198–:1210.The stored copy's views get a longer name. When that package stores an env-wide copy of the same container, the copy takes metadata: a view container with a bare list on another package's object silently replaces that object's packaged default view on GET /meta/view?object= — while the by-name read still serves the original #21334's own-name arm on another package's object (
expandRuntimeViewContainer→expandUnderOwnName). It expands toOBJECT.CONTAINER.KEY, so the copy overlays none of the views its package ships from that container.Probe. Package B ships container
taskon package A's objecttask. B's env-wide copy withdrawsformViews.intake_form. The env-wide list then holds:task.intake_formfrompkg_a, open;task.intake_formfrompkg_b, open;task.task.intake_formfrompkg_b, withdrawn.So the withdrawal saved in B's copy does not reach B's shipped form.
Family and positions
<object>.default, are accepted with no diagnostic #21639, is closed.packages/metadata-protocol/src/protocol.ts:expandRuntimeViewContainer/expandUnderOwnName.expandViewContainernaming.Reader who acts
Triage grades and routes it. It touches #21334's ruled naming arm, so the direction may be the maintainer's. Serial: PR #21979 (#21967) edits the same list read.
Dedupe: MCP
search_issues, repo-scoped: 「shipped view container on another package object stored copy expands under own name withdrawal misses shipped form」 → #21967 (this card's parent family, different mechanism), #21638 and #21639 (closed, different mechanisms). None is this.Dedupe words:
shipped container on another package object loader names·stored copy expands under own name shipped views not overlaid·withdrawal saved in container copy misses shipped form name·expandUnderOwnName loader expandViewContainer mismatchGenerated by Claude Code