Skip to content

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

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-21442-by-name-expanded-view
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-21442-by-name-expanded-view

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 through expandRuntimeViewContainer, the function the list read uses. Nothing is persisted or registered, and there is no kernel-specific branch.

  • By-name read (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.
  • One selection and one expansion, shared with the list read. Three parts of readFlattenedMetaItems move unchanged into private methods: its row selection (the two queryByOrg reads, the overlay cache and the org-over-env merge), its row parse, and its expansion loop. The new methods are readActiveOverlayRows, storedOverlayEntries and expandStoredViewContainers, and both doors call them. resolveRowlessExpandedView is the by-name use of the three. expandRuntimeViewContainer is 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.
  • Layers (getMetaItemLayered). For such a name the layers report the container's provenance in fields that already exist. overlay is the container's own stored row: its name is the container's, and its _packageId is the package the row is bound to. overlayScope is the scope that row was read from (env or org), and effective is the expanded view, which is what the by-name read answers. code keeps its own read: the item a package ships under the name, else null.
  • History and diff (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's ref.name is the container's, and the diff's name is the container's (the item actually diffed, the same rule its echoed type already follows). No history is synthesized for a name that was never stored.
  • Control. The container's own name still answers its stored row on every read.

Measured, in-process at the protocol (the #21334 showcase harness: showcase_task, every member kind)

before, env_local before, unscoped after, both kernels
package-scoped container (saved into another writable package) nothing same item as the list same item as the list
package-less, environment-wide container nothing same item as the list same item as the list
package-less, organization-scoped container nothing nothing same item as the list
tenant overlay of the package's own container, showcase_task.default the packaged view (All Tasks) the overlay's view (env-wide) / the packaged view (org-scoped) the overlay's view
layers for showcase_task.os_qa_probe.in_progress (package-scoped container) overlay: null, effective: null overlay: null, code and effective = the hydrated copy overlay = container os_qa_probe, overlayScope = its scope, effective = the item
history for that name (package-scoped container) [] [] the container's own events, ref.name: os_qa_probe (every scope)
diff for that name (package-scoped container) empty, name echoed empty, name echoed the container's own diff, name: os_qa_probe (every scope)

"Before" is origin/main at 5555047117. "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) and overlayScope (its scope). History events already carry ref.name and ref.org, and the diff already echoes name. So no published response shape gains or loses a key, and Clause-②: no stands as claimed.

A write by an expanded name (out of scope, recorded, unchanged)

Probe: saveMetaItem with type view, the name showcase_task.os_qa_probe.in_progress and 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 with protocol.ts at 5555047117 and at this branch: saveMetaItem is 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 and git diff HEAD empty)

The subject resolves through relative source imports (./index.js), so no dist/ leg applies.

  • A1, by-name step 1b off (if (expanded !== undefined && false)): view-container-runtime-expansion.test.ts went from 119/119 to 30 failed / 89 passed. The failures were every env_local case (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.
  • A2, layers resolution off: 10 failed (every layers assertion, both kernels).
  • A3, history delegation off: 6 failed (the history assertions, both kernels).
  • A4, diff delegation off: 6 failed (the diff assertions, both kernels).

Tests

  • New, in 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 only findOne, and a row-less view name now also issues find for the container selection. The double gains a find that records partitions. The view case now also asserts that the selection reads the same partitions ([null, ORG]). The find is assigned onto the existing double rather than declared in its literal, so the double's check:engine-double-contract accounting is unchanged and no ledger moves.
  • At the final head 3558ad6e70: pnpm --filter @objectstack/metadata-protocol typecheck clean (tsc --listFiles includes all three edited test files). The full suite (vitest run) gave 205 files passed / 3 skipped, 3151 tests passed / 19 skipped.
  • Downstream, against the rebuilt dist/ (a narrowed, declared sample, chosen as the test files that read a view by name through the real protocol): @objectstack/objectql 10 files / 226 tests, @objectstack/rest 9 files / 123 tests, @objectstack/runtime 3 files / 43 tests, all green. The rest of those packages is CI's.
  • Gates: dispatch-gates derived 64 families for this tree (3558ad6e70). 63 ran green after the final commit. 1 is NOT MEASURED: check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET, because it reads every package's dist/ and about 38 packages were not built locally. --ran reconciliation: 64 accounted, 63 run, 1 NOT-MEASURED, 0 UNRUN.
  • Lint, narrowed: eslint --no-inline-config --format json over the 4 changed TypeScript files gave 4 files, 0 errors, 0 warnings. The config does no type-aware linting (no parserOptions.project; see the note at eslint.config.mjs line 328), so this diff cannot move a verdict on an untouched file. A repo-wide pnpm lint is CI's.

Acceptance notes

  • Cost. A by-name read of a view name with no stored row of its own (every packaged view, for instance) now also reads the container selection. That is the list read's two 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.
  • Claim surface, declared. Four edits fall outside getMetaItem, history and diff. The three-method extraction in readFlattenedMetaItems is a pure move, needed so that the by-name read reuses the list's selection rather than a second copy (PM mechanism assumption 2). The getMetaItemLayered edit is the ruling's Layers bullet. The two read-gate test doubles are described above. No edit touches the save door, deletePackage or packages/rest.
  • Kernel-dependent residue this PR does not change. The item every door answers is now the same on both kernels. On an unscoped kernel, the registry still holds the write-through-hydrated copies of an environment-wide container's expansions, without the tenant marker the container itself is given. A package-bound container's copies therefore pass for code artifacts there. So resettable reads true on the by-name envelope, the layers' code is the hydrated copy (on env_local: false, null), and isArtifactBacked is 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

claude added 6 commits October 3, 2026 00:20
…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>
…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>
@github-actions github-actions Bot added the size/l label Oct 3, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 32 documentable anchor(s).

32 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 2ee8383f4e16248322a45a3e4fde5de75598eef4.

⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 cross-cutting symbol(s) contributed no route anchor: organizationId (5 routes)
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2ee8383f4e16248322a45a3e4fde5de75598eef4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5cad4ac6467b5dbee662112ab749d93eff4601b9 — the merge of head 3558ad6e70296c6d74fda97f4a9862574d4aad7d into base 2ee8383f4e16248322a45a3e4fde5de75598eef4, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2ee8383f4e16248322a45a3e4fde5de75598eef4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 3, 2026 01:51
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 3, 2026 01:51
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit e9dec3d Oct 3, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21442-by-name-expanded-view branch October 3, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants