fix(metadata-protocol): a view a stored container expands answers by name what the object door lists, on every kernel and container scope (#21442) - #21508
Conversation
…tored view container expands Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
…swers for a name a stored view container expands Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
…election a row-less view name now reads Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
…-name-expanded-view
…expanded view name Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
…r than declaring a new engine double Claude-Session: https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 32 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. 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 5cad4ac6467b5dbee662112ab749d93eff4601b9 && git checkout 5cad4ac6467b5dbee662112ab749d93eff4601b9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2ee8383f4e16248322a45a3e4fde5de75598eef4 3558ad6e70296c6d74fda97f4a9862574d4aad7d && git checkout -B drift-repro 2ee8383f4e16248322a45a3e4fde5de75598eef4 && git merge --no-ff 3558ad6e70296c6d74fda97f4a9862574d4aad7d
node scripts/docs-audit/affected-docs.mjs --json 2ee8383f4e16248322a45a3e4fde5de75598eef4
|
Fixes #21442
Clause-②: no
What this changes
GET /api/v1/meta/view?object=OBJECT(the object door,getMetaItems) lists the views a stored view container expands. The by-name read (GET /api/v1/meta/view/NAME,getMetaItem) expanded no container. It answered such a name only on an unscoped kernel and only for an environment-wide container, where registry hydration had registered a copy. This PR implements triage's ruling A (comment 5957375321) as ruled: the by-name read expands the in-scope stored containers throughexpandRuntimeViewContainer, the function the list read uses. Nothing is persisted or registered, and there is no kernel-specific branch.getMetaItem). A new step 1b sits after the stored-row read and before the MetadataService and registry steps. When a view name has no stored row of its own and a stored view container in the caller's scope expands it, the read answers the item the list read serves under that name. The step sits before the MetadataService and registry steps because the list read's expansion also wins over items of the same name from those two sources.readFlattenedMetaItemsmove unchanged into private methods: its row selection (the twoqueryByOrgreads, the overlay cache and the org-over-env merge), its row parse, and its expansion loop. The new methods arereadActiveOverlayRows,storedOverlayEntriesandexpandStoredViewContainers, and both doors call them.resolveRowlessExpandedViewis the by-name use of the three.expandRuntimeViewContaineris called as is and is not edited. The list read's own behaviour is unchanged, and the package's full suite still passes over it.getMetaItemLayered). For such a name the layers report the container's provenance in fields that already exist.overlayis the container's own stored row: itsnameis the container's, and its_packageIdis the package the row is bound to.overlayScopeis the scope that row was read from (envororg), andeffectiveis the expanded view, which is what the by-name read answers.codekeeps its own read: the item a package ships under the name, elsenull.historyMetaItem,diffMetaItem). They resolve to the container's own row. Each answers exactly what it answers under the container's row name for the same caller, and says so: every event'sref.nameis the container's, and the diff'snameis the container's (the item actually diffed, the same rule its echoedtypealready follows). No history is synthesized for a name that was never stored.Measured, in-process at the protocol (the #21334 showcase harness:
showcase_task, every member kind)env_localshowcase_task.defaultAll Tasks)showcase_task.os_qa_probe.in_progress(package-scoped container)overlay: null,effective: nulloverlay: null,codeandeffective= the hydrated copyoverlay= containeros_qa_probe,overlayScope= its scope,effective= the item[][]ref.name: os_qa_probe(every scope)nameechoednameechoedname: os_qa_probe(every scope)"Before" is
origin/mainat5555047117. "After" is this branch. Both were run with the same harness, and the item comparison excludes_diagnostics.Provenance needed no new field (PM mechanism assumption 3)
The layered response already carries
overlay(the stored row behind the name) andoverlayScope(its scope). History events already carryref.nameandref.org, and the diff already echoesname. So no published response shape gains or loses a key, andClause-②: nostands as claimed.A write by an expanded name (out of scope, recorded, unchanged)
Probe:
saveMetaItemwith typeview, the nameshowcase_task.os_qa_probe.in_progressand a ViewItem body. On both kernels and for all three container scopes, the save door ACCEPTS it and stores a row under that name. Afterwards the by-name read answers the written row, and the object door still lists the container's expansion for that name. The probe gave byte-identical results withprotocol.tsat5555047117and at this branch:saveMetaItemis not in this diff, and it does not call any read this PR changes for a view. The disagreement it leaves is filed as a finding in the dev report (the list read's upsert by name displaces a stored row of the same name), not fixed here.Reverse verification (each mutation committed-state, landed through
scripts/ablation-replace.mjs, restore proven blob == HEAD andgit diff HEADempty)The subject resolves through relative source imports (
./index.js), so nodist/leg applies.if (expanded !== undefined && false)):view-container-runtime-expansion.test.tswent from 119/119 to 30 failed / 89 passed. The failures were everyenv_localcase (21); on the unscoped kernel, the organization-scoped cases, the isolation case and the org-scoped overlay case (8); and the cross-kernel case (1). The unscoped environment-wide cases stayed green, which is the pre-fix registry-hydration answer the card describes.Tests
packages/metadata-protocol/src/view-container-runtime-expansion.test.ts, nested in the 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 block so the faithful-registry harness is reused: 49 cases. They cover both kernels × all three container scopes × every member kind: every name the object door lists (the packaged names included) answers that same item by name, and the container's own name is the control. Further cases cover the layers, history and diff per scope; that the reads store no row and register nothing; that a name nothing expands answers nothing and gets no history; organization isolation; a tenant overlay of the package's own container (env-wide and org-scoped); and a cross-kernel item equality.get-meta-item-org-read-gate.test.ts/get-meta-item-layered-org-read-gate.test.ts: their engine double declared onlyfindOne, and a row-less view name now also issuesfindfor the container selection. The double gains afindthat records partitions. Theviewcase now also asserts that the selection reads the same partitions ([null, ORG]). Thefindis assigned onto the existing double rather than declared in its literal, so the double'scheck:engine-double-contractaccounting is unchanged and no ledger moves.3558ad6e70:pnpm --filter @objectstack/metadata-protocol typecheckclean (tsc --listFilesincludes all three edited test files). The full suite (vitest run) gave 205 files passed / 3 skipped, 3151 tests passed / 19 skipped.dist/(a narrowed, declared sample, chosen as the test files that read a view by name through the real protocol):@objectstack/objectql10 files / 226 tests,@objectstack/rest9 files / 123 tests,@objectstack/runtime3 files / 43 tests, all green. The rest of those packages is CI's.dispatch-gatesderived 64 families for this tree (3558ad6e70). 63 ran green after the final commit. 1 is NOT MEASURED:check:dual-build-cjs-loadsexit 3 PREREQUISITE NOT MET, because it reads every package'sdist/and about 38 packages were not built locally.--ranreconciliation: 64 accounted, 63 run, 1 NOT-MEASURED, 0 UNRUN.eslint --no-inline-config --format jsonover the 4 changed TypeScript files gave 4 files, 0 errors, 0 warnings. The config does no type-aware linting (noparserOptions.project; see the note ateslint.config.mjsline 328), so this diff cannot move a verdict on an untouched file. A repo-widepnpm lintis CI's.Acceptance notes
finds, served from the same overlay cache entry the object door uses (same key: type, package, gated organization) on an engine with a write epoch. It is the price of one expansion rule rather than two.getMetaItem, history and diff. The three-method extraction inreadFlattenedMetaItemsis a pure move, needed so that the by-name read reuses the list's selection rather than a second copy (PM mechanism assumption 2). ThegetMetaItemLayerededit is the ruling's Layers bullet. The two read-gate test doubles are described above. No edit touches the save door,deletePackageorpackages/rest.resettablereadstrueon the by-name envelope, the layers'codeis the hydrated copy (onenv_local:false,null), andisArtifactBackedis true for a write by that name. The producer-side fix would change the save door's intent for a write by an expanded name, which the ruling keeps unchanged, so it is reported as a finding and not fixed here.Generated by Claude Code