Repository navigation
fix(objectql): materialize the /meta object seam over the BASE layer, never the folded document (#8376) - #8614
Conversation
…the folded document The read-exit replay held an already-folded object and re-decided every document-sensitive materialization stamp over it, so a title-less base that an `extend` contributor gives a text field to was served a `__search` companion the registry never provisioned and the driver's syncSchema never created. Generalises the `nameField` withhold #8268 installed for one stamp into the seam's one rule: a stamp whose decision reads the document is withheld when this registry resolved the object and its own answer does not carry it. Fixes #8376 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
…erialize-before-fold
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
Fixes #8376
Implements the maintainer ruling of 2026-08-13 — branch 1, the registry is authoritative. Branch 2 (re-materialize after folding, growing
syncSchema's column set) was rejected as schema-migration-bearing and is not attempted here.The deliverable's proof is a pin INVERTING
packages/rest/src/meta-object-materialization-agreement.test.tscarried this divergence as an EXPECTED failure — it MEASURED the defect rather than fixing it, precisely so the day it was fixed the file would fail and be updated deliberately instead of drifting. That day is this PR. The case renamed fromMEASURES the companion over-provisioned on an extended title-less base — filed as #8376to
never PROVISIONS a companion the registry itself declined — the #8376 inversionand its two load-bearing assertions flipped:
byName.fields.toContain('__search').not.toContain('__search')divergingKeys(byName, registryResolved)['fields'][]That inversion is the deliverable's proof. The rest of the file staying green is the proof that it cost nothing.
Why this is not a call-site move
The ruling asks the read exits to materialize before the extender fold. Measured, that turns out not to be reachable as a call-site reordering, and the reason is worth recording:
absent) host. There the served body comes fromresolveObject, which folded it inside the registry before the protocol ever saw it —foldObjectExtendersFromRegistrynever runs on that path. Measured directly:registry.getObject(name)has no__search, andmaterializeServedObjectOntoapplied to that same body adds one. That call is the sole producer.subtractExtenderContributions) reachesvalidationsandindexesonly.fieldsis a key-keyed spread with no inverse, because an extender may legitimately replace a base field under the same key. Measured: afterfoldObjectExtendersOnto, the extension's field is still in the map.So the seam instead refuses to re-decide a document-sensitive stamp over the folded document, and defers to the verdict the registry reached over the base at registration. That is the ruling's semantics — the registry is authoritative — and it is the same mechanism #8268 already installed for
nameField, now stated once as the seam's one rule rather than per stamp.materializeBaseLayergains aprovisionCompanionwithhold beside the existingdesignatePrimaryone;materializeServedObjectOntoasksdeclinedBaseLayerStampsfor both. The docstring records the test for stamp five: a stamp needs a withhold only if its decision reads the document. #8375's tenant-scope index does not (deployment flag alone), which is why it has no entry and needs none.Constraint 1 — the OVERLAY list pin survived, measured
Green, by name, in a verbose run:
It survives structurally: no call site moved, so the exit-side convergence the overlay list path depends on is untouched. Ablation A4 below re-measures that dependency and confirms it is live rather than assumed.
Prediction vs measurement — every ablation
Predictions were written down before each run.
expected [...] to not include '__search')never INVENTS a designation…and the inversion, the latter on itsdivergingKeysline, not its companion linesexpected 'nickname' to be undefined, andexpected ['nameField'] to deeply equal []listedhalf; the #8376 inversion stays GREENexpect(host.listed).toContain(SEARCH_COMPANION); inversion greenThe A4 divergence, stated rather than smoothed: I under-counted. Two cases I expected to survive —
serves no companion … when the DEPLOYMENT has companions offandstamps NO tenant index on a SINGLE-TENANT deployment— went red as well. Both assert absence, which is what misled me; both also carry a whole-documentdivergingKeysassertion, and on the artifact host the un-converged body losesnameField, so the class-level assertion fails even though every absence assertion in them holds. Useful correction: on this file the class-level assertion is the one that moves, not the named-stamp ones.A4 also produced the most useful negative result: the #8376 inversion pin stays green when the exit-side convergence is removed wholesale. So that pin alone does not prove the fix is narrow — it would pass for a fix that simply stopped converging anything. The narrowness is carried by the controls, which is why this PR adds one.
Anti-vacuity — how I know the harness reaches the fix
__search.GET /meta/object/:namedropsnameFieldfor artifact-ingested objects — the third materialization stamp to diverge on this seam #8268 limb is not dead code under the new mechanism — removing it reddens two cases, one of them for the predicted reason.nicknameis present in the served fields), and the extension's field really is a title-eligible companion source (type === 'text'), so withholding is a decision and not an inability. Without those two lines the case would pass on an object that simply has no extension.KEEPS provisioning the companion when the base itself carries the source, extension or not. Same shape as the withheld case (extended, companions on), differing only in that the base carries the title-eligible field itself. This is the exact trap the ruling named — do not trade one divergence for another — and a fix that withheld the companion whenever an object had an extender at all passes the inversion and fails this.Verification
Full suites, post-merge with
origin/main:Typecheck green for all three. Gate union re-derived from the final diff with
scripts/pm/dispatch-gates.mjsand re-run after the merge — all pass:check:nul-bytes,check:changeset-gate-self-tests,check:cross-package-test-inputs,check:durability-log-level,check:objectui-changeset,check:query-options-erasure,check-adr-0087-registration,check-changeset-no-major,check-empty-changeset,check-cross-package-test-inputs,check-engine-split-ratio.check:type-check-debtpasses in both--self-testand--re-measure(the latter after building the full workspace closure, which it correctly refuses to run without). No ledger grew.The
nameFielddocstring (ruling constraint 3)materializeServedObjectOnto's docstring carried the note that the same base-vs-folded asymmetry applied to__searchand was deliberately not closed, pointing here. That note is now wrong, so it is gone. It is replaced by the general rule the two stamps now share, the measured reason un-folding is not available, and the test to apply to stamp five.provisionSearchCompanionOntois untouched — it remains the pre-#8268 shim for partial registry doubles, and its own docstring already says the read exits no longer call it.No schema migration: no object gains a column it did not already have.
Generated by Claude Code