Filed by the PM on behalf of the #8038 dev agent, which measured this but could not file it: its container was 403 on every GitHub API call. Recovered over the git transport as .os-handover/8038-handover.md on branch claude/issue-8038-handover. The text below is the dev's, verbatim in substance.
Unassigned. ⚠️Read "Why this may not be a separate card" before assigning.
Symptom
For an object whose by-name read is answered from the metadata service (an artifact-booted deployment), GET /meta/object/:name serves nameField: undefined, while GET /meta/object and the SchemaRegistry's own resolved schema serve nameField: 'name' — same object, same moment, same host. Nothing in the response tells a caller which answer they got.
nameField is the ADR-0079 display/title designation. A consumer reading the by-name answer concludes the object has no designated title field, and every title-rendering decision derived from it (forms, record headers, lookup labels, resolveDisplayField consumers) is made against a document the platform itself does not agree with.
The measurement
Found incidentally while measuring #8038, on that card's reproduction host: a real SchemaRegistry, a real ObjectStackProtocolImplementation, a real RestServer, with the metadata service holding the artifact's raw declaration (serviceMode: 'artifact').
⭐ The dev diffed the two served bodies key by key rather than comparing only the fields map — "precisely so a second divergence could not hide behind the one I was sent for." That is why this exists:
### artifact host
byName vs listed diverge on keys: fields,nameField
byName vs registry diverge on keys: _diagnostics,fields,nameField
byName.nameField = undefined registry.nameField = 'name'
fields was #8038 (__search). nameField is this issue. (_diagnostics is a read decoration, benign.)
Control, same run, same probe: on the bridged host (seeded from registry.getAllObjects()) and the absent host (no metadata service, read falls through to the registry), the same comparison printed diverge on keys: (none) and byName.nameField = name. ⇒ The probe can see agreement when agreement exists, and the divergence tracks the service copy specifically.
Root cause — identical to #8038
The by-name read resolves sys_metadata overlay → MetadataService → SchemaRegistry, and only the last link has been through the registry's object-materialization seam.nameField is written by provisionPrimary(schema, { synthesize: false }) — the line immediately aboveprovisionSearchCompanion inside SchemaRegistry.registerObject, under the same base-layer gate. The artifact copy is the author's declaration captured before that seam runs, so it carries neither stamp.
Where (anchored on function names, ⛔ not line numbers)
Reproduction
- Build a
SchemaRegistry, register an object with a title-eligible field as ownership: 'own'. - Put the raw declaration (⛔ not
registry.getObject(name)) into the metadata service under get('object', name) — the artifact-ingest shape. - Read both routes through a real
RestServer. byName.item.nameField is undefined; GET /meta/object and registry.getObject(name).nameField are both 'name'.
The harness in packages/rest/src/meta-object-search-companion-agreement.test.ts (landed with #8038 as a6cd2c152f) already builds exactly this host — its measure({ serviceMode: 'artifact' }) is a one-line extension from asserting nameField agreement too.
⚠️ Why this may not be a separate card — reconcile with #8037 before assigning
The dev's own caveat, and it is a good one: nameField is a scalar property that mergeObjectDefinitions resolves last-writer-wins, so it may already fall inside #8037's enumeration of every property class the fold touches (landed as 3a27c46edb, PR #8258). provisionSearchCompanion is deployment-gated (OS_SEARCH_PINYIN_ENABLED) while provisionPrimary runs unconditionally, and they land in different parts of the document — but the overlap with #8037 is real.
⭐ It deliberately did not fix this inside #8038's PR: "fixing it in two places concurrently is worse than fixing it in one." Correct call.
⭐ Suggested framing — rule on the CLASS, not the third instance
Three materialization stamps have now been found diverging on this one path, one at a time:
| # | Stamp | Status |
|---|
| #6562 | injected system columns (created_at, owner_id, organization_id, …) | ruled 2026-08-08, Option B — converge at read exits |
| #8038 | __search companion column | landed a6cd2c152f |
| this | nameField (provisionPrimary) | unfiled until now |
A single "materialize this base the way the registry materializes its own" seam — provisionPrimary + provisionSearchCompanion behind one registry method, applied at the read exits — closes the class and stops a fourth stamp arriving as a fourth card. #8038 built half of that seam already.
This is the same lesson the #7556 → #8027 → #8037 family taught in the other direction: pinning one property class proves nothing about the others.
Dedup
- Dev side (repo only, GitHub search was 403 for it):
nameField in metadata-protocol/rest → 7 hits, none about route agreement; provisionPrimary → exactly one production call site (SchemaRegistry.registerObject), no read-exit convergence anywhere; changesets reviewed. Nearest neighbour .changeset/adr0029-d9-object-overlay-contributor-layer.md examined and ruled not a twin — it concerns the registration-side gate (which layers get stamped at registerObject time), not a read serving a copy that never went through registerObject. - PM side (GitHub search):
nameField → 0 open issues; provisionPrimary / provisionSearchCompanion / materialization → 5 hits, all unrelated (permission-model parking lot, CEL binding fullness, Studio coverage, two seat posts).
No twin found.
Related
#8038 (a6cd2c152f) · #8037 (3a27c46edb) · #7556 · #8027 · #6562 · ADR-0079 · ADR-0029 D9
Filed by the PM on behalf of the #8038 dev agent, which measured this but could not file it: its container was 403 on every GitHub API call. Recovered over the git transport as
.os-handover/8038-handover.mdon branchclaude/issue-8038-handover. The text below is the dev's, verbatim in substance.Unassigned.⚠️ Read "Why this may not be a separate card" before assigning.
Symptom
For an object whose by-name read is answered from the
metadataservice (an artifact-booted deployment),GET /meta/object/:nameservesnameField: undefined, whileGET /meta/objectand theSchemaRegistry's own resolved schema servenameField: 'name'— same object, same moment, same host. Nothing in the response tells a caller which answer they got.nameFieldis the ADR-0079 display/title designation. A consumer reading the by-name answer concludes the object has no designated title field, and every title-rendering decision derived from it (forms, record headers, lookup labels,resolveDisplayFieldconsumers) is made against a document the platform itself does not agree with.The measurement
Found incidentally while measuring #8038, on that card's reproduction host: a real
SchemaRegistry, a realObjectStackProtocolImplementation, a realRestServer, with themetadataservice holding the artifact's raw declaration (serviceMode: 'artifact').⭐ The dev diffed the two served bodies key by key rather than comparing only the fields map — "precisely so a second divergence could not hide behind the one I was sent for." That is why this exists:
fieldswas #8038 (__search).nameFieldis this issue. (_diagnosticsis a read decoration, benign.)Control, same run, same probe: on the
bridgedhost (seeded fromregistry.getAllObjects()) and theabsenthost (nometadataservice, read falls through to the registry), the same comparison printeddiverge on keys: (none)andbyName.nameField = name. ⇒ The probe can see agreement when agreement exists, and the divergence tracks the service copy specifically.Root cause — identical to #8038
The by-name read resolves
sys_metadataoverlay → MetadataService → SchemaRegistry, and only the last link has been through the registry's object-materialization seam.nameFieldis written byprovisionPrimary(schema, { synthesize: false })— the line immediately aboveprovisionSearchCompanioninsideSchemaRegistry.registerObject, under the same base-layer gate. The artifact copy is the author's declaration captured before that seam runs, so it carries neither stamp.Where (anchored on function names, ⛔ not line numbers)
packages/objectql/src/registry.ts—SchemaRegistry.registerObject, the ADR-0079 materialization block callingprovisionPrimarythenprovisionSearchCompanion. Producer of the correct answer.packages/spec/src/data/display-name.ts—provisionPrimary(pure,synthesize: false, idempotent).packages/metadata-protocol/src/protocol.ts—getMetaItem(thereadItemFromMetadataServicebranch) is where the unmaterialized copy enters;governServedObjectis the read exit where/meta/object/:nameanswers the__searchquestion two different ways — drops it for all 22 package objects, keeps it for all 71 platform objects (and the list route keeps it for both) #8038's convergence now happens and where this would go;getMetaItemLayeredneeds the same treatment oneffectiveonly./meta/object/:nameanswers the__searchquestion two different ways — drops it for all 22 package objects, keeps it for all 71 platform objects (and the list route keeps it for both) #8038 already installed and this would reuse:SchemaRegistry.provisionSearchCompanionOntoand its callerObjectStackProtocolImplementation.provisionSearchCompanionFromRegistry. AprovisionPrimaryOntosibling drops into the same shape.Reproduction
SchemaRegistry, register an object with a title-eligible field asownership: 'own'.registry.getObject(name)) into themetadataservice underget('object', name)— the artifact-ingest shape.RestServer.byName.item.nameFieldisundefined;GET /meta/objectandregistry.getObject(name).nameFieldare both'name'.The harness in
packages/rest/src/meta-object-search-companion-agreement.test.ts(landed with #8038 asa6cd2c152f) already builds exactly this host — itsmeasure({ serviceMode: 'artifact' })is a one-line extension from assertingnameFieldagreement too.The dev's own caveat, and it is a good one:
nameFieldis a scalar property thatmergeObjectDefinitionsresolves last-writer-wins, so it may already fall inside #8037's enumeration of every property class the fold touches (landed as3a27c46edb, PR #8258).provisionSearchCompanionis deployment-gated (OS_SEARCH_PINYIN_ENABLED) whileprovisionPrimaryruns unconditionally, and they land in different parts of the document — but the overlap with #8037 is real.⭐ It deliberately did not fix this inside #8038's PR: "fixing it in two places concurrently is worse than fixing it in one." Correct call.
⭐ Suggested framing — rule on the CLASS, not the third instance
Three materialization stamps have now been found diverging on this one path, one at a time:
created_at,owner_id,organization_id, …)__searchcompanion columna6cd2c152fnameField(provisionPrimary)A single "materialize this base the way the registry materializes its own" seam —
provisionPrimary+provisionSearchCompanionbehind one registry method, applied at the read exits — closes the class and stops a fourth stamp arriving as a fourth card. #8038 built half of that seam already.This is the same lesson the #7556 → #8027 → #8037 family taught in the other direction: pinning one property class proves nothing about the others.
Dedup
nameFieldin metadata-protocol/rest → 7 hits, none about route agreement;provisionPrimary→ exactly one production call site (SchemaRegistry.registerObject), no read-exit convergence anywhere; changesets reviewed. Nearest neighbour.changeset/adr0029-d9-object-overlay-contributor-layer.mdexamined and ruled not a twin — it concerns the registration-side gate (which layers get stamped atregisterObjecttime), not a read serving a copy that never went throughregisterObject.nameField→ 0 open issues;provisionPrimary/provisionSearchCompanion/ materialization → 5 hits, all unrelated (permission-model parking lot, CEL binding fullness, Studio coverage, two seat posts).No twin found.
Related
#8038 (
a6cd2c152f) · #8037 (3a27c46edb) · #7556 · #8027 · #6562 · ADR-0079 · ADR-0029 D9