fix(plugin-grid): scope the relational copy-set derivation to the cell that is fed the bag - #7584
Merged
os-project-manager merged 1 commit intoSep 4, 2026
Conversation
…l that is fed the bag
The derivation gate extracted a read set from three consumers and collapsed it
into a UNION, then licensed a copy-set entry by membership in it. Only one of
the three — `LookupCellRenderer` — is handed the bag `applyRelationalMeta`
writes; `ObjectGrid.renderCellEditor` feeds the two inline editor widgets
`{ name, ...fieldDef }` off the object schema instead. So membership meant
"some consumer reads this key" and never "this bag is how that consumer gets
it", while a copy-set entry asserts the second. That conflation shipped two
wrong verdicts (objectui#6875) which objectui#7166 then had to undo, and the
gate was green through both.
- Record the reader axis PER CONSUMER on every entry (`readers`), checked
against that consumer's own source in both directions.
- Derive the copy set from `CONSUMERS_FED_THIS_BAG` — the cell alone — with one
self-naming exception (`copiedWithoutCellReader`) confined to keys
`FieldSchema` does not declare, so no authorable key can take it.
- Assert that bound on `ObjectGrid.tsx` itself: `renderCellEditor` must spread
the schema def and must never name `fieldMeta`.
- Remove the `deferred` verdict — it stated a fact that is now measured. Its
seven keys are `spec` and stay off the copy set because no consumer fed the
bag reads them.
The copy set is unchanged: the same seven keys, in the same order.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-project-manager
marked this pull request as ready for review
September 4, 2026 02:29
os-project-manager
deleted the
claude/issue-7187-relational-copy-set-derivation
branch
September 4, 2026 02:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7187
Clause-②: no — the whole diff is two files inside
packages/plugin-grid/srcplus one changeset.relationalMetaKeys.tsis not re-exported from the package entry (src/index.tsx, thevite.config.tslib entry), so none ofRELATIONAL_META_READ_SET,RelationalMetaVerdict,RelationalMetaEntryorRELATIONAL_META_KEYSis indist/index.d.ts. Measured: zero hits forrelationalMetaKeys|RELATIONAL_META|RelationalMetainsrc/index.tsx, lit controlObjectGridin the same query shape returns 5. The copy set itself is byte-identical — the same seven keys, in the same order — so no runtime surface moves either.The defect
The gate re-extracted a read set from three consumers and collapsed it into a union, then licensed a copy-set entry by membership in it. Only one of the three is fed the bag:
applyRelationalMetawrites onto thefieldMetathatgenerateColumnshands toCellRenderer, whileObjectGrid.renderCellEditorbuilds the two inline editor widgets' props from the object schema ({ name: ctx.column.accessorKey, ...fieldDef }). So membership meant "some consumer reads this key" and never "this bag is how that consumer gets it" — and a copy-set entry asserts the second.Measured on this base, which is why the union could not judge a copy:
index.tsx#LookupCellRendererwidgets/LookupField.tsxwidgets/UserField.tsx19 of the 25 union members are read by no consumer that is fed the bag.
What landed
readers, and the gate checks each declared list against that consumer's own source in both directions — a new spelling in a chain is unclassified, a hand-widened declaration is an orphan, both red.CONSUMERS_FED_THIS_BAG— the cell alone — on two conditions: the verdict licenses a copy at all (producer half), and a consumer fed the bag reads the key (reader half). The one deliberate exception, the three snake_case spellings kept on an unanswered producer-side question, must name itself per key (copiedWithoutCellReader), and the gate confines that exit to keysFieldSchemadoes not declare — so no authorable key can take it. This is not a widening of the union; it is a strict narrowing plus an exception that has to state its own reason.renderCellEditorinObjectGrid.tsxand requires it to spread the schema def and to never namefieldMeta. Three positive controls bound the region (...fieldDef,objectSchema,FieldEditWidget) and a lit control proves the instrument can see the identifier (fieldMetaoccurs more than 10 times in the file, 0 times in that region).The three open questions, decided
1. Split the extractor into cell / editor sets? Yes — but the split lands in the table, not only in the extractor. The extractor already computed per-consumer sets; the defect was that only the union crossed into the table. Making the per-consumer fact cross into the table (
readers) is what lets the copy set be derived from the cell alone, and it is what makes the split checkable in both directions instead of being an internal detail of one function.2. Does
deferredsurvive? No. It meant "spec-declared and read only by an editor widget" — a reader-axis fact, hand-written, inside an enum that otherwise records the producer axis. Both halves are now mechanically known: spec-declaredness fromFieldSchema, editor-only-ness from the extractor. A hand-written verdict restating two measured facts is precisely the shape this table's history warns about — read-set membership was taken for a licence to copy and had to be undone. The seven keys arespecnow, which is what they are, and they stay off the copy set because no consumer fed the bag reads them.handled-elsewhereis kept: it records a decision (optionsis written bytranslateOptions; a raw copy would undo that) that neither axis can derive.3. How is
UserField's forwarding restated? As a checked result, withUserFieldstill swept. The gate assertsUserField's extracted set is a subset ofLookupField's and names any keyUserFieldreads thatLookupFielddoes not — the false zero the sweep exists to catch. Dropping it because "it forwards anyway" is exactly the assumption that would hide such a key. Under the split the subset relation is no longer load-bearing for copying, because both are editors and neither licenses a copy either way; that is asserted too.None of the three moves a published surface, so none of them became a stop.
Re-running the load-bearing ablation
The card's central measurement is nine rounds old and triage did not re-run it. Re-run on
ccb3ad78a62bbefore any design work: it reproduces exactly. Re-addinglookupFiltersto the copy set (deferredback tospec), across the fourrelationalMetaCopySet*suites:8 failed | 19 passedis the card's number to the digit. And the only failure inside the derivation gate was the hand-written pin, exactly as filed — the nine other tests in that file stayed green, including "no unclassified key", "no orphan", the spec proofs, and "copy set equals the copied verdicts". No finding to report; the reading was still good.Mutation proven on disk before any result was read (anchor
lookupFilters: { verdict: 'deferred'1 to 0, injected anchor 0 to 1, blob000988fatode6af77c). Restore proven by state undertrap ... EXIT INT TERMwith absolute paths, viagit checkout HEAD -- ABS_PATH: blob back to000988faandgit diff HEADempty.Discrimination — the gate now moves on the FACT
Four legs, each mutating a fact rather than an assertion, each with the mutation proven on disk (anchored fixed-string counts plus
git hash-objectagainst the HEAD blob) before any result was read, and each restore proven by state (git diff HEADempty and on-disk blob equal to the HEAD blob) undertrap ... EXIT INT TERMwith absolute paths. Baseline at this commit:Tests 12 passed (12).lookupFiltersclaimscopiedWithoutCellReaderwhilespec2 failed | 10 passedlegacy-aliaslookupFilterstolegacy-aliasand claims the exit3 failed | 9 passedlegacy-aliasnon-authorability; the exit's own spec checklookupFilterscast read added toLookupCellRendererinpackages/fields/src/index.tsx3 failed | 9 passedreaderstoALL_THREEwith no source support3 failed | 9 passedA2 and A3 are the two routes by which the three retired keys could be re-added; A4 is the fact the derivation now rests on; A5 is the attempt to fake that fact in the table.
The copy-set assertion is deliberately derived from the extracted cell set rather than from the table's
readers, soRELATIONAL_META_KEYSand the gate's expectation reach the same list by two independent routes and a hand-editedreaderscannot carry both. That is why A5 turns it red.The hand-written pin is now redundant as a guard — shown, not claimed
In every one of the four legs at least one derived assertion went red on its own; deleting the named-key pin would leave the gate red in all four. That answers the card's condition directly: the retirement is no longer held by the pin.
It is kept anyway, restated. It no longer says "the derivation cannot enforce that" — it now asserts the derived fact for the three keys by name (the cell does not read them, and their editor readers are non-empty), so a regression is reported by name instead of only as an equality mismatch.
relationalMetaCopySet-7166.test.tsxis untouched and still renders both halves.Verification
All runs from the repo root, exit codes captured by redirect-then-read (never through a pipe), heavy runs serialized through the container's shared verify lock. Union re-run after the final commit, at
c70a04570:pnpm exec vitest runover the fourrelationalMetaCopySet*suites pluslookupPickerKeys-7154,lookupDisplayFieldSpelling-6875, andscripts/__tests__/one-authority-per-exported-name-6273(the new exported names):Test Files 7 passed (7),Tests 47 passed (47). Baseline onccb3ad78a62bfor the six relational suites wasTests 34 passed (34); it is 36 now, the gate having grown from 10 tests to 12. The five behavioural suites are unchanged and green, which is the copy set being byte-identical.pnpm --filter @object-ui/plugin-grid type-check— exit 0,tsc --noEmit && tsc -p tsconfig.test.json. Both changed files are in the checked set, not assumed:--listFilesreturns 1 hit each out of 1799 listed, so "typecheck is clean" is a statement about them.node scripts/check-changeset-presence.mjs— exit 0: "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate."node scripts/check-control-bytes.mjs— exit 0: "check-control-bytes: OK (scanned 6245 tracked text file(s); skipped 85 binary)." Plus a self-scan of the three changed files for raw control bytes: no hits.node scripts/check-changeset-fixed.mjs/check-changeset-no-major.mjs/check-changeset-overwrite.mjs/check-lint-coverage.mjs— all exit 0 ("No changeset declares amajorbump.", "No pre-existing changeset was modified or deleted.", "lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).").eslint .ran over its own full population — 4273 files by its--format jsoncount — with--no-inline-config, the stricter form. My two files: 0 errors. The 77 files carrying errors are all untouched by this diff and all reportreact-hooks/static-components/no-consolesuppressed by inline comments the strict form disables. With the real config the two files give 0 errors and 3no-explicit-anywarnings, all on lines that predate this change ((FieldSchema as any).shape, andapplyRelationalMeta's twoRecordof string to any parameters). This repo's eslint is not type-aware (tseslint.configs.recommended, noproject/projectServiceineslint.config.js), so this diff cannot move any untouched file's verdict.Narrowings declared
RELATIONAL_META_*outside these files; a repo-wide grep finds none — the only other references areplugin-dashboard's prose comments and its own independentCELL_RELATIONAL_META_KEYSliteral, neither of which imports from here. CI runs the full farm regardless.package.jsonand the workflows rather than fromscripts/pm/dispatch-gates.mjs— that script lives only inobjectstackand answers about the tree it sits in, so pointing it atobjectuipaths would return a well-formed wrong list. Ran the changeset family, control-bytes, lint-coverage, lint, type-check, and the exported-name authority gate (implicated by the two new exported names). No gate underscripts/was edited, so no gate's own test suite is owed.dist. No rebuild is owed: the repo-rootvitest.config.mtsaliases@object-ui/fieldsand@object-ui/plugin-gridto theirsrcdirectories (lines 417-419), and the mutated module is reached by a relative import (../relationalMetaKeys), so noexports-to-distresolution is in the path. The dist-preflight failure mode — a mutation that never reaches the running code and leaves the ablation silently green — cannot occur here.What is NOT measured
Whether any host
DataSourceoutside these two repos feedsfieldMetato something other thanCellRenderer. Every sweep behind this boundspackages/andapps/in this repo, andCONSUMERS_FED_THIS_BAGis a statement about this repo's own wiring. The producer-side question the three snake_case spellings rest on is likewise untouched — it needs a producer survey, not another reader sweep, andcopiedWithoutCellReadernow records that per key instead of leaving it to a docblock.Generated by Claude Code