Filed by the #8268 dev agent, measured while enumerating the stamps that cross the /meta read-exit materialization seam. Unassigned. This is a position defect of that seam, not a missing stamp — which is why #8268 did not fix it and deliberately preserved the existing behaviour rather than re-spelling it.
Symptom
An object whose BASE layer has no title-eligible field, extended by an extend contributor that adds one, is served a __search companion column by both /meta reads while registry.getObject(name) has none:
registry.getObject(name).fields => project, seats, nickname, ... (no __search)
GET /meta/object/:name .fields => project, seats, nickname, __search, ...
GET /meta/object .fields => project, seats, nickname, __search, ...
The measurement
Reproduces on the absent host — no metadata service at all, so the read falls through to the registry. That is the tell: this is not the artifact-ingest seam #8038/#8268 are about. Both /meta reads disagree with the registry on an object the registry itself produced.
absent host, untitled base + extend contributor adding a text field:
byName vs registry diverge on keys: fields
listed vs registry diverge on keys: fields
differing key: __search byName: present registry: absent
Control, same run: a base that does carry a title-eligible field agrees on every key with an extender present (diverge on keys: (none)), so the divergence tracks the base-vs-folded distinction specifically and not the presence of extenders.
Root cause
The two run the same provisioning at different points in the pipeline:
SchemaRegistry.registerObject materializes the BASE layer — the ADR-0079 gate is ownership === 'own' || 'overlay', and the comment there says it outright: "extensions must not redesignate the owner's title". resolveObject then folds extend contributors on afterwards and never re-materializes.
- The
/meta read exits apply the convergence at the EXIT, by which point foldObjectExtendersFromRegistry has already folded the extenders on. So provisionSearchCompanionOnto resolves its source field over the folded field set and finds the extension's text column.
So the registry answers "this object has no title, therefore no companion" and the read answers "this object has a title contributed by an extension, therefore a companion".
Why #8268 did not fix it
#8268's dispatch scoped the work to the read-exit stamps and explicitly forbade re-spelling the mechanism #8038 built. Relocating the convergence from the exit to the pre-fold position is exactly that re-spelling, and it has real blast radius: the OVERLAY list path depends on the exit-side convergence (meta-object-search-companion-agreement.test.ts pins that the list read converges an overlay row, and says a revert makes that case alone go red in the listed half). So it needs its own card.
#8268 did make sure it does not GROW the problem: its nameField convergence withholds a designation the registry itself declined, precisely so the title stamp does not acquire this defect too. materializeServedObjectOnto's docstring records that asymmetry and points here.
Which answer is right — worth ruling explicitly
Both readings are defensible and the fix differs:
- The registry is right (materialize the base, extensions never contribute a title or a companion): then the read exits must materialize BEFORE the fold, and the
__search column disappears from these objects.
- The read is right (the resolved object genuinely has a searchable text column, so it should have a companion): then
resolveObject must re-materialize after folding, and the registry — and therefore the driver's syncSchema, which creates the real column — gains one.
⚠️ The second option is schema-migration-bearing (ADR-0045): it would materialize a real DB column on objects that do not have one today. The first is not. That asymmetry probably decides it, but it should be decided rather than fallen into.
Where (anchored on function names, not line numbers)
packages/objectql/src/registry.ts — registerObject (materializes the base), resolveObject / foldExtendersOntoDefinition (folds afterwards, no re-materialization), materializeServedObjectOnto (the read-exit replay, and the docstring recording this gap).
packages/metadata-protocol/src/protocol.ts — foldObjectExtendersFromRegistry runs before governServedObject at each exit; that ordering is the defect.
Reproduction
new SchemaRegistry({ searchCompanion: true }); register a junction object whose fields are only lookup + number as ownership: 'own'.
- Register a second contributor for the same name as
ownership: 'extend' carrying one text field.
- Read
GET /meta/object/:name (no metadata service needed) and compare with registry.getObject(name).
- The served body carries
__search; the registry's does not.
Pinned as an EXPECTED divergence in packages/rest/src/meta-object-materialization-agreement.test.ts (MEASURES the companion over-provisioned on an extended title-less base), so a fix here turns that pin red and should update it in the same PR.
Dedup
GitHub search over open issues for companion provisioning / extender fold / registry disagreement found no twin (nearest: #3955, closed, migrate-CLI vs dev-runtime companion disagreement — a different pair of producers). nameField on this seam is #8268; the multi-tenant indexes stamp is its sibling finding.
Related
#8268 · #8038 (installed the exit-side convergence) · #7556 / #8027 (the extender fold at the read exits) · ADR-0029 D9 · ADR-0045 · ADR-0079
Generated by Claude Code
Filed by the #8268 dev agent, measured while enumerating the stamps that cross the
/metaread-exit materialization seam. Unassigned. This is a position defect of that seam, not a missing stamp — which is why #8268 did not fix it and deliberately preserved the existing behaviour rather than re-spelling it.Symptom
An object whose BASE layer has no title-eligible field, extended by an
extendcontributor that adds one, is served a__searchcompanion column by both/metareads whileregistry.getObject(name)has none:The measurement
Reproduces on the
absenthost — nometadataservice at all, so the read falls through to the registry. That is the tell: this is not the artifact-ingest seam #8038/#8268 are about. Both/metareads disagree with the registry on an object the registry itself produced.Control, same run: a base that does carry a title-eligible field agrees on every key with an extender present (
diverge on keys: (none)), so the divergence tracks the base-vs-folded distinction specifically and not the presence of extenders.Root cause
The two run the same provisioning at different points in the pipeline:
SchemaRegistry.registerObjectmaterializes the BASE layer — the ADR-0079 gate isownership === 'own' || 'overlay', and the comment there says it outright: "extensions must not redesignate the owner's title".resolveObjectthen foldsextendcontributors on afterwards and never re-materializes./metaread exits apply the convergence at the EXIT, by which pointfoldObjectExtendersFromRegistryhas already folded the extenders on. SoprovisionSearchCompanionOntoresolves its source field over the folded field set and finds the extension's text column.So the registry answers "this object has no title, therefore no companion" and the read answers "this object has a title contributed by an extension, therefore a companion".
Why #8268 did not fix it
#8268's dispatch scoped the work to the read-exit stamps and explicitly forbade re-spelling the mechanism #8038 built. Relocating the convergence from the exit to the pre-fold position is exactly that re-spelling, and it has real blast radius: the OVERLAY list path depends on the exit-side convergence (
meta-object-search-companion-agreement.test.tspins that the list read converges an overlay row, and says a revert makes that case alone go red in thelistedhalf). So it needs its own card.#8268 did make sure it does not GROW the problem: its
nameFieldconvergence withholds a designation the registry itself declined, precisely so the title stamp does not acquire this defect too.materializeServedObjectOnto's docstring records that asymmetry and points here.Which answer is right — worth ruling explicitly
Both readings are defensible and the fix differs:
__searchcolumn disappears from these objects.resolveObjectmust re-materialize after folding, and the registry — and therefore the driver'ssyncSchema, which creates the real column — gains one.Where (anchored on function names, not line numbers)
packages/objectql/src/registry.ts—registerObject(materializes the base),resolveObject/foldExtendersOntoDefinition(folds afterwards, no re-materialization),materializeServedObjectOnto(the read-exit replay, and the docstring recording this gap).packages/metadata-protocol/src/protocol.ts—foldObjectExtendersFromRegistryruns beforegovernServedObjectat each exit; that ordering is the defect.Reproduction
new SchemaRegistry({ searchCompanion: true }); register a junction object whose fields are onlylookup+numberasownership: 'own'.ownership: 'extend'carrying onetextfield.GET /meta/object/:name(nometadataservice needed) and compare withregistry.getObject(name).__search; the registry's does not.Pinned as an EXPECTED divergence in
packages/rest/src/meta-object-materialization-agreement.test.ts(MEASURES the companion over-provisioned on an extended title-less base), so a fix here turns that pin red and should update it in the same PR.Dedup
GitHub search over open issues for companion provisioning / extender fold / registry disagreement found no twin (nearest: #3955, closed, migrate-CLI vs dev-runtime companion disagreement — a different pair of producers).
nameFieldon this seam is #8268; the multi-tenantindexesstamp is its sibling finding.Related
#8268 · #8038 (installed the exit-side convergence) · #7556 / #8027 (the extender fold at the read exits) · ADR-0029 D9 · ADR-0045 · ADR-0079
Generated by Claude Code