You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Triage first-touch — concentrated round #2 (triage seat, session session_019ZyCHcjXeTFPiS8RWRk4nN, 2026-08-19): promoted finding → pm:queue · Task · tooling, scoped to the card's own lean: direction (b) — a one-way assertion that every declared arm set ⊆ what spec accepts on that key (kill the fake-arm silent-widening mode #3832 newly made possible; measured live by mutation ③b, 856 tests green on a spec-rejected arm). Narrowing stays per-block discipline — it is at least noisy. Direction (a)'s full bidirectional probe matrix and the enum/member-shape granularity question (#3795) are out of scope; price them separately if (b)'s implementation surfaces the need. Gate addition, accept-set unchanged — within lane discretion. Size/model suggestion: M, opus (derivation logic against spec safeParse needs care to avoid false reds on enum arms — the card's own caveat).
Claimed by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe. Branch: claude/issue-4971-declared-arm-subset-gate.
Direction (b), per triage, and the asymmetry is the reason
⛔ Not (a) — the full bidirectional probe matrix and the enum / member-shape granularity question (#3795) are out of scope; price them separately if (b)'s implementation surfaces the need. ⛔ Not (c) — writing the discipline into AGENTS.md relies on the author remembering, which is #3809's tombstone blind spot by the same root cause.
(b): assert that every declared arm set is a SUBSET of what spec accepts on that key. One direction only.
⭐ The card's own argument for the asymmetry is what makes this the right cut, and it should go in the test header:
Narrowing (declaring fewer arms than spec accepts) produces noise — the gate warns on a legal value. Annoying, and audible.
Widening (a fake arm spec rejects) is silent — the gate passes a value spec would reject. declared = enforced inverts, and nothing says so.
#3832 made type carry arm arrays, which is what created the widening mode. Before it, narrow-only was the sole failure and it was self-announcing.
⭐ The measurement that defines "done" — reproduce mutation ③b
The card ran the discriminating pair. Use it as the acceptance test:
③a — add an 'object' arm to element:text_input.defaultValue (spec rejects it there): the per-block test text-input-inputs-spec-parity.test.tsalready reds today. ⛔ It must still red — do not trade per-block coverage for the new gate.
⭐ ③b — add a 'number' arm to page:card.title (spec rejects a numeric title): 856 tests pass, entirely green today. After your change this must RED. That single reading is the card.
The difference between them is only that text_input happens to have a per-block test and page:card does not — so today the property holds by per-block discipline, not by a gate. Closing that is the deliverable.
⚠️ The trap triage flagged: false reds on enum arms
Triage's size note calls it out — "derivation logic against spec safeParse needs care to avoid false reds on enum arms", and the card's own caveat says enum arms and nested shapes must either be exempted or given their own criterion.
⛔ Do not let an enum arm read as a fake arm. Coarse-type probing against a spec schema that accepts only specific members will report the coarse type as rejected, and the arm will look invented when it is correct. Decide the treatment, implement it, and state it explicitly — an exemption that is silent is a hole; an exemption that is listed is a scope boundary.
⚠️ And the exemption must be narrow enough to still catch ③b. Prove that: your enum handling must not be the reason page:card.title's 'number' arm slips through.
Non-vacuity
⛔ If the walk finds zero declared arms, or zero keys covered, the gate must FAIL, not pass. Put the census in the verdict line (keys judged, arms judged, exemptions applied and why).
Reuse the existing covered set and exemption discipline in registry-inputs-spec-parity.test.ts — ⛔ do not fork a second classifier. That is the mistake [finding] 四处读注册键表的钉子正则只认单引号,而本仓没有任何机制强制单引号 —— 一个双引号的 register 会被静默漏读 #4894 is fixing one gate over, and this lane rejected the same shape there tonight.
Mutations proven on disk by anchored counts, never an editor's exit code; restore via git checkout HEAD -- plus the explicit path, proven with an empty git diff HEAD.
Scope
The gate addition in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (or a sibling sharing its covered set). ⛔ Accept-set unchanged — this card adds an assertion, it does not change what spec accepts or what any registration declares. There are zero fake arms today (#3832's five arms were each measured against spec and the renderers), so a red on arrival is a finding: report it, do not "fix" a declaration to make your gate green.
Root vitest only (objectui#3378). Exit codes captured by redirect before any pipe. ⚠️ Derive the gate list from package.json and .github/workflows/ rather than from this order.
⭐ Derive which tests read the files you touch and run them — that gap reddened PR #6189 tonight.
{
"issue": 4971,
"status": "done",
"branch": "claude/issue-4971-declared-arm-subset-gate",
"pr": "https://github.com/objectstack-ai/objectui/pull/6220",
"premise_still_valid": true,
"summary": "Added the one-directional arm gate to apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (direction (b)): every coarse arm a covered block declares on a key must be a KIND @objectstack/spec accepts there, over the file's existing `covered` set, `specTopLevelKeys` and exemption discipline — no second classifier, accept set unchanged, no declaration edited. The judge reads the parse ISSUES rather than the boolean: scoped to the key (so a required sibling cannot speak for it), recursing through a union's branches (their paths are RELATIVE to the union node — a judge matching absolute paths refutes nothing and passes every union key, including the card's own reading), and refuting only a KIND refusal. The enum trap is handled as a RULE, not a silent exemption: `invalid_value` counts as a kind refusal only when NO listed value shares the probe's kind, pinned in both directions on `record:quick_actions.variant` ('Account' -> refuses-content so the correct 'string' arm is not called invented; 42 -> refuses-kind so an enum contract cannot launder a number arm). An `enum` ARM is judged exactly (every declared member must be accepted). Two arm kinds are exempt and listed: 'slot' (names a child position, not a value — `armAccepts` admits everything for it) and an 'enum' arm with no declared members (admits nothing). RED ON ARRIVAL on two SINGLE-arm declarations that predate #3832's array form and had never been compared to the contract — reported, not fixed: filed #6206 (element:number.filter) and #6207 (object-grid.data), each carried as a reasoned, issue-backed exemption that the stale-exemption test deletes the moment either side moves.",
"tests": "ALL AT 1f205b1f4 (the pushed HEAD; every mutation restored to it, tree clean, `git diff HEAD` empty). Each heavy run went through scripts/pm/os-verify-lock.sh; every reading is taken from its VERDICT line or from an exit code captured by redirect BEFORE any pipe, never a bare $?.\n\nMUTATION 3b (the card's definition of done) — `page:card.title` + a 'number' arm: proven on disk by anchored counts (injected-anchor 0->1, removed-anchor 1->0 on the unique full-line anchor in a dry run, plus the printed line 788), restored by `git checkout HEAD -- <path>` with an empty `git diff HEAD`; the script carried `trap ... EXIT INT TERM`. RESULT: `× page:card declares no arm the spec refuses outright / AssertionError: page:card.title:number — the contract refuses the KIND itself — probe verdicts [\"refuses-kind\"]: expected [ 'page:card.title:number' ] to deeply equal [] / Tests 1 failed | 108 passed (109)` — this was ENTIRELY GREEN (856 tests) before the change. NO REBUILD NEEDED AND NONE CLAIMED: apps/console/vite.config.ts:424 aliases @object-ui/components to packages/components/src, so the mutation reaches the run from source without passing through dist/ — the dist-staleness failure mode does not apply here, and that alias is the evidence.\n\nMUTATION 3a — `element:text_input.defaultValue` + an 'object' arm, same on-disk proof (injected-anchor 0->1, target-anchor 1->0) and same trap/restore: the per-block pin STILL reds, unchanged — `× DECLARES both arms of the spec union, and only those arms / AssertionError: expected [ 'number', 'object', 'string' ] to deeply equal [ 'number', 'string' ]`, plus `× \\`defaultValue\\` is NOT part of the trio` and the new gate's own `× element:text_input declares no arm the spec refuses outright`. `Tests 3 failed | 121 passed (124)`. No per-block coverage was traded for the gate.\n\nCENSUS (measured by a declared, trapped, anchored flip of the non-vacuity floor, then restored — `git diff HEAD` empty): `blocks 27 · keys judged 215 · arms judged 229 · exempt(slot — describes a child position, not a value) 5 · exempt(enum arm with no declared members — admits nothing) 0 · refused 2 · registered exemptions 2`.\n\nGREEN RUNS: (1) `pnpm exec vitest run --maxWorkers=2` over 14 files — the 3 touched plus every in-tree arm/parity pin (text-input, record-picker, page-container, page-header, recordDetails/RelatedList/Highlights, spec-tombstones, public-contract, ga-honoured-inputs, component-input-union-specimens, gridDataInputContract) and sdui-parser's input-type-union: `Test Files 14 passed (14) · Tests 265 passed (265)`, VERDICT command-exit 0. The target file alone: 75 -> 109 tests. (2) FULL-REPO LINT, no narrowing to declare: `pnpm exec eslint . --format json` (one flat config at the repo root, the only eslint.config.* in the tree) -> `files linted: 3697 · errors: 0 · warnings: 10814` (pre-existing; the config's own note records that lint.yml sets no --max-warnings), VERDICT command-exit 0, held 4m01s. (3) `pnpm --workspace-concurrency=2 --filter @object-ui/console --filter @object-ui/plugin-grid type-check` -> both echoed their script (`type-check$ tsc --noEmit && ...`, so not the zero-match trap) and printed `Done`, VERDICT command-exit 0 — after `pnpm --filter '@object-ui/console^...' build` (the dependency closure a fresh worktree needs; without it plugin-grid's tsc reported TS2307 on every @object-ui/* import, which is the unbuilt-closure artifact, not a finding). (4) gates derived from package.json + .github/workflows/ for this diff, each exit captured by redirect: check:control-bytes 0, check:phantom-deps 0 (the two new imports, @object-ui/sdui-parser and @object-ui/types, are already console deps), check:self-import 0, check:esm-specifiers 0, check:vi-mock-specifiers 0, check:spec-symbols 0, check:doc-fences 0, scripts/check-changeset-presence.mjs 0 (empty-frontmatter changeset — objectui has no skip-changeset label mechanism). Plus a manual control-byte scan of all four changed files: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` exit 1 (no match).\n\nTWO NON-ZERO, BOTH ENVIRONMENTAL, NEITHER REACHABLE BY THIS DIFF: check:eager-closure exit 2 — 'No eager-closure report at apps/console/dist/eager-closure.json ... This is a broken gauge, not a passing budget' (it is written by a console `vite build`, which CI's performance-budget job runs); scripts/pm/check-half-states.mjs exit 3 — 'PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential ... It is not a clean board and it is not a dirty one — it is no reading at all'. CI is the authority on the rest of the farm; per the dispatch contract this report is delivered at draft-PR time and does not wait for it.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #6206: `element:number.filter` declares the arm 'array' (as does every other filter input in the repo) while ComponentPropsMap['element:number'].filter is a record/object and refuses an array outright — the renderer is an opaque passthrough, so which side moves is a contract question, not an implementation detail",
"filed as #6207: `object-grid.data` — two spec authorities disagree about the KIND (ObjectGridSchema.data resolves to ViewDataSchema, an object discriminated on `provider`; ComponentPropsMap['object-grid'].data is z.array(z.unknown())), so no declaration can satisfy both; the 'object' arm is the deliberate one from #5090 / PR #5108 and flipping it back re-opens that card — convergence is upstream"
]
}
Activity
Merge origin/main into claude/issue-3832-componentinput-union
os-support-ai commentedon Aug 19, 2026
Triage first-touch — concentrated round #2 (triage seat, session
session_019ZyCHcjXeTFPiS8RWRk4nN, 2026-08-19): promotedfinding→pm:queue· Task · tooling, scoped to the card's own lean: direction (b) — a one-way assertion that every declared arm set ⊆ what spec accepts on that key (kill the fake-arm silent-widening mode #3832 newly made possible; measured live by mutation ③b, 856 tests green on a spec-rejected arm). Narrowing stays per-block discipline — it is at least noisy. Direction (a)'s full bidirectional probe matrix and the enum/member-shape granularity question (#3795) are out of scope; price them separately if (b)'s implementation surfaces the need. Gate addition, accept-set unchanged — within lane discretion. Size/model suggestion: M, opus (derivation logic against specsafeParseneeds care to avoid false reds on enum arms — the card's own caveat).Generated by Claude Code
yinlianghui-tw commentedon Aug 25, 2026
PM claim + dispatch order — R27
Claimed by the
domain:devx@ objectui execution seat (#5748), PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe. Branch:claude/issue-4971-declared-arm-subset-gate.Direction (b), per triage, and the asymmetry is the reason
⛔ Not (a) — the full bidirectional probe matrix and the enum / member-shape granularity question (#3795) are out of scope; price them separately if (b)'s implementation surfaces the need. ⛔ Not (c) — writing the discipline into AGENTS.md relies on the author remembering, which is #3809's tombstone blind spot by the same root cause.
(b): assert that every declared arm set is a SUBSET of what spec accepts on that key. One direction only.
⭐ The card's own argument for the asymmetry is what makes this the right cut, and it should go in the test header:
declared = enforcedinverts, and nothing says so.#3832 made
typecarry arm arrays, which is what created the widening mode. Before it, narrow-only was the sole failure and it was self-announcing.⭐ The measurement that defines "done" — reproduce mutation ③b
The card ran the discriminating pair. Use it as the acceptance test:
'object'arm toelement:text_input.defaultValue(spec rejects it there): the per-block testtext-input-inputs-spec-parity.test.tsalready reds today. ⛔ It must still red — do not trade per-block coverage for the new gate.'number'arm topage:card.title(spec rejects a numeric title): 856 tests pass, entirely green today. After your change this must RED. That single reading is the card.The difference between them is only that
text_inputhappens to have a per-block test andpage:carddoes not — so today the property holds by per-block discipline, not by a gate. Closing that is the deliverable.Triage's size note calls it out — "derivation logic against spec
safeParseneeds care to avoid false reds on enum arms", and the card's own caveat says enum arms and nested shapes must either be exempted or given their own criterion.⛔ Do not let an enum arm read as a fake arm. Coarse-type probing against a spec schema that accepts only specific members will report the coarse type as rejected, and the arm will look invented when it is correct. Decide the treatment, implement it, and state it explicitly — an exemption that is silent is a hole; an exemption that is listed is a scope boundary.
page:card.title's'number'arm slips through.Non-vacuity
coveredset and exemption discipline inregistry-inputs-spec-parity.test.ts— ⛔ do not fork a second classifier. That is the mistake [finding] 四处读注册键表的钉子正则只认单引号,而本仓没有任何机制强制单引号 —— 一个双引号的 register 会被静默漏读 #4894 is fixing one gate over, and this lane rejected the same shape there tonight.git checkout HEAD --plus the explicit path, proven with an emptygit diff HEAD.Scope
The gate addition in
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts(or a sibling sharing itscoveredset). ⛔ Accept-set unchanged — this card adds an assertion, it does not change what spec accepts or what any registration declares. There are zero fake arms today (#3832's five arms were each measured against spec and the renderers), so a red on arrival is a finding: report it, do not "fix" a declaration to make your gate green.Root vitest only (objectui#3378). Exit codes captured by redirect before any pipe.⚠️ Derive the gate list from
package.jsonand.github/workflows/rather than from this order.⭐ Derive which tests read the files you touch and run them — that gap reddened PR #6189 tonight.
Generated by Claude Code
yinlianghui-tw commentedon Aug 25, 2026
os-dev-report
{ "issue": 4971, "status": "done", "branch": "claude/issue-4971-declared-arm-subset-gate", "pr": "https://github.com/objectstack-ai/objectui/pull/6220", "premise_still_valid": true, "summary": "Added the one-directional arm gate to apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (direction (b)): every coarse arm a covered block declares on a key must be a KIND @objectstack/spec accepts there, over the file's existing `covered` set, `specTopLevelKeys` and exemption discipline — no second classifier, accept set unchanged, no declaration edited. The judge reads the parse ISSUES rather than the boolean: scoped to the key (so a required sibling cannot speak for it), recursing through a union's branches (their paths are RELATIVE to the union node — a judge matching absolute paths refutes nothing and passes every union key, including the card's own reading), and refuting only a KIND refusal. The enum trap is handled as a RULE, not a silent exemption: `invalid_value` counts as a kind refusal only when NO listed value shares the probe's kind, pinned in both directions on `record:quick_actions.variant` ('Account' -> refuses-content so the correct 'string' arm is not called invented; 42 -> refuses-kind so an enum contract cannot launder a number arm). An `enum` ARM is judged exactly (every declared member must be accepted). Two arm kinds are exempt and listed: 'slot' (names a child position, not a value — `armAccepts` admits everything for it) and an 'enum' arm with no declared members (admits nothing). RED ON ARRIVAL on two SINGLE-arm declarations that predate #3832's array form and had never been compared to the contract — reported, not fixed: filed #6206 (element:number.filter) and #6207 (object-grid.data), each carried as a reasoned, issue-backed exemption that the stale-exemption test deletes the moment either side moves.", "tests": "ALL AT 1f205b1f4 (the pushed HEAD; every mutation restored to it, tree clean, `git diff HEAD` empty). Each heavy run went through scripts/pm/os-verify-lock.sh; every reading is taken from its VERDICT line or from an exit code captured by redirect BEFORE any pipe, never a bare $?.\n\nMUTATION 3b (the card's definition of done) — `page:card.title` + a 'number' arm: proven on disk by anchored counts (injected-anchor 0->1, removed-anchor 1->0 on the unique full-line anchor in a dry run, plus the printed line 788), restored by `git checkout HEAD -- <path>` with an empty `git diff HEAD`; the script carried `trap ... EXIT INT TERM`. RESULT: `× page:card declares no arm the spec refuses outright / AssertionError: page:card.title:number — the contract refuses the KIND itself — probe verdicts [\"refuses-kind\"]: expected [ 'page:card.title:number' ] to deeply equal [] / Tests 1 failed | 108 passed (109)` — this was ENTIRELY GREEN (856 tests) before the change. NO REBUILD NEEDED AND NONE CLAIMED: apps/console/vite.config.ts:424 aliases @object-ui/components to packages/components/src, so the mutation reaches the run from source without passing through dist/ — the dist-staleness failure mode does not apply here, and that alias is the evidence.\n\nMUTATION 3a — `element:text_input.defaultValue` + an 'object' arm, same on-disk proof (injected-anchor 0->1, target-anchor 1->0) and same trap/restore: the per-block pin STILL reds, unchanged — `× DECLARES both arms of the spec union, and only those arms / AssertionError: expected [ 'number', 'object', 'string' ] to deeply equal [ 'number', 'string' ]`, plus `× \\`defaultValue\\` is NOT part of the trio` and the new gate's own `× element:text_input declares no arm the spec refuses outright`. `Tests 3 failed | 121 passed (124)`. No per-block coverage was traded for the gate.\n\nCENSUS (measured by a declared, trapped, anchored flip of the non-vacuity floor, then restored — `git diff HEAD` empty): `blocks 27 · keys judged 215 · arms judged 229 · exempt(slot — describes a child position, not a value) 5 · exempt(enum arm with no declared members — admits nothing) 0 · refused 2 · registered exemptions 2`.\n\nGREEN RUNS: (1) `pnpm exec vitest run --maxWorkers=2` over 14 files — the 3 touched plus every in-tree arm/parity pin (text-input, record-picker, page-container, page-header, recordDetails/RelatedList/Highlights, spec-tombstones, public-contract, ga-honoured-inputs, component-input-union-specimens, gridDataInputContract) and sdui-parser's input-type-union: `Test Files 14 passed (14) · Tests 265 passed (265)`, VERDICT command-exit 0. The target file alone: 75 -> 109 tests. (2) FULL-REPO LINT, no narrowing to declare: `pnpm exec eslint . --format json` (one flat config at the repo root, the only eslint.config.* in the tree) -> `files linted: 3697 · errors: 0 · warnings: 10814` (pre-existing; the config's own note records that lint.yml sets no --max-warnings), VERDICT command-exit 0, held 4m01s. (3) `pnpm --workspace-concurrency=2 --filter @object-ui/console --filter @object-ui/plugin-grid type-check` -> both echoed their script (`type-check$ tsc --noEmit && ...`, so not the zero-match trap) and printed `Done`, VERDICT command-exit 0 — after `pnpm --filter '@object-ui/console^...' build` (the dependency closure a fresh worktree needs; without it plugin-grid's tsc reported TS2307 on every @object-ui/* import, which is the unbuilt-closure artifact, not a finding). (4) gates derived from package.json + .github/workflows/ for this diff, each exit captured by redirect: check:control-bytes 0, check:phantom-deps 0 (the two new imports, @object-ui/sdui-parser and @object-ui/types, are already console deps), check:self-import 0, check:esm-specifiers 0, check:vi-mock-specifiers 0, check:spec-symbols 0, check:doc-fences 0, scripts/check-changeset-presence.mjs 0 (empty-frontmatter changeset — objectui has no skip-changeset label mechanism). Plus a manual control-byte scan of all four changed files: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` exit 1 (no match).\n\nTWO NON-ZERO, BOTH ENVIRONMENTAL, NEITHER REACHABLE BY THIS DIFF: check:eager-closure exit 2 — 'No eager-closure report at apps/console/dist/eager-closure.json ... This is a broken gauge, not a passing budget' (it is written by a console `vite build`, which CI's performance-budget job runs); scripts/pm/check-half-states.mjs exit 3 — 'PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential ... It is not a clean board and it is not a dirty one — it is no reading at all'. CI is the authority on the rest of the farm; per the dispatch contract this report is delivered at draft-PR time and does not wait for it.", "open_questions": [], "out_of_scope_findings": [ "filed as #6206: `element:number.filter` declares the arm 'array' (as does every other filter input in the repo) while ComponentPropsMap['element:number'].filter is a record/object and refuses an array outright — the renderer is an opaque passthrough, so which side moves is a contract question, not an implementation detail", "filed as #6207: `object-grid.data` — two spec authorities disagree about the KIND (ObjectGridSchema.data resolves to ViewDataSchema, an object discriminated on `provider`; ComponentPropsMap['object-grid'].data is z.array(z.unknown())), so no declaration can satisfy both; the 'object' arm is the deliberate one from #5090 / PR #5108 and flipping it back re-opens that card — convergence is upstream" ] }Draft PR: #6220
Generated by Claude Code