`dueLike` on the detail-view field pair and `operators` on the object-grid
pair are the two LOCAL entries in the `MirroredUndeclared` ledger. This
records what each key DOES — which face judges it, which merely absorbs it,
and whether anything reads it at runtime — without moving either published
accept set, so the at-tier contract review can read the measurement instead
of re-deriving it.
Also corrects the ledger entry for `dueLike`, whose stated reason was a
package-wide name grep: `field-types.ts` declares that name on two field
metadata interfaces. A name is not a key; the measurement stands, its
justification did not.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
Fixes #9729
Clause-②: no — and the declaration is about THIS PR's diff, which is test-only and moves no published accept set.⚠️ The REMEDY the card asks about is
Clause-②: yes, per key, and is deliberately not taken here: this branch measures, proposes, and hands back for the at-tier contract review.What this PR is
The card carries two LOCAL entries of the
MirroredUndeclaredledger — a key the published zod mirror states and the published TypeScript twin does not. Either remedy moves a face@object-ui/typesalready ships, so the dispatch forbade picking one. What was missing for the review was the per-key evidence, so that is what landed: four readings, each with a lit control on the same instrument, plus the pins that keep them honest.The four readings the card asked for
1. Who reads each key at runtime
⛔ A source grep cannot answer this in this tree —
SchemaRendererhands a node's leftover keys to the component as props, so a renderer can consume a key it never names. Both readings below are therefore rendered, and both landed as tests rather than being pasted here.dueLikeis READ.DetailSectionspreads the authored field into the enriched bag (enrichDetailFieldopens with a spread of the view field) and hands that bag to the resolved cell renderer, whereresolveDueLikereads it. Measured on a field namedend_date, which deliberately does not match the due/deadline field-NAME convention that read falls back to, so the authored key is the only thing that can move the output: with the key the cell draws the overdue wording, without it the neutral relative wording. The control leg renders BY VALUE — it asserts the neutral wording is present, not merely that the overdue wording is absent, which would also be true of a document that drew nothing. Pinned byDetailSection.dueLikeReachesTheCell-9729.test.tsx.operatorschanges NOTHING drawn. Measured with a byte ruler: the sameobject-griddocument drawn twice and compared as markup. The ruler is pinned stable first (identical documents, identical bytes), then a LIT CONTROL on a key the renderer demonstrably does read (label, which moves the bytes and appears in them), and only then the measurement — taken twice, once on a plain grid and once with the filter surface on, sinceoperatorsis a filtering word and a grid with no filter affordance would be the wrong corpus to ask. Both come back byte-identical.ObjectGrid.operatorsInert-9729.test.tsx.2. What
@objectstack/specsays on the corresponding surfaceProbed against the installed spec by parsing documents through it, not by grepping it — with a declared-key control and a nonsense-key control on the same schema:
operatorson the grid: the protocol REFUSES it by name.ObjectGridPropsSchemaforobject-gridis a strict object, and a document carryingoperatorscomes back RED withunrecognized_keys— and the refusal message itself proposes the rename tooperations. Controls: the bare document and one carryingoperationsboth parse GREEN. ⇒ the mirror admits a key the protocol rejects, and the protocol has already classified that spelling as a misspelling of a key both this block's authoring vocabulary and the twin declare.dueLikeon the detail-view field: the spec has no such surface to declare it on.RecordDetailsPropstypes itsfields(andsections[].fields) as field NAMES — a string array. Every object-shaped field entry is refused there, carryingdueLikeor not (control: a string entry parses GREEN, an object entry is RED whatever keys it carries). The whole object-shapedDetailViewFieldis objectui's own contract, so for this key there is no "toward the protocol" pull in either direction.3. Who AUTHORS either key in-tree
dueLikeis authored — incontent/docs/fields/date.mdxandcontent/docs/fields/datetime.mdx, where the published docs tell an author to write it.DateFieldMetadata/DateTimeFieldMetadata, the object-field metadata faces, ⛔ not on a detail-view field entry. So narrowing the mirror would falsify no in-tree document by itself — while leaving the docs teaching a key the detail-view face would then reject.operatorsis authored NOWHERE in the doc, example, app or e2e corpora on a grid node. Lit control on the same instrument and the same corpora:operationslights up in them.4. The ledger, and its floor
Both keys are already recorded, as the two LOCAL single-key entries the card describes:
objectql.zod.ts#ObjectGridSchemanamesoperators,views.zod.ts#DetailViewFieldSchemanamesdueLike. ⛔ The seeded entry and key totals are deliberately not repeated here: they live in exactly one place — theMirroredUndeclareddocstring — and are re-derived from that ledger's own AST by the census at the bottom ofzod-mirror-parity.test.ts("the fourth direction is enumerated and sized at test time"). That ledger is a FLOOR that can only shrink, and the ratchet reddens on growth, on fresh drift and on a stale entry alike.⭐ The third option the measurement produced: these are TWO decisions, not one
The card allowed for a third option if the measurement showed one, and it does — the two keys are not the same defect wearing two names:
dueLikeonviews.zod.ts#DetailViewFieldSchemaoperatorsonobjectql.zod.ts#ObjectGridSchemaDetailViewFieldhas no index signature, so TypeScript REFUSES the key (TS2353)ObjectGridSchemaextendsBaseSchema, whose index signature ABSORBS it asany— neither declared nor refused@objectstack/specoperationsoperations, the key everything else spells⇒ what the two faces DO differs in kind. For
dueLikethe two published faces actively CONTRADICT each other — the validator keeps the key, the compiler rejects the same document — and the runtime honours it. Foroperatorsthey do not contradict; one is simply silent, nothing reads it, the protocol refuses it, and the key sits one letter away from the word the whole stack uses.⭐ There is also a precedent in the tree for the
dueLikedirection, and it is worth the reviewer's attention:DateTimeFieldMetadata.dueLikewas declared by objectui#8958, and its docblock gives the reason in the reviewer's own words — leaving it off "would keep the mismatch alive with the sign flipped — a key the renderer honours but the authoring type rejects."⛔ Recommending is not deciding, and this PR decides nothing. Both remedies remain open on both keys.
complex.zod.ts#DashboardConfigSchema'sariawas re-derived on this branch rather than taken from the hand-back: it isz.never(...)with anerrormessage beginning "RETIRED (objectui#5852)" and an.optional(), and a document carrying the key comes back RED at pathariawith that remedy. A LIT CONTROL on the same schema shows an unrecognised key is NOT refused, so the rejection is BY NAME and not a strictness the whole object has; a document that omits the key stays green. ⇒ its consequence is the OPPOSITE of the other two — they admit silently, it rejects loudly — and a sweep treating the three alike would ask a maintainer to repair something already doing its job. ⛔ It is untouched here, and it is pinned as the CONTRAST so the grouping cannot come back by accident.What landed
packages/types/src/__tests__/mirror-only-published-keys-9729.test.ts— the per-key consequence pins. Each key's pin is paired with an unrecognised-key control through the same schema, because a green parse proves nothing on its own: one mirror strips unknown keys, the other passes them through unexamined, so what distinguishes a MIRRORED key is that the mirror JUDGES it. The TypeScript side carries ats-expect-errordirective on theDetailViewFieldliteral — that directive IS the assertion, and it fails in BOTH directions, since it also reddens as unused if the key ever becomes declared.packages/plugin-detail/src/__tests__/DetailSection.dueLikeReachesTheCell-9729.test.tsxandpackages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx— the two runtime readings, landed as tests beside the renderers that own the read paths rather than pasted into this body.packages/types/src/__tests__/zod-mirror-parity.test.ts— two ledger docstrings corrected. ThedueLikeentry justified itself with "agit grepfinds it in the mirror and nowhere in this package's TypeScript declarations", and that sentence is FALSE as written:field-types.tsdeclares that name onDateFieldMetadataand onDateTimeFieldMetadata, both in this package. ⭐ A NAME is not a KEY — a key is (interface, name) — and a package-wide grep for the name answers a question the ledger is not asking. The MEASUREMENT was right and stays; only its stated reason was wrong, and it is corrected rather than repeated. Theoperatorsentry gains the neighbouring-name reading..changeset/9729-mirror-only-published-keys-measured.md— empty frontmatter, the explicit "releases nothing" declaration.Gates
Each exit code was captured to disk before any pipe.
pnpm --filter @object-ui/types type-checktsconfig.test.json, which is the leg that reads the directives abovepnpm --filter @object-ui/types testpnpm --filter @object-ui/plugin-detail --filter @object-ui/plugin-grid type-checknode scripts/check-changeset-presence.mjspnpm check:control-bytespnpm check:new-line-citationspnpm check:changeset-claimszod-mirror-parity.test.ts. Read, as the gate asks: every one of them describesEXCLUSIONS,RuntimeOnlyDeclared,KnownDriftorUnmirroredDeclared, and none mentions the fourth ledger or either key this branch touched. ⇒ nothing went false, so nothing is correctednode scripts/check-governed-queue-guard.mjs --test(5 paths)scripts/pm/; it lives atscripts/check-governed-queue-guard.mjs, and the first invocation was a MODULE_NOT_FOUND, ⛔ not a red gateeslintover the changed filespnpm lintis CI's run and is ⛔ not measured hereReverse validation — both pins were made to fail, then restored
Run from the committed state, each leg with an on-disk anchor count before and after the edit, and each restore proven by an empty
git diff HEADplus a blob hash identical to the HEAD blob:dueLikemember fromviews.zod.ts— anchor count 1 to 0, then the new pin goes RED: 2 of its 7 assertions fail. ⇒ the pin measures the mirror, not itself.ts-expect-errordirective from the pin — anchor count 1 to 0, thentsc -p tsconfig.test.jsonexits 2 withTS2353: … 'dueLike' does not exist in type 'DetailViewField'. ⇒ the refusal is real and the directive is load-bearing.Both mutations are gone; nothing from them is in this branch.
Serial constraint — re-taken against the LIVE pull requests
Re-taken at 2026-09-17T22:16Z, after the sibling appeared: objectui#9637 is now PR #9734, and its file list is two changesets, one new changeset and
packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts— it does not touchzod-mirror-parity.test.ts, so the warning's stop condition did not trigger. PR #9732 (plugin-grid) listsObjectGrid.tsx,hooks/useBulkExecutor.ts, one test and a changeset; the file added here is a different file. PR #9540 does listpackages/types/src/objectql.tsandpackages/types/src/zod/objectql.zod.ts— ⛔ neither is edited here, since no accept set moves on this branch.packages/types/src/{views,objectql}.tsand their zod twins. None of those four is edited — the measurement outcome is that nothing on a published face should move without the review. Two test files outside the declared surface were added instead, in the packages that own the two runtime read paths, because the card's first question cannot be answered frompackages/types, which has no renderer.Generated by Claude Code