Repository navigation
Pin the retired-field-key tombstone registry as check-designer-field-key-parity.mjs's single source — otherwise the registry is a convention, and conventions drift #6699
Description
Activity
- addeddomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repoobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
on Aug 28, 2026 Unlock scan (devx@objectui seat, session
session_01BGMDbrVa8JjZcCQ7DWYH1b, round R40):Blocked-by: #6527is satisfied — #6527 closed as completed on 2026-08-28 via merged PR #6627 (the tombstone registry). Labelpm:blocked→pm:queuein this write.Premise re-verified on
origin/main1c4e2daac:scripts/check-designer-field-key-parity.mjsstill carries no import from the registry — the only mentions of the tombstones are its header prose (lines 31–35) and a comment near line 306 — so the gate is not yet derived from the registry and the card stands.Serial note for dispatch: PR #7272 (#6606) is in the merge queue right now and rewrites the same script (adds
ORACLE_SPECIFIERSand two permissionPAYLOAD_SHAPES). This card dispatches only after #7272 is MERGED, against the ref that carries it.
Generated by Claude Code
Claim: PM loop round R40 (backfill after #7046 returned; batch=5)
Session:session_01BGMDbrVa8JjZcCQ7DWYH1b
Branch:claude/issue-6699-registry-single-source
Worktree:objectui-issue-6699
Domain:domain:devx
File surface:scripts/check-designer-field-key-parity.mjsandscripts/__tests__/check-designer-field-key-parity.test.tsonly. ⛔ Notpackages/types/src/internal/retired-field-keys.tsor its test (the registry is the source, this card makes the gate read it); ⛔ not the three strip sites (MetadataService.ts,object-fields-io.ts,MetadataFieldsPage.tsx); ⛔ notpermission-slice.ts. Stop on breach; explain in the report.
Container & model: M (gate derivation with a measured shape decision and mandatory ablation),mode:subagent,model: opus. Tier is this seat's explicit decision (nodispatch-gates.mjsin objectui).
Clause-②: no
Serial constraints cleared: the serial condition recorded in the unlock comment is met — PR #7272 (#6606) MERGED at 03:12Z asa64bd9c37; the gate now carriesORACLE_SPECIFIERS(line 404),ORACLES(412), eightPAYLOAD_SHAPES(232) andKNOWN_UNPARSEABLE_KEYS(337), and its test carries 45 cases with theSHAPE_WITNESStable. The registry onorigin/main:packages/types/src/internal/retired-field-keys.ts—RETIRED_FIELD_KEY_SITES(3 sites),RETIRED_FIELD_KEY_TOMBSTONES(5 keys:indexed,referenceTo,formula,isSystem,sortOrder, each with a per-sitesitesrecord),retiredFieldKeysFor(site); published as the./internal/retired-field-keyssubpath of@object-ui/types(dist). No in-flight PR in either lane touches the two files in this surface; batch siblings (#6640 test infra · #6600 doc gates · #7183 vitestdistproject · #7044 stylesheet banner pin) are disjoint by construction.
Premise note for the dev (measured by the seat, to be re-measured): on today'smainthe gate carries no retired-key set of its own —KNOWN_UNPARSEABLE_KEYSis a different ledger (declared keys the oracle refuses, card-anchored, with theformulaandsortOrderrows already resolved and removed) and the header'sRETIRED_FIELD_KEYSsentence is history. So "derive what it checks from the registry" needs a concrete, non-vacuous shape; the dispatch order names the candidate and requires ablation to prove it has teeth. Registry to be read from the tracked source via the TypeScript AST, the wayPAYLOAD_SHAPESare read, so the gate gains no build precondition; an unparseable registry is anExtractionError, never a pass.
Generated by Claude Code
- added a commit that references this issue
on Sep 2, 2026 Claim (os-dev, dispatched by the
domain:devx@ objectui PM seat, round R40)- Session:
session_01BGMDbrVa8JjZcCQ7DWYH1b - Branch:
claude/issue-6699-registry-single-source(pushed empty first as the write-access probe, before any edit) - Worktree:
../objectui-issue-6699, baseeb33a8d4c— carries PR feat(gates): cover the permission authoring shapes in designer-field-key-parity #7272 (ORACLE_SPECIFIERSat line 404 of the gate), the serial condition the unlock comment recorded - File surface:
scripts/check-designer-field-key-parity.mjsandscripts/__tests__/check-designer-field-key-parity.test.tsonly. Not the registry or its test, not the three strip sites, notpermission-slice.ts.
Premise re-measured on this base, and it holds: the gate carries no retired-key set of its own —
KNOWN_UNPARSEABLE_KEYSis{}at run time and the only registry mentions in the file are prose. So the deliverable is a concrete derivation proven by ablation, not a copy to delete.Channel note:
ghis absent on this seat and repo-scoped REST reads return 403, so the writes on this card go through MCP; call count is reported in the run report.
Generated by Claude Code
- Session:
os-dev-report
{ "issue": 6699, "status": "done", "branch": "claude/issue-6699-registry-single-source", "pr": "https://github.com/objectstack-ai/objectui/pull/7290", "premise_still_valid": true, "summary": "Re-measured the premise on base eb33a8d4c and it holds: the gate carried NO retired-key set of its own (KNOWN_UNPARSEABLE_KEYS is {} at run time; the only registry mentions were prose), so the deliverable is a concrete derivation rather than a copy to delete. check-designer-field-key-parity.mjs now reads the objectui#6527 registry off the TRACKED source packages/types/src/internal/retired-field-keys.ts via the TypeScript AST (the same walk PAYLOAD_SHAPES use, so no build precondition) and derives one rule with teeth: a retirement is NOT waivable at a site that strips the key -- where a wire shape IS a registry strip site (new explicit `stripSite` column on every PAYLOAD_SHAPES entry) and the tombstone marks that site true, a KNOWN_UNPARSEABLE_KEYS row can no longer quiet it; the violation cites the registry entry (key, retiring card, per-site columns) instead of inviting a new ledger row, and the row itself is reported stale with the registry's reason. The per-site asymmetry survives as DATA and is measured, not asserted: inForce is per (key, site), the citation reproduces the tombstone's columns verbatim on BOTH the stripped and NOT-stripped sides, and the read door is recorded in SITES_WITH_NO_DECLARED_SHAPE (it declares no payload shape at all -- coverage note 3) so the gate asserts nothing there and cannot drift into implying formula is stripped at the read door (objectui#6526 option B). sortOrder is treated exactly as the registry states it: one key, one ledger row, two sites, two different verdicts. Columns consumed: key, retiredBy, sites. specEquivalent and defensive are deliberately NOT extracted (rationale in the header and PR body). Extraction failure is an ExtractionError in every direction, including two new link checks: a shape naming a stripSite the registry does not declare, and a registry site the gate accounts for nowhere -- the latter is what makes the registry the single SOURCE rather than a single copy. Route (ii) from the dispatch (registry/oracle agreement) was REJECTED as a duplicate: the registry's own test already pins it per key with unrecognized_keys and a positive control (lines 104-113); the gate leans on route (i) and says so. File surface respected exactly: only the gate and its self-test were edited (863 additions, 10 deletions, 2 files); the registry, its test, the three strip sites and permission-slice.ts were never touched. Self-test went 45 -> 62 cases.", "tests": "All at 2c9a1ec26 (the final commit; re-run after it, exit codes captured by redirect-then-$?, never through a pipe). `pnpm exec vitest run scripts/__tests__/check-designer-field-key-parity.test.ts scripts/__tests__/zod-wrapper-keys.shared.test.ts --maxWorkers=2` exit 0 -> 'Test Files 2 passed (2) / Tests 69 passed (69)'. `pnpm check:designer-field-key-parity` exit 0 -> 'designer-field-key-parity: OK' (same verdict line before the change at eb33a8d4c, exit 0; the run gained a 'Retired-key registry: ... 5 tombstones over 3 sites' block plus per-site map, and now annotates isSystem/referenceTo on the UI model with their retiring cards while the object-level sortOrder correctly carries none). `pnpm type-check:scripts` exit 0 (clean tsc). `pnpm check:control-bytes` exit 0 -> 'check-control-bytes: OK (scanned 6008 tracked text file(s); skipped 85 binary).' `pnpm check:entry-guard` exit 0 -> '58 scripts/ file(s) -- no entry guard outside the baseline; 52 export bindings, 52 of them inert on import'. `pnpm check:esm-specifiers` exit 0 -> 'no un-ledgered package emits an extensionless relative specifier'. `node scripts/check-changeset-presence.mjs` exit 0 -> 'No source or published contract of a released package changed in this range, so no changeset is owed.' (2 files changed, 0 published source) -- scripts-only, so no changeset and (per the standard clause) no skip-changeset label applied in this repo. ABLATION, six legs, mandatory for every new assertion: each mutates, PROVES the mutation on disk by text count AND by `git hash-object` differing from the HEAD blob, measures, restores, then proves the restore by blob hash EQUAL to HEAD and an empty `git diff HEAD` -- never by an exit code; the harness carries trap '<restore>' EXIT INT TERM with absolute paths. REBUILD: none is owed or possible on this surface and that is stated rather than skipped -- the gate is a plain .mjs that node executes from source and that vitest loads through its SSR transform from source; nothing here resolves through a package `exports` map to a dist/, so there is no dist leg that could carry a stale mutation into a later measurement (the ablation-dist hazard does not apply). A1 remove the waiver ban -> RED, 2 failed/60 passed ('expected [] to deeply equal [formula:true]' and '[sortOrder:true]'), blob 3b2062f3 vs HEAD 93a13cf5. A2 extraction returns no tombstones -> RED, 6 failed/56 passed, the non-vacuity floor first ('expected [] to deeply equal ArrayContaining'), blob ed705770. A3 FLATTEN the registry (inForce ignores the per-site column) -> RED, 2 failed: the false-column case starts refusing the waiver and inForce flips ('expected true to be false'), blob 5fdf7abc -- this is the anti-flattening proof. A4 drop the read door from SITES_WITH_NO_DECLARED_SHAPE -> gate exit 1 'EXTRACTION FAILED ... declares strip site(s) metadataAdminFieldsReadDoor this gate accounts for nowhere' plus 30 test failures, blob bb3c5cc7. A5 cross the site map (swap the two stripSite values) -> RED, 'FieldMetadataPayload is mapped to the wrong site', order on disk verified swapped, blob deb1e32f. A6 (the dispatch's named ablation) re-declare formula on a COPY of MetadataService.ts plus a matching ledger row -- the fenced original is never touched -> RED naming the tombstone: waiverRefused true, retiredBy objectui#6043, inForce true, strippedAt [metadataServiceCarryOver, metadataFieldsPageCarryOver], notStrippedAt [metadataAdminFieldsReadDoor], stale row 'the registry retires it (objectui#6043) and metadataServiceCarryOver strips it -- a retirement cannot be waived by a ledger row'; copy blob 57277b29 vs original HEAD blob e41a23fa, original blob after the run e41a23fa (unchanged) and `git diff HEAD` empty. Every restore leg printed blob==HEAD and an empty git diff HEAD; the final tree is clean. DECLARED NARROWING -- lint: repo-wide `pnpm lint` was NOT run (a repo scan CI owns, and not in this card's gate list); `eslint --no-inline-config` on the two changed files gave 0 errors / 0 warnings, with all three narrowing measurements: (1) universe read from ESLint's OWN config via its API (isPathIgnored + calculateConfigForFile over every tracked file) = 4024 of 6093 tracked files carry rules, 1964 ignored by eslint's own config, 105 matching no rule-carrying entry; (2) files actually linted, from --format json = 2; (3) eslint.config.js declares no parserOptions.project and no projectService, so type-aware linting is off and no untouched file's verdict can move with this diff -- the only cross-file rule configured is no-restricted-imports (per-file), and the only importer of the gate is its own test, which was linted. No console build and no heavy verify were needed, so the shared verify lock was not taken.", "mcp_calls": "7 -- issue_read(get), issue_read(get_comments), add_issue_comment(claim), create_pull_request, pull_request_read(body read-back), add_issue_comment(this report), issue_read(get_comments, report read-back). gh is absent on this seat and repo-scoped REST returns 403, so every GitHub write went through MCP; no list/search call was made.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
ACCEPT — PR #7290 (round R40, PM review).
Checked against the card, the claim and the dispatch rulings:
- File surface exactly as claimed:
scripts/check-designer-field-key-parity.mjsand its self-test (2 files, +863/−10); the ten removed lines are the report wording the derivation replaces. The registry, its test, the three strip sites andpermission-slice.tsare untouched — A6 mutated a copy, and the original's blob is shown unchanged. - Premise re-measured on
eb33a8d4cand stated: the gate held no retired-key set (KNOWN_UNPARSEABLE_KEYSis{}at run time), so the deliverable is a derivation, not a deletion. Ruling 4 satisfied: the derivation is non-vacuous by ablation. - Ruling 2 held: the registry is read from the tracked source via the TypeScript AST (
readRetiredKeyRegistry), no build precondition; every malformed or missing shape is anExtractionError, and the two link checks make it a source rather than a copy (astripSitethe registry does not declare; a registry site the gate accounts for nowhere). - Ruling 3 held and is measured:
inForceis per (key, site); the read door is recorded inSITES_WITH_NO_DECLARED_SHAPEso the gate asserts nothing there and cannot implyformulais stripped at the read door (app-shell: a draft carrying the retiredformulakey still round-trips to a hard 422, and the two ways out trade against each other #6526 option B);sortOrdergets two different verdicts at its two sites; A3 (flatten) goes red.specEquivalentanddefensivedeliberately not consumed, with the reason (Field designer offers aformulacontrol for formula fields — a key FieldSchema rejects by name (spec spells itexpression), so saving a formula field hard-blocks the object #6043's refused rename) in the header — agreed. - Route (ii) rejected as a duplicate of the registry test's per-key
unrecognized_keyspin — correct; route (i) ships and says so. - Ablation: six legs, each with on-disk proof by count and blob, restore by blob equality and empty diff, under a trap. A6 is the dispatch's named ablation and it names the tombstone, the retiring card and both site columns.
- Gates: targeted vitest 69/69 (self-test 45 → 62), gate verdict
OKbefore and after with the new registry block in the log, type-check, control-bytes, entry-guard, esm-specifiers, changeset-presence (none owed, scripts-only). Declared narrowing on lint is measured through ESLint's own config; no type-aware rules, so accepted.
Landing: flip to ready + auto-merge when every check on
2c9a1ec26is success or skipped. Card closes on merge viaFixes #6699.
Generated by Claude Code
- File surface exactly as claimed:
- added a commit that references this issue
on Sep 9, 2026
Filed unlabelled by the
domain:ui@ objectui execution seat, session8ca04858-ea8e-5b85-9182-de59aa49e00c, as the cross-lane half of #6527. ⛔domain:*,typeand grading are triage's to produce — the routing below is a suggestion with its reasoning, not a label.Suggested routing:
domain:devx. The work lands inscripts/check-designer-field-key-parity.mjs, and gate-classscripts/is devx's surface — the same routing #6606 got for the same script. Thedomain:uiseat is fenced out of it, which is why this is a separate card rather than a rider on #6527.Blocked-by: #6527
Why this exists
The maintainer ruled option B on #6527 (2026-08-28): the three drifted retired-field-key lists on the designer seam converge into one tombstone registry, reached through a dedicated internal subpath of
@object-ui/types. That registry is landing in PR #6627.⭐ But a registry with no gate pinning it is a convention, and conventions drift. That is not a hypothetical here — it is the literal history this card's parent documents. From
check-designer-field-key-parity.mjs's own header:Three lists existed because nothing mechanically required them to agree. #6527 makes them agree today. This card is what keeps them agreeing.
What is being asked
Make the registry the gate's single source:
check-designer-field-key-parity.mjsshould derive what it checks from the tombstone registry rather than carrying its own copy of the key set, so that adding or retiring a key in one place cannot leave the gate asserting the old shape.formulais stripped by the two WRITE-side lists and ⛔ must NOT be stripped by the READ door — ruled on app-shell: a draft carrying the retiredformulakey still round-trips to a hard 422, and the two ways out trade against each other #6526 (option B, closed via PR fix(app-shell): name the field and the Formula (CEL) editor in the retiredformula422 diagnostic #6624).ObjectFieldInspectorseeds its CEL editor fromdef.expression ?? def.formulaand the first edit migrates it; stripping it at the read door destroys authored expression text.sortOrderis present only atMetadataService, recorded as explicitly defensive rather than measured — objectui#6045 measured that no writer on this tree ever populated a field-level one.⇒ A gate that flattens the registry into one key set would assert something the registry deliberately does not say. ⛔ Whatever shape this takes, it must be able to express "retired at these sites, not at those."
Prior art to read before starting
PR #6627 already pins the asymmetry at three layers on the product side, including a test that goes red if
formulais ever added to the read door's applicable set. ⭐ That pin is #6526's ruling made mechanical — this card is the same idea one level out, at the gate.Provenance
domain:uiseat under the linkage-chore rule — which is this card.