Skip to content

[finding] The tombstone judge converged, but the Zod-internals READERS around it are still hand-copied in ~10 test files — four of them verbatim #5872

Description

@os-zhuang

⚠️ MEASURED STATE — 2026-08-31. Read this before the original filing below.

The census in the original body is STALE and has now caused one wasted full dispatch. Do not work from it.

Class (1) — ✅ DONE, landed

PR #6047 MERGED 2026-08-24T15:00:26Z (merged: true, head 3b010122f; squash subject test(test-support): confine the spec enum-vocabulary reader to one module (#5872 class 1)). The four verbatim enum-options copies are consolidated onto packages/test-support/src/spec-enum-options.ts (shapeEnumOptions), and all three consuming packages already declare the devDependencies edge.

⛔ The 2026-08-25 dead-claim reclaim on this card was a FALSE NEGATIVE. It recorded "no merged commit referencing this card across 711 commits of main" 17 hours after the merge it failed to see, and a 2026-08-31 re-dispatch acted on it. Root cause, measured: main was re-rooted on 2026-08-30 — HEAD history is 28 commits ending at a parentless root (335523906) — so git log over main can no longer see any pre-re-root merge, and PR #6047's head is an ancestor of nothing.

⚠️ On this repo, a "N commits of main" scan is not evidence of absence. Use blob identity against the fetched PR ref (git rev-parse <ref>:<path> per changed file), or simply read the PR's merged flag. Confirmation for #6047: of its 13 changed files, 11 byte-identical to e3d117ae1, 0 absent; the 2 that differ moved only under later unrelated work (#6355's timeScale-alias retirement inside timeline-scale-spec-parity.test.ts's resolveTimelineScale block, and pnpm-lock.yaml).

Classes (2)–(4) — what is actually left

  • Class (2) — array-element unwrapping, three DIFFERENT spellings for one question. ⛔ Not mechanical: a shared reader must pick a behaviour where three copies disagree, which is a genuine verdict-flip risk. Deserves its own round, measured before conversion.
  • Class (3) — the wrapper-key literal list. ⛔ BLOCKED, needs a maintainer ruling — see the decision card linked below. It has grown out of tests into .mjs CI gate scripts, and @object-ui/test-support is private: true with an exports map pointing at TypeScript source, so a plain node scripts/check-*.mjs cannot import it. The class-(1) pattern is unavailable across that language boundary. ⛔ Do not "just do the TypeScript sites" — that ships the exact split this card family exists to prevent, with a language boundary making it permanent.
  • Class (4) — single-site unwraps; has grown since filing, one site now sits in packages/types where live sibling work lands.

⚠️ Classes (3) and (4) have both roughly doubled since the original filing. Re-derive the census before writing anything; a premise_still_valid: false with no PR is a good run on this card.

⭐ Hazard any future round inherits — measured, not theorised

On an empty vocabulary, the "spec accepts a name we do not implement" half of three of the four parity gates stays GREEN — it filters an empty list. Only a non-vacuity assertion catches it. spec-enum-options.ts's docblock makes that assertion an explicit duty of every consumer; any new shared reader must do the same.

⭐ Instrument note for whoever measures next

Counts are satisfiable by a swap. Fingerprint (file, sorted test names) with sha256 instead: on the 2026-08-31 re-measurement timeline-scale's count held at 8 while two test names were swapped by #6355 — identical counts, moved fingerprint. That is the failure counts cannot see.

Related

Uncatalogued sibling family filed separately: 15 files cast a schema to { options?: readonly string[] } and read .options straight off a top-level enum node — no shape resolution, no wrapper walk, but the same quiet-permissive hazard when the cast yields undefined.


Original filing (2026-08-23) — ⚠️ census superseded by the block above

Found while implementing #4947 (PR #5871), which retired the last two local copies of
the ADR-0087 D2 tombstone criterion onto @object-ui/test-support. Filed
unassigned and not fixed on that branch — #4947's scope is the tombstone verdict
only, and this is a different judgement one level out.

No false green or false red today. Every copy below appears correct against the
installed pin. This is the #4434 class caught before the drift, same as #4947 was —
recorded because the copies are already numerous enough that they cannot move together.

What the shared module actually confines

packages/test-support/src/spec-tombstones.ts says, of resolvePropsShape:

Reaching into internals is confined to this module so that a gate never has to.

That is true for shape resolution (shape / def.shape / _def.shape / the
lazySchema() thunk). It is not true for anything else a spec-parity test reads off a
Zod node, and those readers are still written out by hand in each file.

The copies, by spelling

(1) Enum-options reading — four VERBATIM copies, four packages. ✅ Consolidated by PR #6047; see the measured-state block above. Identical text
apart from the key name:

file line
packages/components/src/__tests__/data-table-selection-mode.test.tsx 38-39
packages/plugin-list/src/__tests__/add-record-position-spec-parity.test.tsx 49-50
packages/plugin-list/src/__tests__/user-filter-arity-spec-parity.test.tsx 50-51
packages/plugin-timeline/src/__tests__/timeline-scale-spec-parity.test.ts 23-24

all spelling .shape?. as { def?: { innerType?: { options?: readonly string[] } } }
then ?.def?.innerType?.options. Note this one reads only def.innerType — not
_def, and not the un-wrapped case — so a schema whose member is not .optional(), or
a Zod build exposing _def, silently yields undefined. Whether each call site fails
loudly on undefined or quietly derives an empty option set is exactly the per-file
question that having four copies makes expensive to answer.

(2) Array-element unwrapping — three different spellings for one question:

  • packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts:83 —
    arr?.element ?? arr?.def?.element ?? arr?._def?.element ?? arr?._def?.type
  • packages/app-shell/src/views/metadata-admin/previews/__tests__/block-config.test.ts:82-91
    — a bounded 6-iteration loop alternating def.innerType / def.element, typed
    Record
  • packages/app-shell/src/views/metadata-admin/clientValidation.optOuts.test.ts:351-360
    — a third spelling again, through _zod.def.element and a def.getter() call

(3) A wrapper-key walk — three copies of one literal list:
['in', 'out', 'innerType', 'schema', 'left', 'right'] appears in
previews/flow-canvas-seeds.spec-parse.test.tsx:124,
inspectors/flow-node-config.spec-reconciliation.test.ts:108, and
packages/core/src/actions/__tests__/actionKeys.pin.test.ts:76.
⚠️ Superseded — this class has since grown out of tests into .mjs CI gate scripts. See the measured-state block.

(4) recordRelatedListInputs.spec-parity.test.ts:61-65 carries yet another
_def.innerType ?? _def.type unwrap.

Why it is worth recording rather than fixing opportunistically

The failure mode is the one spec-tombstones.ts already argues at length: a Zod
internals rework moves one spelling and not the others, and the copies that stop reading
go quietly permissive — a derived option set becomes empty, an element shape becomes
undefined, and the assertion built on it passes over nothing. Copy (1) is the sharpest
case because the four are textually identical right now, so no reviewer diffing one of
them would notice the other three did not move.

It is also worth noting what #4947 did not create: none of this is new. #4947 removed
two copies of the tombstone verdict and left every reader above exactly as it found them.

Suggested disposition (a judgement call, not a prescription)

The obvious shape is to grow @object-ui/test-support's spec module by the readers the
tests actually want — an enumOptions(schema, key) and an arrayElementSchema(schema)
alongside resolvePropsShape — and convert the call sites, per the README's "New gates
import this module" norm. But that is ~10 files across 7 packages for a class with no
live defect, so it may well be right to convert only copy family (1) (four verbatim
duplicates, one small helper, cheapest and highest-drift-risk) and leave (2)-(4) as
recorded. Worth a triage decision on scope before anyone starts.

Refs: #4947 / PR #5871 (the tombstone-verdict convergence), #4434 (the prior
copied-judge drift that motivated @object-ui/test-support), #3809 (the shared judge's
landing).


Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 23, 2026
  2. added theissue type on Aug 24, 2026
  3. claude commented on Aug 24, 2026

    @claude
    Contributor

    Finding concentration round (triage seat, session session_01Kktexqp6uVuFMztvvTMf3V, 2026-08-24, maintainer-invoked): promoted → pm:queue (domain:ui), Task. Extend @object-ui/test-support's confinement to the other Zod-internal readers the ~10 parity tests hand-copy (the card's census: four verbatim copies among them) so "reaching into internals is confined to this module" becomes true one reader-class at a time. Mechanical consolidation; no verdict changes expected — any test whose verdict flips under the shared reader is a real finding to report, not silently absorb.


    Generated by Claude Code

  4. self-assigned this
    on Aug 24, 2026
  5. yinlianghui commented on Aug 24, 2026

    @yinlianghui
    Collaborator

    Claim: PM loop round 38 (refill slot)
    Session: session_01CSoz9uGhaaSgiq3hshtN7L
    Branch: claude/issue-5872-zod-internals-reader-confinement
    Worktree: objectui-issue-5872
    Domain: domain:ui
    File surface: packages/test-support/src/** (the shared readers) + the ~10 parity test files that hand-copy them + a changeset
    Container & model: M, mode:subagent, model: opus
    Clause-②: no — test-support is consumed in-repo by tests; no runtime contract moves. ⚠️ If packages/test-support turns out to be a published package whose surface you widen, ⛔ say so in the report and treat it as declared.
    Serial constraints — ⚠️ read this before your first edit:

    裁决 — triage's grade and its fence

    Triage seat, 2026-08-24 (finding concentration round, maintainer-invoked), promoted → pm:queue / Task. Verbatim:

    Extend @object-ui/test-support's confinement to the other Zod-internal readers the ~10 parity tests hand-copy (the card's census: four verbatim copies among them) so "reaching into internals is confined to this module" becomes true one reader-class at a time. Mechanical consolidation; no verdict changes expected — any test whose verdict flips under the shared reader is a real finding to report, not silently absorb.

    That last clause is the whole card. A consolidation is only safe if it is verdict-preserving, and the way it goes wrong is that the shared reader is subtly better or worse than one hand copy, flipping one test — which then reads as "the refactor broke a test" and gets absorbed by adjusting the test. ⛔ Do not adjust a test to keep it green under the shared reader. A flip is evidence that the two readers disagreed about Zod internals, which is a genuine finding about the internals, and it is worth more than this card's mechanical value.

    "One reader-class at a time" is scope, not style. ⛔ Do not attempt every internals-reading pattern in one PR. Pick the class the card's census names (start with the four verbatim copies — they are the safest and the most clearly duplicated), consolidate it, and leave the rest. Say in the report which classes you did not take and why.

    PM mechanism assumptions — measure, do not assume

    1. "~10 parity tests" and "four verbatim" are the card's census from 2026-08-23. Re-derive both on your merge-base and report the delta. ⚠️ packages/types has moved repeatedly today (feat(types)!: retire the ActionCondition branch shape from ActionSchema.condition #5981, feat(types): close the dashboard widget type vocabulary and gate the plugin-dashboard catalog #6003, feat(types,components)!: ui:icon names its glyph with icon, not the identity key name #6012, and fix(types): widen seven zod mirrors the renderer already implements (#5927 group A) #6032 imminently), and fix(types): widen seven zod mirrors the renderer already implements (#5927 group A) #6032 in particular rewrote part of zod-mirror-parity.test.ts — a file that is plausibly in your census. ⛔ Do not work from the card's list.
    2. ⚠️ "Verbatim" is a claim to verify byte-for-byte, not by eye. Two copies that differ by a single ?. or a default value are not verbatim, and consolidating them is where a verdict flips. Diff them mechanically and report which are genuinely identical and which merely look alike.
    3. The confinement claim is falsifiable — falsify it. If the goal is that reaching into Zod internals is confined to test-support, then after your change there should be zero occurrences of that reader class outside it. ⛔ A zero without a control is not a measurement: show that the same search does find the reader inside test-support, and give the count.
    4. A shared reader is a new dependency edge. Check that every consuming package already depends on @object-ui/test-support; if one does not, adding the dependency is part of the change and the phantom-dependency gate will judge it. ⛔ Do not add a workspace dependency silently.

    Verification

    The verdict-preservation claim is the deliverable and it must be measured, not asserted. Record the full test-count line for each affected suite before and after — same suites, same invocation — and show they are identical. ⛔ "All green" is not sufficient: a suite that lost a test and gained a test is all-green and is exactly the failure this check exists to catch. Report file counts and test counts.

    Reverse-verify the consolidation, direction predicted before running: break the shared reader in one specific way and show every converted call site goes red together. If only some do, the sites are not actually sharing the reader you think they are — ⛔ report that rather than shipping.

    Prove every mutation on disk by grepping the injected text and separately the removed text, and ⚠️ print the landing site rather than assuming it, and ⚠️ assert anchor uniqueness before writing. Both traps were measured in this round: #4730's mis-anchored injection still moved the number in the predicted direction, and #5927's anchor hit three sites because sibling schemas carry byte-identical lines — ⚠️ and byte-identical lines are precisely what this card is about, so you are working in the terrain where that failure is most likely. Restore under trap … EXIT INT TERM; git diff HEAD --stat empty afterwards.

    Gates by name with exit codes: pnpm --filter @object-ui/test-support type-check plus type-check for every package whose tests you touched (⚠️ hyphenated; echo the script name so a zero-match silent pass cannot read as green — and a first RED reading Cannot find module @object-ui/... is the unbuilt-closure signature, not a real failure), root-form vitest over each affected package's test paths (⚠️ this repo's vitest guard refuses the --filter forms — use pnpm exec vitest run <paths> from the repo root), node scripts/check-phantom-dependencies.mjs, and eslint on changed files with the merge-base delta stated. ⚠️ Plain eslint . per package — ⛔ do not add --no-inline-config; it manufactures phantom errors (measured twice today).

    Run node scripts/check-changeset-presence.mjs rather than guessing; empty frontmatter is this repo's "releases nothing" declaration. Draft PR, Fixes #5872 only if you take the whole reader class the card names — otherwise Refs #5872 and say what remains. ⛔ Do not mark it ready, ⛔ do not enqueue, ⛔ do not enable auto-merge — the PM lands it. Post the terminal report here (plain-text first-line marker os-dev-report; ⛔ avoid short angle-bracket placeholders in prose — this repo's sanitizer eats them) and return it.


    Generated by Claude Code

  6. yinlianghui commented on Aug 24, 2026

    @yinlianghui
    Collaborator

    os-dev-report

    {
      "issue": 5872,
      "status": "done",
      "branch": "claude/issue-5872-zod-internals-reader-confinement",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6047",
      "premise_still_valid": true,
      "summary": "Took reader class (1) only — the four verbatim enum-options copies — per the triage fence 'one reader-class at a time'. New module packages/test-support/src/spec-enum-options.ts exports shapeEnumOptions(schema, key); the four call sites (components/data-table-selection-mode, plugin-list/add-record-position, plugin-list/user-filter-arity, plugin-timeline/timeline-scale) now import it, and three released packages gain a devDependencies edge on the private, never-published @object-ui/test-support. NO VERDICT FLIPPED and no test was adjusted: the candidate reader was run side by side with the hand copy BEFORE any conversion and returned the identical array in the identical order for all four (schema, key) pairs (DISAGREEMENTS: 0 of 4). Census re-derived on merge-base b0de7a85c: class (1) is still exactly 4 files, byte-for-byte identical (normalising schema/key/local names to S/K/V and hashing gives ONE distinct form, sha256 prefix 226c3eb04c1dafc0, raw lengths 337/344/335/331 differing only by identifier length). The card's '~10 parity tests' measures as 11 files matching innerType, one of which (core/src/actions/actionKeys.ts) is not a test but a comment. Serial constraints observed: packages/types/src/__tests__/zod-mirror-parity.test.ts (PR #6032, still enqueued/draft at branch time) turned out NOT to be in the census at all — it does not read innerType — so nothing here went near packages/types; #5903, #5835, #6010 and #5808 directories were likewise untouched. Clause-2: test-support is private:true and never published, so no published surface widens — confirmed from its package.json, not assumed."
      ,
      "tests": "All exit codes captured before any pipe; all runs on 3b010122f (the tree in PR #6047). VERDICT PRESERVATION, same suites / same invocation, root-form vitest: BEFORE -> 'Test Files 4 passed (4)' / 'Tests 31 passed (31)'; AFTER -> 'Test Files 4 passed (4)' / 'Tests 31 passed (31)'. Per-file identical both times (8 / 5 / 10 / 8), and a sha256 fingerprint over (file, sorted test names) is identical too — c909f7b78011cad5c33d both runs — which is what rules out a suite that loses one test and gains another. Only declared count movement: packages/test-support went 2 files / 13 tests -> 3 files / 25 tests, i.e. exactly the 12 new calibration tests, with both pre-existing suites unmoved (measured by re-running just those two files). BROADER SUITES: 'pnpm exec vitest run packages/test-support/src packages/plugin-list/src packages/plugin-timeline/src --maxWorkers=2' EXIT=0, 58 files / 770 tests; 'pnpm exec vitest run packages/components/src --maxWorkers=2' EXIT=0, 183 files / 1681 tests. TYPE-CHECK (hyphenated script name echoed by each run, so a zero-match silent pass cannot read as green; dependency closure built first with 'pnpm --filter <pkg>^... build' EXIT=0 so no Cannot-find-module false red): @object-ui/test-support 'tsc --noEmit' EXIT=0; @object-ui/components 'tsc --noEmit && tsc -p tsconfig.test.json' EXIT=0; @object-ui/plugin-list same two-step EXIT=0; @object-ui/plugin-timeline same two-step EXIT=0. OTHER GATES: 'node scripts/check-phantom-dependencies.mjs' EXIT=0, its own verdict line 'Every in-scope import is declared by the package that publishes it.'; 'node scripts/check-changeset-presence.mjs' EXIT=0, verdict line '4 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s) ... Every one of them has an EMPTY frontmatter — declared as releasing nothing' (it was EXIT=1 before the changeset was added, so the gate is non-vacuous here); 'pnpm exec eslint .' in each of the four packages EXIT=0 with 0 errors (plain form, no --no-inline-config; the warnings are pre-existing no-explicit-any / react-refresh ones and none sits on a line this PR changed). CONFINEMENT, falsified with a control: searching the reader class (innerType?.options and the innerType?: { options cast tail) outside packages/test-support returns 0; the CONTROL search inside packages/test-support/src returns 8 hits (3 executable — spec-enum-options.ts lines 71, 72, 108 — and 5 inside the docblocks that quote the retired copies), and a second control finds shapeEnumOptions at 4 consumer call sites plus the index export plus the calibration. REVERSE VERIFICATION, direction predicted in writing BEFORE the run: 'break the shared reader so it reports an empty vocabulary and all four converted files must go red, each at minimum through its own reads-a-non-empty-enum-from-the-spec assertion; if only some go red the sites are not really sharing the reader.' Mutation was one line in the shared reader, '[...options] as string[]' -> '[] as string[]' (the quiet-permissive failure the module exists to stop). Anchor uniqueness asserted BEFORE writing: grep -Fc gave 1. Mutation proved on disk before any reading was taken — injected text count 1, removed text count 0, landing site printed as spec-enum-options.ts:104 — because an editor's exit code proves nothing on a zero-hit anchor. Observed: 'Test Files 5 failed (5)' / 'Tests 17 failed | 26 passed (43)'. Exactly the predicted direction and exactly the predicted set: all four converted files plus the calibration, each converted file failing its own reads-a-non-empty-enum assertion AND one further parity assertion (data-table: does not accept selection modes the spec rejects; add-record: gives every spec position a placement that matches its name; user-filter: does not declare control types the spec rejects; timeline: declares exactly the spec scales). Restored under trap ... EXIT INT TERM; 'git diff HEAD --stat' empty afterwards and the removed text back at count 1. No ablation of a built dist was involved — @object-ui/test-support has no build step, consumers resolve its TypeScript source through the exports map, so the mutation was live to every call site with no rebuild. INCIDENTAL FINDING from that same ablation, reported rather than absorbed: on an empty vocabulary, the 'spec accepts a name we do not implement' HALF of three of the four parity gates stays GREEN (it filters an empty list). That is the card's own quiet-permissive argument reproduced under measurement — only the non-vacuity assertion catches it, which is why the shared reader's docblock makes that assertion an explicit duty of every consumer."
      ,
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Notes that do not fit the JSON fields but the PM should see:

    • Refs #5872, not Fixes. Reader classes (2), (3) and (4) from the card's census are untouched and still hand-copied, so the card is not closed. Class (2) is the one that deserves its own round with real care: it is three different spellings for one question (recordDetailsInputs.spec-parity.test.ts:83, previews/__tests__/block-config.test.ts:82-91, clientValidation.optOuts.test.ts:351-360), so a shared reader must pick a behaviour where three copies disagree — the flip risk this card refuses to run blind. Class (3) is a literal wrapper-key list in three files, a different reader shape. Class (4) is two single-site unwraps, one of which (packages/types/.../gantt-view-mode-declared.test.ts:53) is new since the card was filed.
    • The #6032 serial constraint cost nothing. zod-mirror-parity.test.ts is not in the census — it does not read innerType. No file in packages/types, packages/app-shell/src/views/RecordDetailView.tsx, packages/components/src/renderers/form/form.tsx or packages/plugin-detail/src/renderers/__tests__/ was touched.
    • One dependency edge added deliberately, not silently: @object-ui/components, @object-ui/plugin-list, @object-ui/plugin-timeline each gain "@object-ui/test-support": "workspace:*" in devDependencies per the package README's convention, plus the pnpm-lock.yaml update. The phantom-dependency gate judged it, exit 0.
    • PR is draft, not enqueued, no auto-merge, per the dispatch. CI had not converged when this report was written — reporting at the draft-PR moment is the contract.
    • Sanitizer note: the PR body's first publish lost an angle-bracket placeholder inside a code span (--filter '(pkg)^...' build came back as --filter '^...' build). It was re-published with the concrete filter spelling instead. Nothing else in the body moved.

    Generated by Claude Code


    Generated by Claude Code

  7. yinlianghui commented on Aug 24, 2026

    @yinlianghui
    Collaborator

    PM review — ACCEPT

    PR #6047, head 3b010122f, merge-base b0de7a85c. ⚠️ Refs, not Fixes — classes (2), (3), (4) remain, so the card stays open. Gate read at the usual mark (you noted CI had not converged at report time — that is expected and is the PM's step).

    Fences held, checked: packages/types/** returns nothing in the diff — and the reason is better than compliance, see below. private: true confirmed in packages/test-support/package.json, so the Clause-② answer is no widening, read from the file rather than assumed.

    Verdict preservation — measured harder than I asked

    I said "all green" was insufficient because a suite that loses a test and gains a test is all-green, and demanded file and test counts. You gave those (4/31 → 4/31, per-file 8/5/10/8 identical) and then closed the hole properly:

    a sha256 fingerprint over (file, sorted test names) is identical too — c909f7b78011cad5c33d both runs

    ✅ That is strictly stronger than counts, because counts are still satisfiable by a swap. And the only declared movement — test-support 2 files/13 tests → 3/25, exactly the 12 new calibration tests, with both pre-existing suites re-run to prove they did not move — is accounted for rather than absorbed into a bigger number.

    ⭐ The flip risk was measured BEFORE the conversion, not after

    the candidate reader was run side by side with the hand copy BEFORE any conversion and returned the identical array in the identical order for all four (schema, key) pairs (DISAGREEMENTS: 0 of 4)

    ✅ This is the right shape and it is not what I asked for — I asked you to report a flip if one happened. Measuring agreement before converting means a flip could never have been mistaken for "the refactor broke a test", which is the exact absorption the triage fence was written to prevent. ⛔ The failure mode is not catching a flip; it is catching one and misreading it.

    "Verbatim" verified mechanically, as required

    normalising schema/key/local names to S/K/V and hashing gives ONE distinct form (sha256 prefix 226c3eb04c1dafc0), raw lengths 337/344/335/331 differing only by identifier length

    ✅ Assumption 2 satisfied exactly: four copies that look alike proven byte-identical modulo identifier names, with the length spread explained rather than waved at. ⛔ Eyeballing this is how a ?. difference gets consolidated away.

    The census delta, and a serial constraint dissolved by measurement

    ✅ The card's "~10 parity tests" measures as 11 files matching innerType, one of which — packages/core/src/actions/actionKeys.ts — is not a test but a comment. Reporting the count and the disqualification is what makes the census reusable for classes (2)–(4).

    ✅ And my serial fence turned out to be moot for a reason worth having: packages/types/src/__tests__/zod-mirror-parity.test.ts is not in the census at all — it does not read innerType. So the #6032 collision I fenced against could not have happened. ⛔ You did not merely avoid the file; you established it was never in scope, which is the difference between obeying a fence and knowing why it exists.

    The reverse verification, and the finding inside it

    ✅ Prediction written before the run, and specific: "break the shared reader so it reports an empty vocabulary and all four converted files must go red, each at minimum through its own reads-a-non-empty-enum assertion; if only some go red the sites are not really sharing the reader." Mutation [...options] as string[] → [] as string[] — deliberately the quiet-permissive failure the module exists to stop. Anchor uniqueness asserted before writing (grep -Fc = 1), landing site printed (spec-enum-options.ts:104), injected and removed text grepped separately. Observed: 5 files / 17 tests red — all four converted plus the calibration, each failing its own non-vacuity assertion and a parity assertion, named individually.

    ⭐ And the incidental finding is the most valuable thing in this report:

    on an empty vocabulary, the "spec accepts a name we do not implement" HALF of three of the four parity gates stays GREEN (it filters an empty list). That is the card's own quiet-permissive argument reproduced under measurement — only the non-vacuity assertion catches it.

    ✅ That is a fact about the whole parity-gate family, not about this refactor: half of a two-direction gate degrades silently when its input vocabulary empties. Any future consolidation in classes (2)–(4) inherits that hazard, and the shared reader's docblock now makes the non-vacuity assertion an explicit duty of every consumer — which converts a discovered hazard into a structural one. ✅ Reported rather than absorbed, exactly as the fence required.

    Confinement — falsified with a control, as required

    ✅ 0 hits for the reader class outside packages/test-support; the control inside returns 8 (3 executable at spec-enum-options.ts:71,72,108, 5 inside docblocks quoting the retired copies), plus a second control finding shapeEnumOptions at the four call sites, the index export and the calibration. ⛔ A zero without a control is not a measurement, and this one has two.

    ✅ Dependency edges declared and judged: three released packages gain a devDependencies edge on the private @object-ui/test-support, pnpm-lock.yaml moved with them, and check-phantom-dependencies returns its own verdict line. ⛔ Not added silently.

    Scope — "one reader-class at a time" honoured, with reasons

    ✅ Class (1) taken; (2), (3), (4) named and left, and the reason for (2) is the right one: three DIFFERENT spellings, so a shared reader must pick a behaviour where three copies disagree — "the flip risk this card refuses to run blind." That is precisely the case where consolidation stops being mechanical, and stopping at the boundary is the judgement the fence was asking for. ⚠️ Also noted: one of class (4)'s two sites is new since the card was filed, in packages/types where live sibling work sits — so the census itself is a moving target, which is worth carrying into whichever round takes the rest.


    Generated by Claude Code

  8. os-litant commented on Aug 25, 2026

    @os-litant
    Collaborator

    Dead-claim reclaim — back to pm:queue, ⛔ no work lost

    domain:ui @ objectui execution seat, PM session session_01Mn4BZ5AVDM81pvfij1WwM9 (os-litant), on a maintainer forced-handover from session_01CSoz9uGhaaSgiq3hshtN7L (account yinlianghui, retired).

    Evidence

    The claim was held by a retired account, so it can never be discharged by its owner. Both legs of the corrected stale-claim test are satisfied:

    • ⛔ No branch under any naming.
    • ⛔ No merged commit referencing this card across 711 commits of main.

    ⇒ Nothing was pushed; nothing is lost. pm:dispatched → pm:queue, assignee cleared.

    ⚠️ The single-leg test must not be used here. "Empty ls-remote ⇒ stale" is false in a squash-merging repo — a completed card looks identical. Both legs, every time.

    ⚠️ For whoever picks this up

    The card's premise is a duplication count across ~10 test files, and counts are exactly the class of premise that has been going stale in this lane — about half the cards dispatched on 08-24 had materially wrong premises, several of them counts.

    ⛔ Re-derive the current copy set before writing anything. Do not inherit the card's figures, and if the count has moved materially, stop and report rather than proceeding against a number that no longer holds. A premise_still_valid: false with no PR is a good run here, not a failed one.


    Generated by Claude Code

  9. os-project-manager commented on Sep 3, 2026

    @os-project-manager
    Collaborator

    Claim: domain:ui execution seat, PM session session_01EMrWaQw3XS5DxTHxp4yRyC, R2. Branch claude/issue-5872-zod-reader-classes-2-4. Dispatching to an os-dev subagent at opus.

    ⛔ Scoped dispatch — classes (2) and (4) ONLY. Comments re-read before claiming, and the body's measured-state block read first, as it instructs.

    What is explicitly NOT dispatched, and why the label is still honest

    • Class (1) — ✅ already landed (PR test(test-support): confine the spec enum-vocabulary reader to one module (#5872 class 1) #6047, merged 2026-08-24). Nothing to do.
    • Class (3) — ⛔ excluded: the card itself records it as blocked on a maintainer ruling, and the blocker is structural, not preference: the literal wrapper-key list has grown out of tests into .mjs CI gate scripts, while @object-ui/test-support is private: true with an exports map pointing at TypeScript source, so node scripts/check-*.mjs cannot import it. The card is explicit that "just do the TypeScript sites" would ship the exact split this card family exists to prevent, with a language boundary making it permanent. The dispatch forbids touching it.
    • Classes (2) and (4) — dispatchable, as a measurement round.

    ⚠️ I considered whether pm:queue on a card with a blocked class is the same mislabel I disputed on #6951 today. It is not: #6951 named no dispatchable deliverable, while this card names two classes with concrete work and a stated method. That makes it the #6952 shape — defensible — so ⛔ no pm:retriage. The distinction is whether the body names something a dev can produce, not whether the title or a class sounds blocked.

    Why this card, now

    ⭐ Byte-free by construction — test files plus @object-ui/test-support, which is private: true and ships in no bundle. This lane is blocked on #7399 (the framework per-chunk ceiling, 177 bytes repo-wide), so surfaces that cannot reach the console bundle are the selection rule until it rules. Same reason #7115 was chosen and #7178 was passed over despite being equally ruled.

    The three traps this card has already sprung, carried into the dispatch

    1. ⭐ The census in the original body is stale and has already cost one full dispatch. Re-derive before writing anything. The card says a premise_still_valid: false with no PR is a good run here, and the dispatch repeats that.
    2. ⭐ A "N commits of main" scan is not evidence of absence on this repo. main was re-rooted on 2026-08-30, so git log cannot see any pre-re-root merge — that is exactly how the 2026-08-25 dead-claim reclaim produced a false negative 17 hours after the merge it failed to see. Use blob identity against the fetched ref, or the PR's merged flag.
    3. ⭐ Counts are satisfiable by a swap. On the 2026-08-31 re-measurement timeline-scale's count held at 8 while two test names were swapped — identical count, moved fingerprint. Fingerprint (file, sorted test names) with sha256 instead.

    The constraint that decides the shape

    Per the 2026-08-31 census (comment 5480747303): ~18 hand-written wrapper walks across 7 packages, and ⚠️ "converting these is not the mechanical job #6924/#7025 were" — several walks are deliberately one level deep with a comment saying so, while enumOptions walks up to 8. A widening can change a verdict rather than preserve it. ⇒ Per-site measurement, not a sweep. A partial conversion with the untouched sites justified is a better outcome than a complete one that flipped a verdict silently.

    Also carried: two sites spell it Schema.shape.key.unwrap().options with no cast and no guard — a seventh spelling, structurally invisible to both instruments used so far, because each of those needs an as to match.

    And the hazard any new shared reader inherits: on an empty vocabulary the "spec accepts a name we do not implement" half of three of the four parity gates stays GREEN, because it filters an empty list. Only a non-vacuity assertion catches it.


    Generated by Claude Code

  10. os-project-manager commented on Sep 3, 2026

    @os-project-manager
    Collaborator

    os-dev-report

    {
      "issue": 5872,
      "status": "done",
      "branch": "claude/issue-5872-zod-reader-classes-2-4",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7423",
      "premise_still_valid": true,
      "summary": "RE-DERIVED CENSUS (the card's own instrument, re-run on main@24d4a21ff, pathspec `packages/ examples/ apps/` minus `packages/test-support/src/`, extensions ts+tsx): 31 matching LINES in 23 FILES across 7 packages — types x11 files, app-shell x5, plugin-detail x2, components x2, plugin-gantt x1, data-objectstack x1, core x1. The 2026-08-31 census said ~18 across 7 packages with types x8 / app-shell x5: app-shell held exactly, types grew 8 to 11, and `core/src/actions/actionKeys.ts` is a SOURCE file (a comment about walking zod internals), not a test. Population real and still growing ⇒ premise valid, round proceeded. A second, broader instrument was also run (73 files / 152 lines over `packages apps scripts e2e eslint-rules`, tokens `_def`, `_zod`, `.def`, `innerType`, `.element`, `unwrap(`, `getter()`, comments stripped) to find spellings with NO cast — it finds both `Schema.shape.key.unwrap().options` sites the census flagged, which the card said prior instruments could not see. DELIVERED: `arrayElementSchema` added to @object-ui/test-support (`src/spec-array-element.ts`), and the ONE wrapper walk extracted to `src/spec-zod-wrappers.ts` so the new reader is not a second copy of it — `enumOptions` now stands on the same `firstInWrapperChain`. Converted 4 sites: all three class-(2) array-element walks plus one class-(4) `.unwrap().options`. TWO PREMISE CORRECTIONS FOR THE CARD, neither acted on: (1) class (3) is NOT blocked any more — it was RULED on 2026-08-31 (objectui#6923), `zod-wrapper-keys.json` plus the `./zod-wrapper-keys` exports subpath gave the DATA a build-free home the `.mjs` gates can read, and `packages/test-support/README.md` records `No copy of the list is left in-tree`; the dispatch's structural blocker no longer holds, though the ruling deliberately left the WALKS with their callers. (2) A LANDMINE was found and disarmed inside scope: `recordDetailsInputs`'s hand copy ended its chain with `arr?._def?.type`, which is Zod 3's element spelling but on the installed zod@4.4.3 is the type-name STRING 'array' — measured — so had its three earlier limbs ever missed it would have handed `listedShapeKeys` a string and derived the empty set in silence. The shared reader keeps that Zod 3 limb only behind `_def.typeName === 'ZodArray'`. Also measured and recorded in the reader's docblock: on this pin `node.def === node._def === node._zod.def` are the SAME OBJECT, so the three spellings the copies disagreed about are one object today.",
      "tests": "All heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh (slot issue-5872-ui); verdicts read off its VERDICT line, exit codes captured by redirect-then-read, never after a pipe. FINAL UNION AT HEAD 3f06af818 (worktree clean at the time): `pnpm exec vitest run <37 files> --maxWorkers=2` → 'Test Files  37 passed (37) / Tests  538 passed (538)', VERDICT command-exit 0. That set is every one of the 33 in-tree `enumOptions` consumers plus the 4 converted sites plus both test-support calibration suites — the consumer sweep owed for editing spec-enum-options.ts. TYPE-CHECK: `pnpm --workspace-concurrency=2 --filter @object-ui/test-support --filter @object-ui/plugin-detail --filter @object-ui/data-objectstack --filter @object-ui/app-shell run type-check` → VERDICT command-exit 0, after first building the dependency closure (`--filter '<pkg>^...' run build`, 29 of 47 projects, exit 0) — the first attempt without it was TS2307 noise, not a red gate. NOT-MEASURED TRAP CHECKED: `tsc -p <config> --listFiles` per package confirms every edited file is inside the program that reported clean (spec-zod-wrappers.ts, spec-array-element.ts, spec-array-element.test.ts, metadata-client.overlayScope.test.ts, recordDetailsInputs.spec-parity.test.ts, clientValidation.optOuts.test.ts, block-config.test.ts). LINT: the FULL repo-wide farm ran, no narrowing — `turbo run lint --concurrency=2` → 'Tasks: 47 successful, 47 total', 0 errors (2876 pre-existing warnings). GATES, all exit 0: control-bytes, phantom-deps, self-import, readme-exports, eager-closure, spec-symbols, dist-completeness, published-dist, published-tsconfig-exclude, esm-specifiers, vi-mock-specifiers, vi-mock-inherit, side-effects-array, doc-fences, spec-floors, check-changeset-presence. readme-exports and eager-closure first returned PREREQUISITE-style failures ('run pnpm build first' / 'No eager-closure report ... a broken gauge, not a passing budget') — read as NOT MEASURED, not red; a full `turbo run build` (43/43) then made both green. EQUIVALENCE MEASUREMENT for the shared-walk extraction: a differential harness ran the pre-change `enumOptions` verbatim and the post-change algorithm over 1386 nodes reachable from all 12 @objectstack/spec subpath modules (334 answer non-empty) → 'enumOptions OLD vs NEW differences: 0' and 'new getter() limb fired: 0 times' (ZodLazy exposes unwrap() on this pin, so the one added limb is unreachable). PER-SITE SET IDENTITY, every element compared by REFERENCE identity not merely derived keys: 2A sections/hideFields/fields all SAME (ZodObject/ZodString) with key sets [name,label,columns,fields] / [] / []; 2B specSectionKeys SAME [name,label,columns,fields]; 2C sharingRules and connectors elements SAME by reference, 16- and 30-key shapes SAME, checks() counts SAME (0 and 1), and array-vs-not discrimination swept over ALL 43 ObjectStackSchema members with zero divergence; C4b overlayScope SAME ['org','env']. ABLATION on the new calibration suite — @object-ui/test-support has NO build step and its exports map points at ./src/index.ts (verified from package.json), so the mutated bytes ARE the bytes that ran and no dist can serve a stale copy; both legs under `trap restore EXIT INT TERM` with absolute paths derived from `git rev-parse --show-toplevel`. Leg 1 (drop the `_def.typeName === 'ZodArray'` guard): on-disk proof removed-text 1→0, injected-text 0→1, vitest exit 1, 'Tests 13 failed | 9 passed (22)'. Leg 2 (answer with the node itself instead of undefined): removed-text 1→0, injected-text 0→1 by `grep -cF`, vitest exit 1, 'Tests 10 failed | 12 passed (22)' including the case 'never returns Zod 4's `_def.type` STRING as if it were a schema'. RESTORE proven by STATE both legs, not by exit code: `git diff HEAD` 0 changed paths, `git hash-object` equals the HEAD blob 83e125a6e95e442caaf317fd9f91f06c19a710f6, and post-restore anchored counts back to 1/0. REVERSE VERIFICATION for the site NOT converted, direction predicted before the run (turns red) and observed: converting LayeredDiff.overlayScope.test.tsx:31 to `shapeEnumOptions` yields 'src/views/metadata-admin/LayeredDiff.overlayScope.test.tsx(57,46): error TS2345: Argument of type string is not assignable to parameter of type org|env|null', tsc exit 2; same trap/anchored-count/blob-equality discipline, restored to blob 0b880f8998f40c2b5afa64ae3d6cabaceaf5cfee. EAGER-CLOSURE UNMOVED, measured on a real console build rather than asserted: framework 523,823 bytes against the 524,000 ceiling = 177 bytes headroom, the exact repo-wide figure the lane is blocked on; vendor-objectstack and ui-components also within ceiling; gate exit 0. Structural corroboration: the whole diff is test files, `packages/test-support/**` (private:true, no build), a devDependencies-only hunk in data-objectstack/package.json, .changeset and pnpm-lock.yaml — and a grep confirms nothing outside test files and scripts/*.mjs imports @object-ui/test-support.",
      "mcp_calls": "5 — issue_read(get), issue_read(get_comments), create_pull_request, pull_request_read(get), add_issue_comment. No search_issues call: no out-of-scope defect needed filing, so no duplicate check was owed and no GraphQL quota was spent.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Per-site detail the JSON compresses

    Converted (4). Old spelling to new, walk depth before to after, and the evidence the derived set is unchanged:

    site old new depth evidence
    plugin-detail/.../recordDetailsInputs.spec-parity.test.ts local shapeMember (one unwrap()) then arr?.element ?? arr?.def?.element ?? arr?._def?.element ?? arr?._def?.type arrayElementSchema(resolvePropsShape(RecordDetailsProps)?.[key]) 0 wrappers to up to 8 3 keys, elements identical by REFERENCE, key sets identical
    app-shell/.../previews/__tests__/block-config.test.ts bounded 6-iteration loop, _def.innerType / _def.element, Record<string, any> listedShapeKeys(arrayElementSchema(resolvePropsShape(...)?.sections)) 6 to 8, _def only to all spellings specSectionKeys identical
    app-shell/.../clientValidation.optOuts.test.ts local recursive unwrap through _zod.def plus a type-gated arrayElement arrayElementSchema(...) 6 to 8 elements identical by reference, shapes and checks() identical, undefined-parity swept over 43 members
    data-objectstack/.../metadata-client.overlayScope.test.ts specOverlayScope.unwrap().options (no cast, no guard) enumOptions(specOverlayScope) 1 to 8 ['org','env'] both ways

    Not converted, each for a measured reason.

    • app-shell/.../LayeredDiff.overlayScope.test.tsx:31 — the OTHER no-cast site. ⭐ The finding worth carrying forward: at this site the absence of a cast IS a compile-time guard, not merely an unguarded read. SPEC_SCOPES inherits its element type from the spec schema, so layeredWith(scope) stops compiling the day the spec adds a scope — which is precisely when someone must add a zh-CN label. A shared reader returns string[]; any cast that silences today's TS2345 silences that future one too. Proven by reverse verification (TS2345 quoted above). It is also the one site with no non-vacuity assertion — its module-scope .unwrap().options fails loudly by accident of placement — so converting it as-is would have traded a loud failure for a quiet one at the same stroke.
    • types/spec-subschema-parity.test.ts:163 and plugin-detail/.../recordHighlightsInputs.spec-parity.test.ts:50 — union arms, not enum names. Measured: ZodUnion.options on zod@4.4.3 is an array of arm SCHEMAS, so enumOptions would return schema objects behind a string[] annotation. This hazard is now written into enumOptions's docblock so a future sweep cannot make the mistake quietly. Checked for a live defect and found none: recordHighlightsInputs guards its [] fallback with expect(specEntryKeys()).toContain('readonly'), which an empty set fails.
    • components/.../toast-button-variant-parity.test.ts:95 — deliberately throws naming the card when .options is not an array; the shared reader answers []. Converting as-is deletes a loud failure.
    • plugin-detail/.../recordRelatedListInputs.spec-parity.test.ts:61-65 — walks until a shape is reachable, a fourth question no shared reader expresses; a walk-to-the-end reader would overshoot into an array element where this one stops. Its _def.type limb is already neutralised by a typeof next !== 'object' guard, so no landmine.
    • types/object-view-spec-parity.test.ts:91 — its own comment at line 78 says "Deliberately ONE unwrap, not a loop".
    • types/gantt-view-mode-declared.test.ts:53, types/timeline-declared-keys.test.ts:77, types/button-group-doc-surface-6347.test.ts:203, types/export-options-spec-parity.test.ts, types/list-view-spec-parity.test.ts, types/page-nav-misc-spec-parity.test.ts, types/table-column-type-canonical.test.ts, types/static-table-narrow-surface.test.ts, types/handler-keys-json-refusal-6124.test.ts — packages/types carries live sibling work and the dispatch asked for a minimal diff there; several of these also assert reference identity of the unwrapped node (expect(local).toBe(spec)), which no reader signature expresses.
    • plugin-gantt/readme-navigation-example.test.ts:99-101, components/.../table-declared-equals-enforced.test.tsx:74 — shape-resolution walks, a different reader class from (2)/(4).
    • app-shell/.../flow-node-config.spec-reconciliation.test.ts, app-shell/.../flow-canvas-seeds.spec-parse.test.tsx, core/.../actionKeys.pin.test.ts — class (3) territory, excluded by the dispatch. No .mjs gate script and no six-key list was touched.

    Non-vacuity ledger. Had one already: block-config (not.toEqual([])), recordDetailsInputs (length > 0), clientValidation.optOuts (toBeDefined() on the element, twice), metadata-client.overlayScope (toEqual(['org','env'])). Gained one: none needed. Still lacks one: LayeredDiff.overlayScope — and that is one of the two reasons it was not converted. The duty is restated in spec-array-element.ts's docblock, as spec-enum-options.ts requires of every new reader, and the new calibration suite pins the undefined cases so a consuming suite's toBeDefined() cannot become a rubber stamp.


    Generated by Claude Code

  11. os-project-manager commented on Sep 3, 2026

    @os-project-manager
    Collaborator

    Correction to the os-dev-report comment above — read-back caught the body sanitizer eating three angle-bracket placeholders inside the tests string. Nothing measured changed; three commands are quoted wrong. The verdicts, counts and hashes in that report all survived intact. Restated with placeholder words:

    • dependency-closure build: pnpm --workspace-concurrency=2 --filter 'PKG^...' run build — it reads as --filter '^...' above, with PKG eaten.
    • the not-measured check: tsc -p CONFIG --listFiles per package (tsconfig.json for test-support and data-objectstack, tsconfig.test.json for plugin-detail and app-shell) — it reads as tsc -p --listFiles above.
    • the type the block-config conversion removed was Record of string to any — the type arguments were eaten, so the table cell reads as a bare Record.

    The lesson is the one already written down for this repo and I still tripped over it: any angle-bracket fragment in text bound for GitHub gets eaten, backticks and fenced code included, and only a read-back finds it. Placeholder words, not brackets.


    Generated by Claude Code

  12. os-project-manager commented on Sep 3, 2026

    @os-project-manager
    Collaborator

    ACCEPT — classes (2) and (4) converged. The card STAYS OPEN, and its class (3) is now dispatchable.

    domain:ui execution seat, PM session session_01EMrWaQw3XS5DxTHxp4yRyC, R2. PR #7423 at 3f06af818, correctly Part of #5872 with no closing keyword. ⛔ Not yet enqueued — 18 checks green, 9 still running.

    ⭐ The landmine — the most valuable thing this round produced

    recordDetailsInputs's hand copy ended its chain with arr?._def?.type. That is Zod 3's element spelling. On the installed zod@4.4.3 it is the type-name STRING 'array' — measured, not inferred.

    ⇒ Had its three earlier limbs ever missed, that copy would have handed listedShapeKeys a string and derived the empty set, in silence. And an empty derived set is precisely the state where three of the four parity gates stay green while asserting over nothing — the hazard this card's own header records and the dispatch carried forward. A latent quiet-permissive failure, sitting in one of the three copies the round existed to reconcile.

    The shared reader keeps that Zod 3 limb only behind _def.typeName === 'ZodArray', and the ablation proves the guard is load-bearing: dropping it turns 13 of 22 tests red, including one named "never returns Zod 4's _def.type STRING as if it were a schema". ⇒ The fix is pinned by a test that fails for the right reason, not just present.

    ⭐ This is the argument for the whole card, made concrete: the copies were not merely redundant, one of them was wrong in a way no reviewer diffing another copy could see.

    Two premise corrections, both reported and neither acted on

    1. ⛔ My dispatch was wrong that class (3) is blocked. I wrote that its structural blocker held — the .mjs CI gates cannot import a private: true package whose exports map points at TypeScript source. It was RULED on 2026-08-31 ([Decision] Where does logic shared between .mjs CI gate scripts and TypeScript tests live? — @object-ui/test-support is TS-source-only, so #5872's class (3) cannot use the class-(1) pattern #6923), and the ruling dissolved exactly that blocker: zod-wrapper-keys.json plus a ./zod-wrapper-keys exports subpath gave the data a build-free home the .mjs gates can read, and packages/test-support/README.md records "No copy of the list is left in-tree". The ruling deliberately left the walks with their callers. ⇒ The dev read the ruling, corrected me, and did not expand scope on the strength of it — which is the right order.
    2. On this pin, node.def, node._def and node._zod.def are the same object — so the three spellings the copies disagreed about are one object today. Recorded in the reader's docblock, where the next person to wonder will find it.

    Why the conversion is trustworthy

    • Equivalence, not assertion. A differential harness ran the pre-change enumOptions verbatim against the post-change algorithm over 1386 nodes reachable from all 12 @objectstack/spec subpath modules (334 answering non-empty): 0 differences. And "new getter() limb fired: 0 times" — the one added limb is unreachable on this pin because ZodLazy exposes unwrap(), stated rather than left as an untested branch.
    • Per-site identity by REFERENCE, not by derived keys — elements compared as objects, shapes and checks() counts compared, and the array-vs-not discrimination swept over all 43 ObjectStackSchema members with zero divergence.
    • ⭐ The extraction did not create a second copy of the problem. The wrapper walk was pulled into spec-zod-wrappers.ts and enumOptions now stands on the same firstInWrapperChain — otherwise this round would have shipped a fourth spelling while retiring three.
    • ⭐ A NON-conversion justified by measurement. LayeredDiff.overlayScope.test.tsx:31 was left alone, and the reason is a reading with the direction predicted before the run: converting it yields TS2345: Argument of type string is not assignable to parameter of type org|env|null, tsc exit 2. A site left untouched with evidence is worth more than one converted on faith.
    • The ablation cannot be fooled by a stale artifact: @object-ui/test-support has no build step and its exports map points at ./src/index.ts, so "the mutated bytes ARE the bytes that ran". Both legs proven on disk, restored by state (git diff HEAD clean, blob equal to 83e125a6e…).
    • The three census traps the card is famous for were all avoided: the stale census re-derived (31 lines / 23 files, up from ~18 — types grew 8 → 11), no git log used for absence, and a second broader instrument run to catch the two spellings with no cast that every prior instrument was structurally blind to.

    An independent third measurement for #7399

    check:eager-closure exit 0, and the report gives framework 523,823 bytes / 524,000 ceiling = 177 bytes headroom — measured on a real console build. That is the third independent arrival at the same figure tonight, from a card that has nothing to do with the ceiling. Corroborates #7399 without being about it.

    The card stays open, and what is left is now different

    Class (1) landed (PR #6047). Classes (2) and (4) land here. Class (3) remains — and is no longer blocked, per #6923. ⇒ It is the named next candidate for this lane: byte-free, ruled, and with the data/walk split already decided for it. Whoever takes it should read #6923's ruling verbatim first, not this card's body, which is stale on exactly this point — as this round proved twice.


    Generated by Claude Code

  13. os-project-manager commented on Sep 3, 2026

    @os-project-manager
    Collaborator

    Closing — all four classes accounted for. Class (3) needed no work; it landed under a different card.

    domain:ui execution seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. PR #7423 landed at 42c129b64, verified by content below. This comment then re-derives the whole card rather than only the part that PR touched, because the next reader's question is "what is left", not "what merged".

    PR #7423 (classes 2 and 4) — landed, verified BY CONTENT on origin/main

    Two-sided readings, bf244f400 → 42c129b64, ⛔ not the PR head SHA:

    probe before after
    packages/test-support/src/spec-array-element.ts absent exists
    arrayElementSchema across packages/ 0 32 in 8 files
    control — enumOptions in packages/test-support/ 28 34 (non-zero both sides)

    The zod@4 landmine the round was filed to avoid is guarded on disk, with both belts — spec-array-element.ts:85: legacy?.typeName === 'ZodArray' && typeof legacy.type === 'object'. And LayeredDiff.overlayScope.test.tsx:31 still carries its raw .unwrap().options, so the compile-time guard the PR declined to delete is intact.

    ⭐ Class (3) — already complete, and NOT by this card

    The measured-state block above says class (3) is blocked pending a maintainer ruling. That is now stale in both directions: the ruling came, and the work was implemented and landed by someone else. It was ruled on #6923 (2026-08-31, verbatim 「同意」, option B narrowed to data) and implemented by PR #7022 in the domain:devx lane.

    Measured on origin/main just now — the three sites this card names by path:

    class (3) site literal 'innerType' imports the shared list
    previews/flow-canvas-seeds.spec-parse.test.tsx 0 2
    inspectors/flow-node-config.spec-reconciliation.test.ts 0 2
    packages/core/src/actions/__tests__/actionKeys.pin.test.ts 0 2

    packages/test-support/src/zod-wrapper-keys.json holds exactly ["in", "out", "innerType", "schema", "left", "right"], reached by node through "./zod-wrapper-keys": "./src/zod-wrapper-keys.json" and by TypeScript through the re-export. Control fires: 9 files import it, including both .mjs gates — check-action-forward-parity.mjs and check-designer-field-key-parity.mjs, the language boundary this class existed to describe.

    ⚠️ One hand-written copy of the literal remains, in scripts/__tests__/zod-wrapper-keys.shared.test.ts. That is not a leftover — it is the ruling's constraint-4 drift detector, which has to spell the expected list independently or it would compare the file to itself.

    ⭐ Recorded because it nearly cost a round: this seat's own patrol note carried "class (3) is now unblocked and dispatchable." Both halves were wrong in different ways — the ruling's constraint 5 expressly walls off shared logic (⛔ 带函数的跨语言共享不借本卡开门), so "unblocked" was too broad; and the data half it did unblock was already built. Reading the ruling verbatim caught the first error; only measuring the three sites caught the second. A ruling tells you what was permitted, never what has since been done.

    Class (4) — the residue is deliberate, not pending

    PR #7423 converted data-objectstack/.../metadata-client.overlayScope.test.ts. Every other member was declined with a measured reason recorded in the merged PR, not for budget:

    • LayeredDiff.overlayScope.test.tsx — conversion deletes a live compile-time guard, proven by the TS2345 it produces.
    • types/spec-subschema-parity.test.ts, plugin-detail/recordHighlightsInputs.spec-parity.test.ts — union arms; ZodUnion.options yields arm schemas, a different question.
    • components/toast-button-variant-parity.test.ts — deliberately throws where a shared reader answers [].
    • plugin-detail/recordRelatedListInputs.spec-parity.test.ts — walks until a shape is reachable, which no shared reader expresses.
    • types/object-view-spec-parity.test.ts:78 — its own comment says "Deliberately ONE unwrap, not a loop".

    Disposition

    Class (1) PR #6047 · class (2) + (4) PR #7423 · class (3) PR #7022. Nothing here is waiting on a decision, a dependency, or a dispatch, and the unconverted sites are unconverted on purpose, each with evidence. Closing as completed; pm:dispatched stripped in the same pass.

    ⚠️ If a later reader judges any declined site should convert after all, that is a new card with its own measurement — ⛔ do not reopen this one to carry it, and ⛔ do not convert LayeredDiff.overlayScope.test.tsx without first reproducing the TS2345 that says why not.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions