Skip to content

fix(metadata-protocol,objectql): the /meta read exits materialize a served base the way the registry materializes its own (#8268) - #8380

Merged
os-zhuang merged 7 commits into
mainfrom
claude/issue-8268-materialization-stamp-seam
Aug 13, 2026
Merged

fix(metadata-protocol,objectql): the /meta read exits materialize a served base the way the registry materializes its own (#8268)#8380
os-zhuang merged 7 commits into
mainfrom
claude/issue-8268-materialization-stamp-seam

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Part of #8268.

GET /meta/object/:name served nameField: undefined for an object whose by-name read is answered from the metadata service, while GET /meta/object and the registry's own resolved schema served 'name' — same object, same moment, same host, with nothing in the response saying which link answered.

This is the third time one seam has been ruled (#6562 injected system columns, then #8038 __search, now nameField), so it is fixed as a class rather than as a third stamp-specific patch.

What changed

SchemaRegistry.registerObject's materialization block is now a single method, materializeBaseLayer, and materializeServedObjectOnto replays that same code onto a body that never came through registerObject. The /meta read exits ask for the whole seam (materializeFromRegistry) instead of naming one stamp, so a stamp added to the block converges on served documents the day it is added — no fourth method, no fourth card.

provisionSearchCompanionOnto is reused, not duplicated; it stays public because the write-side strip is its exact inverse and hosts pin the pair.

The seam turned out to be a PAIR

The read stamp created a write obligation, and the landed #4326 round-trip pin caught it — meta-object-search-companion-roundtrip.test.ts went red with + "nameField": "name" in the stored body, and the row's checksum moved. So stripMaterializedStampsFrom is the whole-seam inverse, reached through the helper saveMetaItem already calls. saveMetaItem's own body is untouched.

The title strip is exactness-bounded and its boundary is load-bearing in a way the __search strip's is not: __search removes a value byte-identical to a platform-canonical field definition, where coincidence is implausible, whereas nameField is a single string an author writes deliberately to override the derivation. So the pointer comes off only when identical to what the seam would designate for a body carrying no pointer, and a pointer naming any other field is kept. Pinned directly.

Stamp inventory

Every stamp that crosses the read-exit seam, what converges it, and whether this PR covers it.

# Stamp Registry producer Read-exit converger This PR
1 injected system columns (created_at, owner_id, organization_id, …) applySystemFields applyInjectedSystemColumns (#6562) already converged
2 audit-field governance (declared audit field normalized) — (read-side only) applyAuditFieldGovernance (#4513) already converged
3 __search companion column provisionSearchCompanion provisionSearchCompanionOnto (#8038) already converged; now routed through the shared seam
4 nameField (ADR-0079 designation) provisionPrimary none existed fixed here, both halves
5 multi-tenant indexes applySystemFields none — converger handles the fields map only not fixed — filed as #8375

Not part of this seam, listed so the enumeration is complete: reconcileManagedApiMethods and the two warn* diagnostics run in registerObject above the base-layer gate, so converging them is a separate decision, not an omission from this block.

What this PR deliberately does NOT fix

All three are outside this card's region, all three are filed, and all three are pinned as EXPECTED divergences in the tests — so the day any of them is closed, the pin fails deliberately instead of drifting.

#8375 — the multi-tenant indexes stamp. On multiTenant: true, applySystemFields also stamps indexes: [{ fields: ['organization_id'] }]; the read exit converges the fields map only, so the by-name read serves no indexes. This is the general lesson worth more than the patch: the stamp that delegates to the registry (__search) has not drifted; the stamp with no converger (nameField) was simply missing; the stamp converged by a second implementation of its producer (applyInjectedSystemColumns vs applySystemFields) is the one that silently disagrees. That is exactly the shape d5031f6a1 fixed next door by deleting a second implementation and calling the canonical one — still live here.

#8376 — the seam's POSITION relative to the extender fold. registerObject materializes the base; resolveObject folds extend contributors on afterwards and never re-materializes; the read exits transform the already-folded document. So a title-less base that an extension gives a text field to gets a __search column from both /meta reads and none from the registry — reproducing on the registry-only host, so it is not the artifact seam. Live since #8038 and unchanged here. Now labelled needs-user-decision: one branch of the fix is schema-migration-bearing (it would make syncSchema create real __search columns on objects that have none today), which is the maintainer's call, not a seat's.

This card's fix does not grow that defect: the title convergence withholds a designation the registry itself declined, so it can only move a served copy onto the registry's answer and never manufacture one. Pinned as never INVENTS a designation the registry itself declined.

#8381 — an author's explicit nameField that coincides with the derivation is not persisted. The strip is exactness-bounded, and nameField is a single string, so "the author wrote nameField: 'name'" and "the read derived nameField: 'name'" are the same bytes; the strip cannot tell them apart and drops it. Ruled by the PM as acceptable to ship (option A) with the lossless refinement (option C — make the strip stored-row-aware) filed as its own card, since registerObject re-derives the designation over every base layer at load: no resolved answer changes, now or at any future boot. What is lost is stability of intent against a later edit that adds a higher-ranked field.

⚠️ Writing the pin corrected the claim: the pointer is dropped by the first save that carries it, before any read or round trip — the strip runs on every write, so there is nothing to lose on a round trip because nothing was ever stored. The first version of this pin asserted "stored, then lost" and failed for that reason. Pinned as MEASURES that an author pointer identical to the derivation is never STORED, which also asserts the resolved answer is unchanged, so the pin records a persistence-fidelity gap rather than a behaviour regression. #8381 also records that the fence which blocked option C here — saveMetaItem being held by #8184 — is lifted, since #8184 has landed (003feae65).

Verification

Reverse-verified with the direction predicted before running: reverting both source files to origin/main turns exactly 5 of the 10 read-side cases red — the artifact agreement, the companion convergence, the companions-off case, keeps-designating, and the indexes measurement — and leaves the 5 that describe behaviour main already had green.

× agrees on an ARTIFACT-ingested host — where the by-name read used to drop `nameField`
× converges the companion stamp on the same host, in the same call
× serves no companion on either route when the DEPLOYMENT has companions off
× keeps designating when the base itself carries the title, extension or not
× MEASURES the un-converged multi-tenant `indexes` stamp
  Tests  5 failed | 5 passed (10)

The write half was reverse-verified by the landed pin going red on its own before the strip existed.

Suites, after the latest main merge:

packages/objectql            Test Files 197 passed (197)   Tests 3500 passed (3500)
packages/metadata-protocol   Test Files  79 passed (79)    Tests 1163 passed (1163)
packages/rest                Test Files 111 passed (111)   Tests 1827 passed (1827)
packages/runtime             Test Files 150 passed (150)   Tests 2306 passed (2306)

All 56 check:* gates from lint.yml pass. Four initially reported PREREQUISITE NOT MET — the workspace packages are not built and explicitly measured nothing; after a full package-closure build, check:app-nav-i18n, check:i18n, check:i18n-coverage and check:type-check-debt are green. Path-re-derivation via scripts/pm/dispatch-gates.mjs on the actual changed paths surfaced families the dispatch list did not name — check:authz-resolver, check:meta-type-normalized, check:engine-double-contract, check:query-options-erasure, check:type-check-coverage — all run and green.

Anti-vacuity: every host asserts the read is non-empty and that its name matches the registry's before comparing, and the untitled host proves the stamps are discriminated rather than always-applied.

Docs checked, none updated: every nameField page under content/docs/ is authoring-side and none states what GET /meta/object/:name serves. data-modeling/formulas.mdx's "an explicit nameField pointer is honored either way" stays true — #8381 concerns whether a redundant pointer is stored, not whether it is honored. content/docs/releases/** is read-only in a code PR and contains nothing this makes stale.

Not touched: translateObject and i18n precedence (#8284 / #8037 — that fork is the maintainer's), saveMetaItem's refusal branch (#8184), assertFilterFieldsExist (#8296), the audit-write sites, and the error/catch message construction.


Generated by Claude Code

@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 13, 2026 10:53am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/objectql.

16 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, @objectstack/objectql)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/objectql)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/permissions/system-context.mdx (via packages/objectql)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/metadata-protocol)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)

2 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM review, domain:metadata seat

Green at 7f596819: all 25 checks, each job's own conclusion verified individually rather than by a roll-up. Path-fork check clean — 5 files (.changeset/meta-read-exit-materialization-seam.md, protocol.ts, registry.ts, and the two new test files), none under docs/adr/**, .claude/skills/** or skills/**.

Against the bar this card was dispatched under

It was promoted as a class fix, not a third stamp-specific patch, and that is what shipped: registerObject's materialization block is one method, materializeServedObjectOnto replays it onto a body that never came through registerObject, and the read exits ask for the whole seam via materializeFromRegistry rather than naming a stamp — so stamp four converges the day it is added to the block. provisionSearchCompanionOnto is reused rather than duplicated, the stamp inventory is in the PR body as a table with producer / converger / covered columns, and saveMetaItem's own body is untouched, which is what kept #8184's region clear.

Three things beyond the bar are worth naming because other seats should copy them:

  1. The seam turned out to be a pair, and the landed [P3] Read decorations (_diagnostics, _draft) round-trip into persisted sys_metadata bodies #4326 round-trip pin caught it rather than a human noticing — the read stamp created a write obligation, so the fix grew its whole-seam inverse.
  2. Known-but-unfixed divergences were pinned as deliberate tripwires, not left silent. GET /meta/object/:name drops the multi-tenant indexes stamp — the fourth materialization stamp on the #8268 seam, and the one whose converger is a second implementation #8375 and The /meta read exits materialize the FOLDED object, the registry materializes the BASE — so an extended title-less object gets a __search column the registry never provisioned #8376 each fail on purpose the day they are fixed, instead of drifting.
  3. A failing pin was treated as evidence, not an obstacle. Writing the Q1 pin falsified the card's own premise — the redundant pointer is not lost on a round trip, it is never stored at all, dropped by the first write that carries it. The response was to re-derive the mechanism and correct the card, not to adjust the pin until it passed. ⚠️ That correction propagates: it means option C as written in The nameField write-side strip drops an author's EXPLICIT pointer when it coincides with the derivation — make it stored-row-aware (option C), now that #8184 has landed and the fence is lifted #8381 is equivalent to option A on the very case it exists to fix, which I have recorded on that card so its taker does not implement a fix that closes nothing.

Closure ruling — #8268 CLOSES on merge

The trailer is Part of #8268, so this will not auto-close; I will close it manually once the merge is confirmed by commit on origin/main.

Delivered: the card's named defect (GET /meta/object/:name dropping nameField for artifact-ingested objects), both halves — the read convergence and its write-side inverse — plus the class mechanism the card was promoted for, which is the part that stops this seam being ruled a fourth time.

Remaining, and separately owned — closing this is NOT a claim the seam is now uniform:

card what is left owner
#8375 the multi-tenant indexes stamp has no converger at all; the read exit converges the fields map only pm:queue, this lane
#8376 the read exits materialize the folded object while the registry materializes the base needs-user-decision — branch 2 is schema-migration-bearing
#8381 the nameField strip drops an author's redundant pointer at first save pm:queue, this lane — read the correction above before starting

Why close rather than re-queue: none of the three declares Blocked-by: #8268, so closing creates no circular block, and leaving it open would park a card in the pool whose content is fully duplicated across three children. That is the shape #7654 and #8136 were closed to avoid earlier in this shift.

Marking ready and enqueueing.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 13, 2026 11:08
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit 75fd301 Aug 13, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8268-materialization-stamp-seam branch August 13, 2026 11:25
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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants