diff --git a/.changeset/9729-mirror-only-published-keys-measured.md b/.changeset/9729-mirror-only-published-keys-measured.md
new file mode 100644
index 0000000000..f6bd28400f
--- /dev/null
+++ b/.changeset/9729-mirror-only-published-keys-measured.md
@@ -0,0 +1,14 @@
+---
+---
+
+Measure the two LOCAL mirrored-but-undeclared keys the `MirroredUndeclared`
+ledger carries — `views.zod.ts#DetailViewFieldSchema`'s `dueLike` and
+`objectql.zod.ts#ObjectGridSchema`'s `operators` — per key, for objectui#9729's
+at-tier contract review. Test only; no package is released by this change, and
+neither published accept set moves: the remedy for each key (declare it on the
+twin, or narrow the mirror) is the decision this measurement exists to inform.
+
+Also corrects the ledger entry for `dueLike`, which justified itself with a
+package-wide name grep. `field-types.ts` does declare that name — on
+`DateFieldMetadata` and `DateTimeFieldMetadata`, not on this pair's twin. The
+measurement was right and its stated reason was not: a name is not a key.
diff --git a/packages/plugin-detail/src/__tests__/DetailSection.dueLikeReachesTheCell-9729.test.tsx b/packages/plugin-detail/src/__tests__/DetailSection.dueLikeReachesTheCell-9729.test.tsx
new file mode 100644
index 0000000000..934df4d085
--- /dev/null
+++ b/packages/plugin-detail/src/__tests__/DetailSection.dueLikeReachesTheCell-9729.test.tsx
@@ -0,0 +1,78 @@
+/**
+ * ObjectUI
+ * Copyright (c) 2024-present ObjectStack Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/**
+ * An authored `dueLike` on a DETAIL-VIEW FIELD reaches the cell (objectui#9729).
+ *
+ * ## Why this reading exists
+ *
+ * `views.zod.ts#DetailViewFieldSchema` states `dueLike` and the `DetailViewField`
+ * twin does not — one of the two LOCAL entries in the `MirroredUndeclared`
+ * ledger. The contract review that decides which published face moves needs one
+ * fact the ledger cannot carry: whether anything READS the key on THIS pair's
+ * authored face, because a key nothing reads is a different decision from one a
+ * renderer consumes.
+ *
+ * ⛔ A source grep cannot answer that. `SchemaRenderer` hands a node's leftover
+ * keys to the component as props, and this path adds a second hop of its own:
+ * `DetailSection` spreads the authored field into the enriched bag
+ * (`enrichDetailField` opens with a spread of the view field) and hands that bag
+ * to the resolved cell renderer. So the question is asked here the only way it
+ * can be answered — by rendering, and by watching the drawn text change.
+ *
+ * ## The reading, and its control
+ *
+ * `end_date` deliberately does NOT match the due/deadline field-NAME convention
+ * that `resolveDueLike` falls back to, so the authored key is the only thing
+ * that can turn the affordance on. The control leg renders BY VALUE — it asserts
+ * the neutral relative wording is present, not merely that "Overdue" is absent,
+ * which would also be true of a document that drew nothing.
+ *
+ * ⛔ This file takes no position on the remedy. It measures a read; whether the
+ * key should be declared on the twin or removed from the mirror is objectui#9729.
+ */
+
+import { describe, it, expect, beforeAll, afterEach } from 'vitest';
+import { render, cleanup } from '@testing-library/react';
+import * as React from 'react';
+import { DetailSection } from '../DetailSection';
+import type { DetailViewSection } from '@object-ui/types';
+
+beforeAll(() => {
+ Object.defineProperty(window, 'innerWidth', { configurable: true, value: 1280 });
+});
+afterEach(cleanup);
+
+const daysAgo = (n: number): string => {
+ const d = new Date();
+ d.setDate(d.getDate() - n);
+ return d.toISOString();
+};
+
+const sectionOf = (fields: unknown[]): DetailViewSection =>
+ ({ title: 'S', fields }) as unknown as DetailViewSection;
+
+/** A past date on a field whose NAME carries no due/deadline convention. */
+const BASE = { name: 'end_date', label: 'End', type: 'date', format: 'relative' };
+const DATA = { end_date: daysAgo(3) };
+
+describe('an authored `dueLike` on a DetailViewField', () => {
+ it('reaches the date cell and changes the drawn wording', () => {
+ render();
+ expect(document.body.innerHTML).toContain('Overdue');
+ });
+
+ it('CONTROL — the same field without the key draws the neutral wording', () => {
+ render();
+ const html = document.body.innerHTML;
+ // Rendered BY VALUE first: an absent affordance means nothing on a document
+ // that drew nothing at all.
+ expect(html).toContain('days ago');
+ expect(html).not.toContain('Overdue');
+ });
+});
diff --git a/packages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx b/packages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx
new file mode 100644
index 0000000000..124d6088cb
--- /dev/null
+++ b/packages/plugin-grid/src/__tests__/ObjectGrid.operatorsInert-9729.test.tsx
@@ -0,0 +1,98 @@
+/**
+ * ObjectUI
+ * Copyright (c) 2024-present ObjectStack Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/**
+ * An authored `operators` on an `object-grid` changes nothing drawn (objectui#9729).
+ *
+ * ## Why this reading exists
+ *
+ * `objectql.zod.ts#ObjectGridSchema` states `operators` and the `ObjectGridSchema`
+ * twin does not — the second LOCAL entry in the `MirroredUndeclared` ledger, and
+ * the one whose mirror line carries its own provenance: a comment saying the key
+ * was missing from an earlier TypeScript scan. The contract review needs to know
+ * whether a renderer consumes it.
+ *
+ * ⛔ A source grep cannot answer that, because this renderer receives a node's
+ * leftover keys as props: `ObjectGridRenderer` spreads everything it does not
+ * itself consume onto `ObjectGrid`. So the question is asked with a RULER — the
+ * same document drawn twice, compared as bytes — rather than by reading code.
+ *
+ * ## The ruler, and why the control comes first
+ *
+ * A byte comparison that reports "no difference" is worthless unless the
+ * instrument can report one. The first case pins that two identical documents
+ * draw identical bytes (the ruler is stable — no timestamps, no random ids), the
+ * second is a LIT CONTROL on a key the renderer demonstrably DOES read, and only
+ * then is the measurement taken. It is taken twice: once on a plain grid and
+ * once with the filter surface on, since `operators` is a filtering word and a
+ * grid without a filter affordance would be the wrong corpus to ask.
+ *
+ * ⚠️ The claim is bounded by the ruler: this is what the RENDERED output does
+ * with the key, on these documents. ⛔ It is not a claim that no code anywhere
+ * could ever read it.
+ *
+ * ⛔ This file takes no position on the remedy — objectui#9729 is the card.
+ */
+
+import { describe, it, expect, afterEach } from 'vitest';
+import { render, screen, cleanup } from '@testing-library/react';
+import React from 'react';
+import { ObjectGridRenderer } from '../index';
+
+afterEach(cleanup);
+
+const BASE: Record = {
+ type: 'object-grid',
+ objectName: 'probe',
+ columns: ['name'],
+ staticData: [
+ { id: '1', name: 'Alpha' },
+ { id: '2', name: 'Beta' },
+ ],
+};
+
+/** Draw one document and return its markup, once the rows are on screen. */
+async function draw(schema: Record): Promise {
+ const { container } = render();
+ await screen.findByText('Alpha', {}, { timeout: 5000 });
+ return container.innerHTML;
+}
+
+describe('the ruler', () => {
+ it('two identical documents draw identical bytes', async () => {
+ const a = await draw({ ...BASE });
+ cleanup();
+ const b = await draw({ ...BASE });
+ expect(b).toBe(a);
+ });
+
+ it('LIT CONTROL — a key the renderer DOES read moves the bytes', async () => {
+ const a = await draw({ ...BASE });
+ cleanup();
+ const b = await draw({ ...BASE, label: 'GRID-CAPTION-9729' });
+ expect(b).not.toBe(a);
+ expect(b).toContain('GRID-CAPTION-9729');
+ });
+});
+
+describe('an authored `operators`', () => {
+ it('changes nothing the grid draws', async () => {
+ const a = await draw({ ...BASE });
+ cleanup();
+ const b = await draw({ ...BASE, operators: { name: ['equals', 'contains'] } });
+ expect(b).toBe(a);
+ });
+
+ it('changes nothing with the filter surface on either', async () => {
+ const FILTERS = { ...BASE, showFilters: true, searchableFields: ['name'] };
+ const a = await draw({ ...FILTERS });
+ cleanup();
+ const b = await draw({ ...FILTERS, operators: { name: ['equals', 'contains'] } });
+ expect(b).toBe(a);
+ });
+});
diff --git a/packages/types/src/__tests__/mirror-only-published-keys-9729.test.ts b/packages/types/src/__tests__/mirror-only-published-keys-9729.test.ts
new file mode 100644
index 0000000000..d0f606de8e
--- /dev/null
+++ b/packages/types/src/__tests__/mirror-only-published-keys-9729.test.ts
@@ -0,0 +1,182 @@
+/**
+ * ObjectUI
+ * Copyright (c) 2024-present ObjectStack Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/**
+ * The two LOCAL mirrored-but-undeclared keys, measured per key (objectui#9729).
+ *
+ * ## What this file is, and ⛔ what it is not
+ *
+ * `MirroredUndeclared` in `zod-mirror-parity.test.ts` measures the DIRECTION —
+ * a key the zod mirror states and the TypeScript twin does not — and reconciles
+ * it per pair. It deliberately says nothing about what each key DOES, because
+ * the remedy is a contract decision: declaring the key on the twin ENLARGES the
+ * published TypeScript accept set, narrowing the mirror SHRINKS the published
+ * validator's, and both move a face `@object-ui/types` already ships.
+ *
+ * ⛔ This file does not pick either direction, and it is ⛔ not a repair. It is
+ * the per-key CONSEQUENCE the ledger's one-line entries cannot carry, recorded
+ * so the at-tier contract review can read it instead of re-deriving it. Every
+ * assertion below is a statement about `origin/main` as it stands; whichever
+ * direction the review rules, the entries here move with it.
+ *
+ * ## The two keys do NOT behave the same way, and the difference is the ruling
+ *
+ * Both twins are hand-written interfaces, but only one of them can REFUSE:
+ *
+ * - `DetailViewField` carries no index signature, so TypeScript rejects an
+ * authored `dueLike` outright (`TS2353`). The two published faces therefore
+ * actively CONTRADICT each other: the validator judges the key and keeps
+ * it, the compiler refuses the same document.
+ * - `ObjectGridSchema` extends `BaseSchema`, whose `[key: string]: any` index
+ * signature absorbs any unstated key. TypeScript neither declares nor
+ * refuses `operators`; it types it `any`. The faces do not contradict — one
+ * is simply silent, which is a weaker defect and a different decision.
+ *
+ * ## ⚠️ The third key the ledger groups with these two is NOT this defect
+ *
+ * `complex.zod.ts#DashboardConfigSchema`'s `aria` is a `z.never()` retirement
+ * tombstone: it REFUSES the key by name and prints a remedy. Its consequence is
+ * the OPPOSITE of the two above — they admit silently, it rejects loudly — so a
+ * sweep that treats the three alike would ask a maintainer to repair something
+ * already doing its job. Pinned here as the CONTRAST, by re-deriving its shape
+ * rather than by citing the reading.
+ *
+ * ## Every zero below carries a lit control on the same instrument
+ *
+ * A green `safeParse` proves nothing on its own: both mirrors accept unknown
+ * keys (one strips them, the other keeps them unexamined). What distinguishes a
+ * MIRRORED key from an unknown one is that the mirror JUDGES it — so each key's
+ * pin is paired with an unrecognised-key control parsed through the same schema.
+ */
+
+import { describe, it, expect } from 'vitest';
+import { DetailViewFieldSchema } from '../zod/views.zod.js';
+import { ObjectGridSchema } from '../zod/objectql.zod.js';
+import { DashboardConfigSchema } from '../zod/complex.zod.js';
+import type { DetailViewField } from '../views.js';
+import type { ObjectGridSchema as ObjectGridSchemaType } from '../objectql.js';
+
+/** A control key no surface in this package declares. */
+const UNKNOWN_KEY = 'zzzNotAKeyAnySurfaceDeclares9729';
+
+describe('views.zod.ts#DetailViewFieldSchema — `dueLike`', () => {
+ it('the mirror JUDGES the key, while an unknown key is stripped unexamined', () => {
+ const judged = DetailViewFieldSchema.safeParse({ name: 'end_date', dueLike: true });
+ expect(judged.success).toBe(true);
+ // KEPT, not stripped — the mirror states it.
+ expect(judged.success && judged.data).toMatchObject({ dueLike: true });
+
+ // LIT CONTROL, same schema, same document shape: an unstated key survives
+ // the parse and is GONE from the output. So "green" is not the evidence —
+ // the key surviving into `data` is.
+ const control = DetailViewFieldSchema.safeParse({ name: 'end_date', [UNKNOWN_KEY]: true });
+ expect(control.success).toBe(true);
+ expect(control.success && Object.keys(control.data)).not.toContain(UNKNOWN_KEY);
+ });
+
+ it('the mirror refuses a wrong-typed `dueLike` BY NAME', () => {
+ const r = DetailViewFieldSchema.safeParse({ name: 'end_date', dueLike: 'yes' });
+ expect(r.success).toBe(false);
+ expect(!r.success && r.error.issues.map((i) => i.path.join('.'))).toContain('dueLike');
+ });
+});
+
+describe('objectql.zod.ts#ObjectGridSchema — `operators`', () => {
+ it('the mirror JUDGES the key, while an unknown key is kept unexamined', () => {
+ const judged = ObjectGridSchema.safeParse({
+ type: 'object-grid',
+ objectName: 'probe',
+ operators: { name: ['equals'] },
+ });
+ expect(judged.success).toBe(true);
+
+ // LIT CONTROL: this mirror's base passes unknown keys THROUGH, so an
+ // unstated key is kept as-is and is never type-checked. The distinguishing
+ // evidence for a mirrored key is therefore the refusal below, not the pass.
+ const control = ObjectGridSchema.safeParse({
+ type: 'object-grid',
+ objectName: 'probe',
+ [UNKNOWN_KEY]: 42,
+ });
+ expect(control.success).toBe(true);
+ });
+
+ it('the mirror refuses a wrong-typed `operators` BY NAME, and ignores the control', () => {
+ const judged = ObjectGridSchema.safeParse({
+ type: 'object-grid',
+ objectName: 'probe',
+ operators: 42,
+ });
+ expect(judged.success).toBe(false);
+ expect(!judged.success && judged.error.issues.map((i) => i.path.join('.'))).toContain('operators');
+
+ const control = ObjectGridSchema.safeParse({
+ type: 'object-grid',
+ objectName: 'probe',
+ [UNKNOWN_KEY]: 42,
+ });
+ expect(control.success).toBe(true);
+ });
+});
+
+describe('complex.zod.ts#DashboardConfigSchema — `aria` is the CONTRAST, not the defect', () => {
+ it('REFUSES the key by name and names a remedy — the opposite consequence', () => {
+ const r = DashboardConfigSchema.safeParse({ aria: { label: 'x' } });
+ expect(r.success).toBe(false);
+ const issue = !r.success ? r.error.issues.find((i) => i.path.join('.') === 'aria') : undefined;
+ expect(issue).toBeDefined();
+ // The remedy travels with the refusal. Asserted as the leading token rather
+ // than the whole sentence: the wording is the contract here, the prose that
+ // follows it is not.
+ expect(String(issue?.message)).toContain('RETIRED');
+
+ // LIT CONTROL on the same schema: an unrecognised key is NOT refused, so
+ // the refusal above is BY NAME and not a strictness the whole object has.
+ const control = DashboardConfigSchema.safeParse({ [UNKNOWN_KEY]: true });
+ expect(control.success).toBe(true);
+ });
+
+ it('a document that omits the key stays green — a tombstone gates nothing else', () => {
+ expect(DashboardConfigSchema.safeParse({ showHeader: true }).success).toBe(true);
+ });
+});
+
+/* ── The TypeScript side, where the two keys stop behaving alike ───────────── */
+
+/**
+ * `DetailViewField` REFUSES `dueLike`. The `@ts-expect-error` below IS the
+ * assertion: it fails the package's `tsc -p tsconfig.test.json` leg in both
+ * directions — if the key stops being refused (someone declares it on the twin,
+ * which is one of the two remedies under review) the directive becomes unused
+ * and TypeScript reports it.
+ */
+// @ts-expect-error objectui#9729 — `dueLike` is not a member of `DetailViewField`.
+export const authoredDueLike: DetailViewField = { name: 'end_date', dueLike: true };
+
+/** LIT CONTROL: a key the twin DOES declare is accepted on the same literal. */
+export const authoredCurrency: DetailViewField = { name: 'end_date', currency: 'USD' };
+
+/**
+ * `ObjectGridSchema` does NOT refuse `operators` — and the absence of a
+ * `@ts-expect-error` here is the measurement, not an omission: adding one
+ * reddens the same type-check leg as unused, which is how this claim fails if
+ * the twin ever loses `BaseSchema`'s index signature.
+ */
+export const authoredOperators: ObjectGridSchemaType = {
+ type: 'object-grid',
+ objectName: 'probe',
+ operators: { name: ['equals'] },
+};
+
+describe('the TypeScript twins', () => {
+ it('are compiled by this package’s type-check leg, which is where the two directives above are read', () => {
+ // The runtime here only keeps the three bindings alive; the assertions that
+ // matter are the directives, and `tsc -p tsconfig.test.json` is the reader.
+ expect([authoredDueLike, authoredCurrency, authoredOperators]).toHaveLength(3);
+ });
+});
diff --git a/packages/types/src/__tests__/zod-mirror-parity.test.ts b/packages/types/src/__tests__/zod-mirror-parity.test.ts
index ee6b2bf2c1..9f8d5ea259 100644
--- a/packages/types/src/__tests__/zod-mirror-parity.test.ts
+++ b/packages/types/src/__tests__/zod-mirror-parity.test.ts
@@ -3649,15 +3649,38 @@ interface MirroredUndeclared {
* was missing from an earlier TypeScript scan — so the gap was NOTICED at authoring
* time, written down in a comment, and then measured by nothing for as long as the
* fourth direction did not exist. That is this card's thesis in one key.
+ *
+ * ⚠️ The twin is NOT silent about the neighbouring name: `ObjectGridSchema`
+ * declares `operations` (the CRUD-affordance toggles the block's authoring
+ * vocabulary and `@objectstack/spec`'s `object-grid` surface both spell that
+ * way), and it is `BaseSchema`'s index signature — not a declaration — that
+ * lets an authored `operators` type-check. Per-key consequences, including
+ * which face judges the key and which merely absorbs it, are measured by
+ * `mirror-only-published-keys-9729.test.ts` for objectui#9729's contract
+ * review. ⛔ Neither remedy is taken there and none is implied here.
*/
"objectql.zod.ts#ObjectGridSchema":
| "operators";
/**
- * LOCAL, one key. `dueLike` exists on the zod face only: `git grep` finds it in the
- * mirror and nowhere in this package's TypeScript declarations. An author writing
- * it gets a green `safeParse` from a published validator for a key the published
- * types never invited.
+ * LOCAL, one key. An author writing `dueLike` on a detail-view field gets a green
+ * `safeParse` from a published validator for a key the published `DetailViewField`
+ * never invited — and, because that twin carries no index signature, the SAME
+ * document is refused by the compiler (`TS2353`). The two published faces
+ * contradict each other rather than one of them being merely quiet.
+ *
+ * ⛔ This entry once justified itself with "a `git grep` finds it in the mirror and
+ * nowhere in this package's TypeScript declarations", which is FALSE as written and
+ * is corrected here rather than repeated: `field-types.ts` declares `dueLike` on
+ * `DateFieldMetadata` and on `DateTimeFieldMetadata`, 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 this ledger is not asking. What the operator measures,
+ * and what stays true, is that the key is absent from THIS PAIR'S twin.
+ *
+ * The per-key consequences — that the mirror judges the key rather than merely
+ * passing it, and that objectui's own detail-view path READS it at runtime — are
+ * measured by `mirror-only-published-keys-9729.test.ts` and by the two probes
+ * objectui#9729 landed beside the renderers that read it. ⛔ No remedy is implied.
*/
"views.zod.ts#DetailViewFieldSchema":
| "dueLike";