You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(plugin-grid): 6 of the 10 keys ObjectGrid copies onto a column's fieldMeta have no reader on that bag — the picker keys reach the editor by a different seam #7166
Provenance: measured while implementing objectui#7154, by the same rendering measurement that card asked for. Filed unassigned. Not folded into objectui#7154 — that card is about four keys NOT on the copy set; this is about keys that ARE on it.
What is measured
On 51449a043, RELATIONAL_META_KEYS derives to 10 keys:
applyRelationalMeta writes them onto fieldMeta, and fieldMeta in ObjectGrid.tsx is handed to CellRenderer as the field prop and to nothing else — grepped, all six occurrences are that one JSX prop across the three column-building paths. getCellRenderer sends a relational column to LookupCellRenderer (packages/fields/src/index.tsx) and a user column to UserCellRenderer.
LookupCellRenderer reads exactly reference_to, reference, display_field, displayField, reference_field off that prop (plus options).
UserCellRenderer destructures { value } only — it reads no field meta at all.
Intersecting: 6 of the 10 copied keys have no reader on the bag they are copied onto — descriptionField, description_field, lookupColumns, lookupFilters, lookup_filters, id_field.
That is the shape objectui#6711 (reference_to_field) and objectui#6874 (titleFormat) each retired: a member written from the schema def on every column build that nothing on the receiving path reads.
Why the docblocks say otherwise
Both relationalMetaKeys.ts and relationalMetaCopySet.derivation.test.ts stated that the grid's inline editor "dispatches the same bag" into LookupField and UserField. It does not. ObjectGrid.renderCellEditor looks the field up in the object schema and spreads the WHOLE def into the widget:
const fieldDef = (objectSchema)?.fields?.[ctx.column?.accessorKey];
let field: any = { name: ctx.column.accessorKey, ...fieldDef };
So every key a def carries reaches LookupField whether or not it is copied. objectui#7154's PR corrected those two docblocks and pinned the consequence by rendering: four spec-declared picker keys that are NOT on the copy set (multiple, allowCreate, lookupPageSize, dependsOn) all take effect in the inline picker with the table unchanged, and an ablation that strips them from the editor's spread turns 4 of those 5 assertions red while the copy-set assertion stays green.
Why this matters beyond tidiness
The keys are not equally inert.
id_field, lookup_filters, description_field were copied for the inline picker, on the docblock's stated reasoning ("the extra keys drive the inline picker's query"). The picker gets them from the def instead, so the copies are dead writes — the objectui#6711 class, and retirable on the same reasoning.
descriptionField and lookupColumns were ADDED by objectui#6875 as its fix, on the reading that they were "read, never copied" spellings the picker needed. Under this measurement that half of objectui#6875 delivered nothing: their only reader is LookupField, which was already receiving them off the def. objectui#6875's third key, displayField, is genuinely delivered — LookupCellRenderer reads it, and lookupDisplayFieldSpelling-6875.test.tsx renders the difference. So objectui#6875 is 1-for-3, not wrong.
What is NOT measured
Whether the DERIVATION should be re-scoped. The gate sweeps three consumers, but only one of them is fed this bag; a read set that mixes the cell's bag with the editor's def is why a "read, never copied" verdict can be reached for a key that needed no copying. Rebuilding it around the cell alone (with the editor widgets classified separately) is a design change to objectui#6875's mechanism, not a call to make from here.
Whether any host DataSource outside these two repos feeds fieldMeta to something else. The sweep bounds packages/ and apps/ in this repo, as objectui#6875's own docblock notes for its verdicts.
Whether retiring the four legacy spellings is safe on back-compat grounds. id_field, lookup_filters, description_field are recorded as legacy-alias precisely because a host outside these repos may hand-feed them; that argument is about producers, and is untouched by this reader-side measurement.
File collision: packages/plugin-grid/ is live under PR objectui#7167. Unblocks automatically.
This card audits a fix I landed an hour ago, and it is right to
Two of the six dead keys — descriptionField and lookupColumns — were added by objectui#6875 as its fix. They are inert on that bag: LookupField reads them, but via the schema spread in renderCellEditor, not from fieldMeta. So objectui#6875 delivered 1 of the 3 keys it shipped.
I have posted that correction on objectui#6875. The displayField half is genuinely delivered and stays pinned — and it is the one that came with a rendering test (ACME-42 vs Wrong Name). The two inert keys were classified from a read-set derivation alone.
⚠️That is the generalisable lesson and it belongs in this card's eventual fix: a derivation establishes that a consumer reads a key. It does not establish that this bag is how the consumer gets it. Those are different claims, and objectui#6875's evidence bar only covered the first for two of its three keys — my omission, not the dev's.
Note for whoever takes it
This is the objectui#6711 / objectui#6874 class arriving from the opposite direction — those cards removed keys with no declaration; this removes keys with no reader on this path.
⛔ Do not simply delete all six. Each needs the seam question answered per key: does any consumer read it off fieldMeta, as opposed to reading it after a schema spread? displayField is the worked example of a key that genuinely does. And ⛔ the derivation gate in relationalMetaKeys.ts must keep working — removals go through the table, not by hand.
Retiring a declared-but-unread key may engage ADR-0049 enforce-or-remove; if the chosen route widens or narrows a published surface, that is Clause ② and the fable quota is exhausted — stop and report rather than routing around it.
Serial constraints cleared: the packages/plugin-grid/ collision this card was blocked on is gone — PR test(plugin-grid): render what the inline lookup picker actually receives #7167 merged and is verified in the tree (new suite lookupPickerKeys-7154.test.tsx present, control lookupDisplayFieldSpelling-6875.test.tsx present alongside it). No other open PR touches this package.
Clause ② — assessed conditionally not engaged, and the condition is the implementer's first measurement
Removing dead writes onto an internal bag moves no published surface, so on its face this is below CONTRACT_REVIEW_TIER. ⛔ But that rests on RELATIONAL_META_KEYS / relationalMetaKeys.ts being internal to plugin-grid rather than re-exported from the package barrel — and this seat has not verified that. It is the first thing the dispatch order asks for, with an explicit instruction to stop and report if the constant is public. The fable quota is exhausted, so a Clause ②-engaged route cannot land today regardless of merit.
⚠️ Scope is 3 keys, not 6 — the split is in the card's own "What is NOT measured"
The card measures six keys with no reader on the bag. They are not one population:
keys
spelling
disposition
descriptionField, lookupColumns, lookupFilters
modern camelCase
in scope — dead writes on reader-side evidence
id_field, lookup_filters, description_field
snake_case legacy-alias
⛔ out of scope — recorded as legacy-alias precisely because a host outside these repos may hand-feed them. That is a producer-side argument and this card's measurement is entirely reader-side. Retiring them on this evidence would be answering a question nobody asked.
⇒ The deliverable is the 3 modern spellings retired, and the 3 legacy aliases left in place with their reader-side verdict recorded and the unanswered producer question stated on the table, so the next pass inherits a measurement instead of a silence.
The lesson this card exists to encode, restated for the implementer
⭐ A derivation establishes that a consumer READS a key. It does not establish that a given BAG is how the consumer gets it. Those are different claims. #6875 classified descriptionField and lookupColumns from a read-set derivation alone and shipped them onto a bag their only reader never consults — LookupField was already receiving them off the schema spread in renderCellEditor. Its third key, displayField, is genuinely delivered, and it is the one that arrived with a rendering test (ACME-42 vs Wrong Name). One-for-three, and the difference between the hit and the misses is exactly the evidence bar. That omission was mine, not the dev's — and it is why this card's fix must be pinned by rendering, not by derivation.
{
"issue": 7166,
"status": "done",
"branch": "claude/issue-7166-inert-fieldmeta-copies",
"pr": "https://github.com/objectstack-ai/objectui/pull/7186",
"premise_still_valid": true,
"summary": "RULING ITEM 3 FIRST, and it came back INTERNAL, so I proceeded: `RELATIONAL_META_KEYS` is not re-exported from the plugin-grid barrel (fully enumerated export list, no `export *`), the built `dist/index.d.ts` holds zero occurrences of any of the module's symbols, and it is absent from the built bundle's export list — each with `useRowColor` as a live control that hits in all three probes. Clause 2 not engaged. A2.1 re-derived on 47035ce79 and it still holds: the copy set was 10, six had no reader on the bag. Retired the 3 modern spellings (`descriptionField`, `lookupColumns`, `lookupFilters`) through the derivation table by flipping their verdict to `deferred`, the verdict this repo already uses for the same class; copy set now derives to 7, verified at runtime. Kept the 3 snake_case legacy aliases with their reader-side verdict recorded on the table and the unanswered producer question stated beside it, so the next pass inherits a measurement rather than a silence. A2.3, A2.4 and A2.5 all confirmed on head by re-measurement, not inherited: 6 `field={fieldMeta}` JSX passes across the three column-building paths and no other whole-object use except `applyRelationalMeta`; `UserCellRenderer` has zero occurrences of `field` against a control of 8 in `LookupCellRenderer`; and `packages/fields/src/index.tsx`, which holds every cell renderer, has zero occurrences of all six keys against a control of 22 for the spellings the cell does read. The gate was strengthened, not weakened: every `deferred` verdict is now mechanically proved spec-declared, and the two sibling pins turn the retired `lookupFilters` into a live negative assertion on a fixture that still declares it. Note for the PM: the platform appended a second copy of the signature footer to the PR body; I left it, because a PATCH edit would downgrade both footers to the bare form and lose the session URL.",
"tests": "All runs used the canonical repo-root vitest invocation (this repo's guard refuses a package-cwd run as a false green) and went through the shared verify lock. UNION on the final commit f4f299e23: `pnpm exec vitest run packages/plugin-grid/src packages/fields/src/__tests__/lookupCellDisplayField.test.tsx` -> `Test Files 106 passed (106)` / `Tests 956 passed (956)`, VERDICT command-exit 0. Baseline before any edit was 5 files / 27 tests; the relational suites after the change are 6 files / 34 tests. `pnpm --filter @object-ui/plugin-grid run type-check` clean — the FIRST attempt returned TS2307 'Cannot find module @object-ui/*' across files I never touched, which is an unbuilt dependency closure and NOT MEASURED rather than a red; re-ran after `pnpm --filter '@object-ui/plugin-grid^...' build` (13 packages) and it passed with the script name echoed, so it was not a zero-match no-op. Confirmed the new test file is actually IN the tsconfig.test.json program with `--listFiles` (1 hit, control `lookupPickerKeys-7154` also 1, 1770 files) so that green really does cover it. LINT: ran `eslint . --no-inline-config` REPO-WIDE rather than narrowing — 4100 files by eslint's own count, 75 files with errors, every one pre-existing and none in this diff; my files are E0 (one unused import it caught was fixed). Control-character self-scan clean over all 6 changed files, with the pattern itself verified against 0x01 and 0x0b after my first control (a tab) proved nothing because 0x09 is deliberately outside the class. ABLATIONS — both targets resolve from SOURCE (the root config aliases every @object-ui/* to src/, and both are relative imports), so no rebuild leg applies and none was needed; each mutation was proven on disk by marker count AND blob hash versus the HEAD blob, never by an editor's exit code, and each restore proven by STATE with `git diff HEAD`, `git diff --cached` and `git status --short` all empty plus the blob back to the HEAD blob and the marker gone. Both ran against the COMMITTED implementation. (1) Break `renderCellEditor`'s schema spread — direction predicted before running as 'the editor half goes red, the cell half stays green' — result `Tests 3 failed | 2 passed`: exactly B1/B2/B3 red, test A and the premise test green. (2) Re-add `lookupFilters` to the copy set — direction predicted as 'the derived gate assertions stay green, only the hand-written pins go red' — result `Tests 8 failed | 19 passed`, and the ONLY derivation-gate failure was my hand-written pin while every derived assertion stayed green. One flaw recorded rather than hidden: in ablation 2 my `deferred`-count grep was malformed by nested quotes and printed a bogus 0 — the exact 'empty output read as a zero' trap; the mutation was still proven by the injected-marker count and the blob change, and I re-counted cleanly afterwards (7 deferred, control `legacy-alias` 4).",
"mcp_calls": "9 — REST was 403 on every repo-scoped read and `gh` is absent, so the duplicate check for the filed finding used ONE targeted MCP search_issues (3 hits, so a non-empty reading needing no control query). Channel switch declared.",
"open_questions": [
{
"question": "The 3 snake_case legacy aliases (`description_field`, `lookup_filters`, `id_field`) have the SAME reader-side verdict as the three I retired — no reader on this bag — and are kept only on a producer-side argument that no evidence gathered in these two repos can confirm or refute: does any host DataSource outside objectui and objectstack actually hand-feed these spellings onto a field def? Their verdict is now recorded on the table, so this is not blocking, but it is the question that decides whether a future card retires them.",
"options": [
"A. Leave as is. The reader-side verdict is now recorded with the open producer question stated beside it, so the next pass inherits a measurement instead of a silence, and nothing is retired on evidence that does not address the argument for keeping them.",
"B. Commission a producer survey outside these two repos (host DataSource implementations, any published adapter) and retire whichever aliases come back with zero producers.",
"C. Retire all three now on the reader-side measurement alone, treating the legacy-alias verdict as unbacked."
],
"recommendation": "A, and B only if someone independently wants the producer inventory for its own sake. On the four axes: REAL BUSINESS NEED — there is no measured pull for retiring them; they cost three conditional writes per relational column build and nothing else, while the failure mode of being wrong is a silently broken host integration we cannot see from here. LONG-TERM SOUNDNESS — the honest state is now encoded rather than implied, which is the durable improvement; retiring on reader-side evidence would set the precedent that a back-compat verdict can be overturned by a measurement that does not address it. AI-ERROR-RESISTANCE — this is the axis that argues hardest against C: these are runtime-only spellings that no spec-compliant producer can emit, so the strict FieldSchema already stops an AI-authored app from declaring them, and removing the runtime tolerance buys no authoring-time safety while risking a live host. STARTUP SCOPE — C is the option that spends real risk on tidiness with zero pull, and B spends survey effort on the same. C is explicitly out of scope per the ruling and I did not take it."
}
],
"out_of_scope_findings": [
"filed as #7187: the relational copy-set derivation cannot judge a COPY — its read set is a union over three consumers, two of which are never fed that bag, so read-set membership proves only that some consumer reads a key and not that this bag is how it arrives. Pinned by my ablation 2: re-adding a retired key leaves every DERIVED gate assertion green and turns red only the hand-written pins. Re-scoping it around the cell alone is a design change to #6875's mechanism, which #7166 explicitly declined to make, so it is filed rather than ridden. Unassigned, labels finding + domain:ui.",
"not filed, recorded here: #7165 (the dependsOn uneditable-column bug) was named out of scope by the dispatch and I did not touch it; the `dependsOn` gating behaviour it concerns is still pinned as current behaviour by #7154's suite, which stayed green throughout."
]
}
legacy-alias, kept — each carrying its reader-side verdict and the open producer question
displayField
still delivered — the 1 of 3 keys #6875 genuinely shipped
7 verdict: 'deferred' entries, 4 verdict: 'legacy-alias' — exactly the counts the implementer reported.
⚠️ My first probe was wrong, and it is on my own standing rules list
I ran git grep -c and got 10deferred and 8legacy-alias, against the reported 7 and 4. Both inflated in the same direction, which is the tell.
git grep -c counts matching lines, and this file is heavily documented: the extra hits are the type union (:211, :201), a membership array (:217), and prose (:93, :150, :262, :267). The rule — "a line-count grep over a documented file counts PROSE alongside code… both INFLATE, so they read as MORE evidence" — is one I carry in every dispatch order, and I still tripped it.
⭐ Inflation is the dangerous direction, because it reads as more confirmation rather than less. Had the true count been 7 and my probe returned 7 by coincidence of prose, I would have "verified" nothing. The fix was to print the matching lines and count the entries, which took one extra command.
That is my third instrument slip today (two markers that a correction quotes, now this). All three were caught, but the pattern is worth naming: every one was a probe I wrote quickly against a rule I had already written down.
What landed
Copy set 10 → 7, done through the derivation table rather than by hand, and the gate came out stronger: every deferred verdict is now mechanically proved spec-declared, and the retired lookupFilters becomes a live negative assertion on a fixture that still declares it.
Ruling item 3 — is RELATIONAL_META_KEYS public surface? — came back internal on three probes, each with a useRowColor control: not in the barrel's fully enumerated export list (no export *), zero occurrences in the built dist/index.d.ts, absent from the built bundle's exports. ⇒ Clause ② never engaged.
The open question stays ruled A — the three legacy aliases keep their verdict recorded beside the question they cannot answer
They have the same reader-side verdict as the three retired, and are kept on a producer-side argument no evidence from these two repos addresses. ⛔ A back-compat verdict cannot be overturned by a measurement that does not address it. No spec-compliant producer can emit those spellings, so the strict FieldSchema already blocks them at authoring time; removing runtime tolerance buys no authoring-time safety while risking a host integration we cannot see.
⭐ What shipped is better than either "retire" or "leave alone": the table now carries the reader-side verdict and the unanswered producer question side by side, so the next pass inherits a measurement instead of a silence.
the relational copy-set derivation cannot judge a COPY — its read set is a union over three consumers, two of which are never fed that bag
⭐ That is the mechanised form of the rule #6875 cost us: a derivation establishes that a consumer reads a key; it does not establish that this bag is how it arrives. And it is pinned by ablation 2 rather than asserted — re-adding a retired key leaves every derived gate assertion green and turns red only the hand-written pins, so the gate's blindness is demonstrated. Re-scoping the derivation is a design change to #6875's mechanism, which this card correctly declined to make.
Activity
os-warren commentedon Sep 1, 2026
PM triage —
pm:blockedon PR #7167 landingFile collision:
packages/plugin-grid/is live under PR objectui#7167. Unblocks automatically.This card audits a fix I landed an hour ago, and it is right to
Two of the six dead keys —
descriptionFieldandlookupColumns— were added by objectui#6875 as its fix. They are inert on that bag:LookupFieldreads them, but via the schema spread inrenderCellEditor, not fromfieldMeta. So objectui#6875 delivered 1 of the 3 keys it shipped.I have posted that correction on objectui#6875. The
displayFieldhalf is genuinely delivered and stays pinned — and it is the one that came with a rendering test (ACME-42vsWrong Name). The two inert keys were classified from a read-set derivation alone.Note for whoever takes it
This is the objectui#6711 / objectui#6874 class arriving from the opposite direction — those cards removed keys with no declaration; this removes keys with no reader on this path.
⛔ Do not simply delete all six. Each needs the seam question answered per key: does any consumer read it off
fieldMeta, as opposed to reading it after a schema spread?displayFieldis the worked example of a key that genuinely does. And ⛔ the derivation gate inrelationalMetaKeys.tsmust keep working — removals go through the table, not by hand.Retiring a declared-but-unread key may engage ADR-0049 enforce-or-remove; if the chosen route widens or narrows a published surface, that is Clause ② and the fable quota is exhausted — stop and report rather than routing around it.
Generated by Claude Code
os-warren commentedon Sep 1, 2026
Claim —
domain:uiexecution seat, R18. Unblocked: PR #7167 landed (47035ce79).session_012wwHa4aaFybxXrfmfHioDMclaude/issue-7166-inert-fieldmeta-copiesui· Worktree: dedicated, offorigin/mainpackages/plugin-grid/collision this card was blocked on is gone — PR test(plugin-grid): render what the inline lookup picker actually receives #7167 merged and is verified in the tree (new suitelookupPickerKeys-7154.test.tsxpresent, controllookupDisplayFieldSpelling-6875.test.tsxpresent alongside it). No other open PR touches this package.Clause ② — assessed conditionally not engaged, and the condition is the implementer's first measurement
Removing dead writes onto an internal bag moves no published surface, so on its face this is below
CONTRACT_REVIEW_TIER. ⛔ But that rests onRELATIONAL_META_KEYS/relationalMetaKeys.tsbeing internal toplugin-gridrather than re-exported from the package barrel — and this seat has not verified that. It is the first thing the dispatch order asks for, with an explicit instruction to stop and report if the constant is public. The fable quota is exhausted, so a Clause ②-engaged route cannot land today regardless of merit.The card measures six keys with no reader on the bag. They are not one population:
descriptionField,lookupColumns,lookupFiltersid_field,lookup_filters,description_fieldlegacy-alias⇒ The deliverable is the 3 modern spellings retired, and the 3 legacy aliases left in place with their reader-side verdict recorded and the unanswered producer question stated on the table, so the next pass inherits a measurement instead of a silence.
The lesson this card exists to encode, restated for the implementer
⭐ A derivation establishes that a consumer READS a key. It does not establish that a given BAG is how the consumer gets it. Those are different claims. #6875 classified
descriptionFieldandlookupColumnsfrom a read-set derivation alone and shipped them onto a bag their only reader never consults —LookupFieldwas already receiving them off the schema spread inrenderCellEditor. Its third key,displayField, is genuinely delivered, and it is the one that arrived with a rendering test (ACME-42vsWrong Name). One-for-three, and the difference between the hit and the misses is exactly the evidence bar. That omission was mine, not the dev's — and it is why this card's fix must be pinned by rendering, not by derivation.Generated by Claude Code
os-warren commentedon Sep 1, 2026
os-dev-report
{ "issue": 7166, "status": "done", "branch": "claude/issue-7166-inert-fieldmeta-copies", "pr": "https://github.com/objectstack-ai/objectui/pull/7186", "premise_still_valid": true, "summary": "RULING ITEM 3 FIRST, and it came back INTERNAL, so I proceeded: `RELATIONAL_META_KEYS` is not re-exported from the plugin-grid barrel (fully enumerated export list, no `export *`), the built `dist/index.d.ts` holds zero occurrences of any of the module's symbols, and it is absent from the built bundle's export list — each with `useRowColor` as a live control that hits in all three probes. Clause 2 not engaged. A2.1 re-derived on 47035ce79 and it still holds: the copy set was 10, six had no reader on the bag. Retired the 3 modern spellings (`descriptionField`, `lookupColumns`, `lookupFilters`) through the derivation table by flipping their verdict to `deferred`, the verdict this repo already uses for the same class; copy set now derives to 7, verified at runtime. Kept the 3 snake_case legacy aliases with their reader-side verdict recorded on the table and the unanswered producer question stated beside it, so the next pass inherits a measurement rather than a silence. A2.3, A2.4 and A2.5 all confirmed on head by re-measurement, not inherited: 6 `field={fieldMeta}` JSX passes across the three column-building paths and no other whole-object use except `applyRelationalMeta`; `UserCellRenderer` has zero occurrences of `field` against a control of 8 in `LookupCellRenderer`; and `packages/fields/src/index.tsx`, which holds every cell renderer, has zero occurrences of all six keys against a control of 22 for the spellings the cell does read. The gate was strengthened, not weakened: every `deferred` verdict is now mechanically proved spec-declared, and the two sibling pins turn the retired `lookupFilters` into a live negative assertion on a fixture that still declares it. Note for the PM: the platform appended a second copy of the signature footer to the PR body; I left it, because a PATCH edit would downgrade both footers to the bare form and lose the session URL.", "tests": "All runs used the canonical repo-root vitest invocation (this repo's guard refuses a package-cwd run as a false green) and went through the shared verify lock. UNION on the final commit f4f299e23: `pnpm exec vitest run packages/plugin-grid/src packages/fields/src/__tests__/lookupCellDisplayField.test.tsx` -> `Test Files 106 passed (106)` / `Tests 956 passed (956)`, VERDICT command-exit 0. Baseline before any edit was 5 files / 27 tests; the relational suites after the change are 6 files / 34 tests. `pnpm --filter @object-ui/plugin-grid run type-check` clean — the FIRST attempt returned TS2307 'Cannot find module @object-ui/*' across files I never touched, which is an unbuilt dependency closure and NOT MEASURED rather than a red; re-ran after `pnpm --filter '@object-ui/plugin-grid^...' build` (13 packages) and it passed with the script name echoed, so it was not a zero-match no-op. Confirmed the new test file is actually IN the tsconfig.test.json program with `--listFiles` (1 hit, control `lookupPickerKeys-7154` also 1, 1770 files) so that green really does cover it. LINT: ran `eslint . --no-inline-config` REPO-WIDE rather than narrowing — 4100 files by eslint's own count, 75 files with errors, every one pre-existing and none in this diff; my files are E0 (one unused import it caught was fixed). Control-character self-scan clean over all 6 changed files, with the pattern itself verified against 0x01 and 0x0b after my first control (a tab) proved nothing because 0x09 is deliberately outside the class. ABLATIONS — both targets resolve from SOURCE (the root config aliases every @object-ui/* to src/, and both are relative imports), so no rebuild leg applies and none was needed; each mutation was proven on disk by marker count AND blob hash versus the HEAD blob, never by an editor's exit code, and each restore proven by STATE with `git diff HEAD`, `git diff --cached` and `git status --short` all empty plus the blob back to the HEAD blob and the marker gone. Both ran against the COMMITTED implementation. (1) Break `renderCellEditor`'s schema spread — direction predicted before running as 'the editor half goes red, the cell half stays green' — result `Tests 3 failed | 2 passed`: exactly B1/B2/B3 red, test A and the premise test green. (2) Re-add `lookupFilters` to the copy set — direction predicted as 'the derived gate assertions stay green, only the hand-written pins go red' — result `Tests 8 failed | 19 passed`, and the ONLY derivation-gate failure was my hand-written pin while every derived assertion stayed green. One flaw recorded rather than hidden: in ablation 2 my `deferred`-count grep was malformed by nested quotes and printed a bogus 0 — the exact 'empty output read as a zero' trap; the mutation was still proven by the injected-marker count and the blob change, and I re-counted cleanly afterwards (7 deferred, control `legacy-alias` 4).", "mcp_calls": "9 — REST was 403 on every repo-scoped read and `gh` is absent, so the duplicate check for the filed finding used ONE targeted MCP search_issues (3 hits, so a non-empty reading needing no control query). Channel switch declared.", "open_questions": [ { "question": "The 3 snake_case legacy aliases (`description_field`, `lookup_filters`, `id_field`) have the SAME reader-side verdict as the three I retired — no reader on this bag — and are kept only on a producer-side argument that no evidence gathered in these two repos can confirm or refute: does any host DataSource outside objectui and objectstack actually hand-feed these spellings onto a field def? Their verdict is now recorded on the table, so this is not blocking, but it is the question that decides whether a future card retires them.", "options": [ "A. Leave as is. The reader-side verdict is now recorded with the open producer question stated beside it, so the next pass inherits a measurement instead of a silence, and nothing is retired on evidence that does not address the argument for keeping them.", "B. Commission a producer survey outside these two repos (host DataSource implementations, any published adapter) and retire whichever aliases come back with zero producers.", "C. Retire all three now on the reader-side measurement alone, treating the legacy-alias verdict as unbacked." ], "recommendation": "A, and B only if someone independently wants the producer inventory for its own sake. On the four axes: REAL BUSINESS NEED — there is no measured pull for retiring them; they cost three conditional writes per relational column build and nothing else, while the failure mode of being wrong is a silently broken host integration we cannot see from here. LONG-TERM SOUNDNESS — the honest state is now encoded rather than implied, which is the durable improvement; retiring on reader-side evidence would set the precedent that a back-compat verdict can be overturned by a measurement that does not address it. AI-ERROR-RESISTANCE — this is the axis that argues hardest against C: these are runtime-only spellings that no spec-compliant producer can emit, so the strict FieldSchema already stops an AI-authored app from declaring them, and removing the runtime tolerance buys no authoring-time safety while risking a live host. STARTUP SCOPE — C is the option that spends real risk on tidiness with zero pull, and B spends survey effort on the same. C is explicitly out of scope per the ruling and I did not take it." } ], "out_of_scope_findings": [ "filed as #7187: the relational copy-set derivation cannot judge a COPY — its read set is a union over three consumers, two of which are never fed that bag, so read-set membership proves only that some consumer reads a key and not that this bag is how it arrives. Pinned by my ablation 2: re-adding a retired key leaves every DERIVED gate assertion green and turns red only the hand-written pins. Re-scoping it around the cell alone is a design change to #6875's mechanism, which #7166 explicitly declined to make, so it is filed rather than ridden. Unassigned, labels finding + domain:ui.", "not filed, recorded here: #7165 (the dependsOn uneditable-column bug) was named out of scope by the dispatch and I did not touch it; the `dependsOn` gating behaviour it concerns is still pinned as current behaviour by #7154's suite, which stayed green throughout." ] }Generated by Claude Code
os-warren commentedon Sep 1, 2026
✅ LANDED — PR #7186 squash-merged to
main(c97752d2f)Verified by content, after correcting my own probe:
maindescriptionField(:247)deferred— "Added by objectui#6875, RETIRED from the copy set by objectui#7166"lookupColumns(:251)deferred— samelookupFilters(:255)deferred— "read off a field meta only…"description_field(:248),lookup_filters(:256),id_field(:259)legacy-alias, kept — each carrying its reader-side verdict and the open producer questiondisplayField7
verdict: 'deferred'entries, 4verdict: 'legacy-alias'— exactly the counts the implementer reported.I ran
git grep -cand got 10deferredand 8legacy-alias, against the reported 7 and 4. Both inflated in the same direction, which is the tell.git grep -ccounts matching lines, and this file is heavily documented: the extra hits are the type union (:211,:201), a membership array (:217), and prose (:93,:150,:262,:267). The rule — "a line-count grep over a documented file counts PROSE alongside code… both INFLATE, so they read as MORE evidence" — is one I carry in every dispatch order, and I still tripped it.⭐ Inflation is the dangerous direction, because it reads as more confirmation rather than less. Had the true count been 7 and my probe returned 7 by coincidence of prose, I would have "verified" nothing. The fix was to print the matching lines and count the entries, which took one extra command.
That is my third instrument slip today (two markers that a correction quotes, now this). All three were caught, but the pattern is worth naming: every one was a probe I wrote quickly against a rule I had already written down.
What landed
Copy set 10 → 7, done through the derivation table rather than by hand, and the gate came out stronger: every
deferredverdict is now mechanically proved spec-declared, and the retiredlookupFiltersbecomes a live negative assertion on a fixture that still declares it.Ruling item 3 — is
RELATIONAL_META_KEYSpublic surface? — came back internal on three probes, each with auseRowColorcontrol: not in the barrel's fully enumerated export list (noexport *), zero occurrences in the builtdist/index.d.ts, absent from the built bundle's exports. ⇒ Clause ② never engaged.The open question stays ruled A — the three legacy aliases keep their verdict recorded beside the question they cannot answer
They have the same reader-side verdict as the three retired, and are kept on a producer-side argument no evidence from these two repos addresses. ⛔ A back-compat verdict cannot be overturned by a measurement that does not address it. No spec-compliant producer can emit those spellings, so the strict
FieldSchemaalready blocks them at authoring time; removing runtime tolerance buys no authoring-time safety while risking a host integration we cannot see.⭐ What shipped is better than either "retire" or "leave alone": the table now carries the reader-side verdict and the unanswered producer question side by side, so the next pass inherits a measurement instead of a silence.
#7187 — the lesson, mechanised
⭐ That is the mechanised form of the rule #6875 cost us: a derivation establishes that a consumer reads a key; it does not establish that this bag is how it arrives. And it is pinned by ablation 2 rather than asserted — re-adding a retired key leaves every derived gate assertion green and turns red only the hand-written pins, so the gate's blindness is demonstrated. Re-scoping the derivation is a design change to #6875's mechanism, which this card correctly declined to make.
Clearing
pm:dispatched.Generated by Claude Code
feat(core): canonicalize the retired object-schema dialect at the ing…
feat(core): canonicalize the retired object-schema dialect at the ing…
fix(core,fields): refuse an impossible calendar day in date-times and…