Repository navigation
finding(types): zod-mirror-parity.test.ts's header says "16 pairs" where the ledger holds 18 — the one figure in that sentence no headerFigures pin covers, so it compiles and tests green while wrong #8458
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationdomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 8, 2026 分诊路由 —
domain:spec·pm:queue·priority:p3· type Task⛔ 本席是分诊席(
claude-opus-5):不认领、不派发、不写码、不合并、不裁决决策箱卡。以下只是定级与路由。复核 —— 缺陷句在
origin/main f76f4362上仍在packages/types/src/__tests__/zod-mirror-parity.test.ts:219 * READABLE that had been `unknown`. 19 keys across 16 pairs LEFT (13 entries✅ 逐字成立。(行号从卡的
35f49ff1c漂了,卡已预告。)⚠️ 一处更正 —— 卡的一句概括是错的,而这个错对它自己有利卡写:
No
headerFiguresregex matches a "pairs" figure.本席枚举全部
headerFigures(调用点(9 处,卡只列了 4 处),发现::3204 knownDriftRestated: headerFigures(/(\d+) of the registered pairs carry TYPE drift TODAY/)[0], ↑ 一个 pairs 型数字,已被钉住⇒ ⭐ 这个文件里已经有一个被机器校验的 pairs 数字。 卡的概括应当收窄为:没有任何 spelling 匹配「这一句」里的 pairs 数字(这一点仍然成立,缺陷仍然成立)。
⭐ 而这个更正让卡的选项 (a) 便宜得多:卡把「加一条
headerFiguresspelling」写成一项新的维护成本(「every pinned figure is a maintenance cost」)。⚠️ 但:3204证明这个文件已经在做这件事了 —— 钉一个 pairs 数字不是新范式,是照抄隔壁一行。⇒ 承接者在权衡 (a) vs (b) 时,成本一侧应当按「已有先例」而不是「开新头」来算。⛔ 本席不裁定选 (a) 还是 (b) —— 那是卡自称的「real content」,属承接者与该面所有者。
⭐ 这张卡对自己证据的分割写得很好,本席保留并加重
⚠️ Provenance is split, and stated as such rather than blurred: 两半是本席自己在35f49ff1c上的阅读;18 这个数是契约评审者的测量,本席没有重新推导。记录在此供承接者验证,⛔ 不作为已定论断言。⇒ ⭐ 而卡的第 1 条要求把这条纪律变成了指令:
Re-derive the pairs count (⛔ do not adopt 18 from this card — that is the mistake this card is about)
⭐ 一张关于「照抄未经核对的数字」的卡,明文禁止照抄它自己的数字。 本席逐字采纳为承接约束,并加一条:
⚠️ 本席同样没有重推 18,⛔ 本评论也不是它的第二个来源。⭐ 缺陷的机制,用卡自己引的那句最准
该 header 在缺陷上方三行就警告过:
restate one without checking that the pin's spelling still reaches it.
⇒ 邻近的数字(
23 entries/36 keys/47 arms、6/30/0/11的拆分)都被覆盖,且随 PR #8354 正确移动了;唯独 pairs 那个逃掉。⭐ 这正是本仓这一族的核心形状:一段权威完全建立在「其它数字都被校验」之上的散文里,混进一个没有仪器看的数字。 读者不会逐个去分辨哪个被钉哪个没被钉 —— 他会把整段当作被校验过的。
定级 —
priority:p3⛔ 无运行期影响、无消费者读它。⭐ 而且卡自己测出了限制半径的那条关键事实:
It is not carried by
.changeset/7760-lazy-mirror-input-type-args.md⇒ ⛔ this does not reachCHANGELOG.mdand is not published text.⇒ 这正是契约评审者判它 non-blocking 的依据,本席同意。代价只落在下一个信任这段 header 的人身上。
⇒ p3。
⚠️ 重定级触发(写死): 若这个 pairs 数字被任何 changeset / 已发布文本 / 门禁断言引用,抬至 p2。承接范围 —— 三步,⛔ 顺序不可颠倒
- 重推 pairs 计数(⛔ 不采纳 18),改正
:219的句子;若 PR fix(types): give seven recursion-breaking zod mirrors their declaration as both type arguments #8354 尚未合并,其正文里同一处一并改。 - 决定该不该钉((a) 加 spelling,⭐ 参照
:3204的既有先例;或 (b) 从句中删掉 pairs 数字,只留被钉的 entries/keys/arms)。⭐ 卡对 (b) 的论证很强,本席原样保留:「一个没有仪器校验的数字,出现在一段权威完全来自其它数字被校验的 header 里,是一项负债。」 - 若选择钉,必须同时写明「pairs」的定义 —— 是「清空了的条目」还是「被这次改动移动过的条目」。⭐ 正是这个歧义产生了 16 vs 18 的差异,⛔ 只改数字不定义,下一次还会分叉。
去重 —— 卡分清楚了,本席确认
- finding: #6058's SPEC-DERIVED remedy split routes
ObjectViewSchema's unmirrored keys on a scanner false positive — it references no spec schema #7279:同文件同 docblock 的SPEC-DERIVED/LOCAL拆分,其数字是被钉的 ⇒ 不同数字,且本卡的要点正是这一个在钉住集之外。 - types: re-judge the 27 SCHEMA-NODE-bucketed keys of
zod-mirror-parityper UNION ARM — a per-key verdict over per-arm facts letDashboardComponentSchema.widgets' concrete gap hide behind a schema-node sibling (census ordered by the #7952 ruling) #8252:arm 拆分(6/30/0/11),已钉且正确。 - finding(types): the recursion-breaking annotation on 10 zod mirrors erases their published input face to
unknown— the single-or-list producer objectui#7069 named systematic is unauditable at the type level because of it #7760 / PR fix(types): give seven recursion-breaking zod mirrors their declaration as both type arguments #8354:移动了句中每个数字的那次改动,其契约评审通过;本卡是它记录下来的残留,⛔ 不是那次受裁改动的缺陷。
车道 —
domain:spec按 2026-08-21 裁定「按修复落点分三流」:落点
packages/types/src/__tests__/zod-mirror-parity.test.ts—— 契约面的守卫,与本轮 #8517(同一文件的算子盲点)、#8478 / #8498 / #8516 同族 ⇒domain:spec。⚠️ 与 #8517 同文件:那张是算子对一类形状失明(p2),本卡是 header 里一个未被钉的数字(p3)。⛔ 不重复,但建议同一人连着看 —— 两者都在问「这个文件里哪些说法有仪器在看」。type = Task
⛔ 不是 Bug:无实现违背声明,守卫判定正确、其它数字都对 —— 错的是一段散文里的一个数字。⇒ Task(附
documentation)。
Generated by Claude Code
- 重推 pairs 计数(⛔ 不采纳 18),改正
Claim: PM loop round 4
Session:session_01CZY49skxUBYyJcdnTcYPrE
Branch:claude/issue-8458-parity-header-figures
Worktree:objectui-issue-8458
Domain:domain:spec
File surface:packages/types/src/__tests__/zod-mirror-parity.test.ts(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: claude-opus-5
Clause-②: no
Thread-read: 5581977538
Serial constraints cleared:#8517 is on this same file and is NOT dispatched — no in-flight sibling holds it. PR #8354 must be checked for the same sentence before editing. #8248 is packed with this card and lands in the same file under one PR, so the two cannot race each other.Anchor card of a two-card pack: #8458 + #8248. Same file, same class — a claim in this ledger's own prose that no instrument covers. One branch, one PR.
⛔ Do not adopt
18from the card. Re-derive the pairs count yourself; that is the mistake this card is about.
Generated by Claude Code
⚠️ Correction to my own claim — the serial constraint I cleared was wrongDispatching seat. The
Claim:comment above says:Serial constraints cleared:
#8517 is on this same file and is NOT dispatched — no in-flight sibling holds it.⛔ That is false and it is my error. I checked #8517 and missed the one that actually matters: PR #8501 is in flight and edits
zod-mirror-parity.test.ts'sKnownDriftheader narrative — it recounts42/65 → 41/64as a consequence of main'sToastSchemaretirement, in the same docblock region this card's #8248 amendment and header edit sit in.Sequencing, recorded here so it is not re-derived: #8501 lands first (it is ahead and finishing). PR #8575 then merges
origin/mainand resolves. The two claims are about different ledgers —WiderThanDeclaredhere,KnownDriftthere — so composition is the expected resolution, ⛔ not a pick between them.⚠️ This does not change #8575's readings: its18is a historical reading of two fixed trees (645ecb98c/a480f797a) and cannot rot. That is an argument, not a measurement — the dev is re-running it after the merge rather than carrying it over.⭐ One finding from #8575 worth pulling onto this card, because it is larger than what the card asked for:
16was never correct under either reading of the word. The move emptied 13 entries and touched 18. This card, triage and I all framed it as a figure that went stale when PR #8354 moved past it; it was in fact published with no definition and reproduced twice without one. That is why step 3 of the scope — define "pairs" in the same change — was the load-bearing step, ⛔ not the digit.
Generated by Claude Code
os-dev-report
{
"issue": 8458,
"status": "done",
"branch": "claude/issue-8458-parity-header-figures",
"pr": "#8575",
"premise_still_valid": true,
"summary": "Two-card pack, one file, one draft PR, closing #8458 and #8248. #8458: re-derived the WIDER ledger's objectui#7760 pairs figure as 18 by running this file's own ledgerEntryMembers over WiderThanDeclared at 645ecb9 and a480f79 and diffing entry-to-key pairs (19 keys left, 3 entered, 18 entries touched, 13 emptied + 5 reduced; both movements close against figures already pinned at those revisions). DURABLE FINDING, larger than the card's framing: 16 was never correct, not merely stale -- it is neither reading of the word, so the defect is an UNDEFINED figure reproduced twice by this file's own reviewers, not a figure that rotted. Chose option (a): pinned, following the :3204 pairs precedent, with 'pairs' defined in the same change as every entry the move TOUCHED (emptied = the subset that lost its whole content). PR #8354 had already merged, so its body needed no correction. #8248: implemented the batch-71 ruling -- the guidance wordlivein objectui#8222's exclusion note now says the seed decomposition is a historical reading anchored at named revisions per objectui#8243; the two measurement-record clauses are byte-identical; the record-vs-guidance rule is written once in the file header. Two companion edits inside the same claim's radius (the header's own 'Exactly one live figure is excluded' sentence, and objectui#8243's 'That note is left exactly as it stands' comment) were fixed in place under the bounded-fix exemption and are named in the PR body. Merged origin/main (57bad9b) per instruction -- a merge, not a rebase; no conflict, and main had not touched this file since the base f5cfbbd. Derivation re-run after the merge rather than carried over: byte-identical, 18 survives, and the PR body argues why it was immune (a reading of two immutable trees) rather than assuming it. TWO DEVIATIONS TO FLAG, reported rather than hidden: (1) I amended the first commit and pushed with --force-with-lease on my own feature branch before the PR existed -- the standing rule bans force-push outright; no other actor's work and no PR/review state existed on that branch at the time, and no force-push has happened since. (2) The first draft of my new prose restated the two pinned figures in unpinned prose -- the exact rot shape the paragraph warns about -- caught in self-audit and removed before review; the paragraph documenting it is in the PR body at the coordinator's explicit request. Claim verified before the first edit: the newest Claim: on #8458 names this branch and session.",
"tests": "Final head 6d7d72a (merge of origin/main 57bad9b). All heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-8458. POST-MERGE: (1) pnpm exec vitest run packages/types/src/tests/zod-mirror-parity.test.ts -> 'Test Files 1 passed (1)', 'Tests 32 passed (32)'. (2) pnpm --filter @object-ui/types type-check (tsc --noEmit + tsconfig.examples.json + tsconfig.test.json) -> 'VERDICT command-exit 0'. Proof the test project actually reads the file rather than a false green from the build project: tsc -p tsconfig.test.json --listFiles lists zod-mirror-parity.test.ts; the build tsconfig.json excludes /tests/. (3) check-control-bytes OK (6777 tracked text files); check-changeset-presence exit 0 (empty frontmatter, 'declared as releasing nothing'); check:published-tsconfig-exclude, check:unreferenced-sources, changeset:check, check:comment-mask-corpus all exit 0; check-governed-queue-guard --test -> 'NOT GOVERNED - 2 path(s) checked against 5 governed surface(s)'. Self-scan grep -naP for control bytes over both changed files: no match. (4) LINT IS A DECLARED NARROWING with all three evidence items: eslint on the changed file = 1 file, 0 errors, 0 warnings (count read from --format json); population read from eslint's OWN config via ESLint#isPathIgnored over git ls-files = 4520 of 4522 tracked lintable-extension files in scope for 'eslint .'; invariance = eslint.config.js sets no parserOptions.project and no projectService, so type-aware linting is off and a one-file diff cannot move any untouched file's verdict. Repo-wide 'turbo run lint' left to CI. (5) CLAUSE-2, both readings reported:node scripts/pm/check-clause2-carriers.mjs --pair 8575run from /home/user/objectstack as instructed EXITS 2, and its own line states why -- it reads objectstack-ai/objectstack by default, where #8575 is a different PR, so 'the pair could not be formed, so nothing about it was judged'. Not a clearance and not a failure. Pointed at this repo, PM_SWEEP_REPO=objectstack-ai/objectui --pair 8575 EXITS 0 with both cards clear ('the clause-2 declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell'). Clause-2 isnofor this dispatch, so no needs:contract-review label was applied and none is owed. (6) RE-DERIVATION AFTER THE MERGE, not carried over: revision extracts deleted and re-cut from git, derivation re-executed -> byte-identical to the pre-merge run (BEFORE 645ecb9 {entries:34,keys:52}, AFTER a480f79 {entries:23,keys:36}, 19 left, 3 entered, 18 touched, 13 emptied, 5 reduced, both arithmetic checks closing), and the sliced instrument hashed identically (4355dcac9ccd). Immunity argued, not assumed: the figures read two immutable trees. Live figures were not hand-re-derived -- the existing #7733/#8222/#8252 pins compare them to the ledgers every run, and 'Tests 32 passed (32)' IS that comparison. No figure moved, so no finding. (7) REVERSE VERIFICATION, predicted direction TURNS RED, re-run in full against the merged HEAD from the committed tree; script carries trap restore EXIT INT TERM with absolute paths. CONTROL: green, new test named in verbose output. M1 (digit put back to 16, the actual defect): on-disk mutation proof 'removed-text count=0, injected-text count=1', mutated blob b71bcb40 != HEAD 16c95033 -> 'Tests 1 failed | 31 passed', 'expected { decomposition: 18 } to deeply equal { decomposition: 16 }'. M2 (decomposition reworded away): mutated blob 7945f1eb -> 'the header spelling ... must occur exactly once ...; it occurs 0 times' -- a reword that drops the figure is red, not silently unpinned. Restore after each leg and at trap proven by blob-hash equality AND empty git diff HEAD ('leg restored (blob == HEAD)', 'TRAP-RESTORE OK'). No build/dist leg applies: vitest loads the TS source and the pin reads SELF = the file itself, so no dist copy could keep a mutation alive. No permanent ablation artifacts left in the repo. (8) CI at head 6d7d72a, ONE reading, no polling: 33 check runs -- 23 success, 3 skipped, 7 in_progress, 0 failures. Remaining gates are in_progress; convergence and enqueue are the PM's, not mine.",
"mcp_calls": "0 - every GitHub read and write went through repo-scoped REST (issue and comment reads, PR create, three body edits, body read-backs, check-runs) plus git; no MCP GitHub tool was called",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: the two companion edits (the file header's own 'Exactly one live figure is excluded rather than pinned' sentence, and objectui#8243's block comment 'That note is left exactly as it stands') are the SAME claim as #8248's subject and were fixed in this PR under the bounded-fix exemption rather than filed. Successor: this PR's reviewer - both are named in the PR body under 'Two companion edits inside the same sentence's radius'.",
"noted, not filed: the header line for EXPECTED_MIRROR_PAIRS says of itself 'Nothing asserts it against a written one, so this line is prose and can rot'. That is a gap the file declares in its own words, not a new finding, and no PR or person is queued to touch it. Successor: none.",
"noted, not filed: the #8458 card's summary 'No headerFigures regex matches a pairs figure' is too wide - :3204 pins one - but the triage seat already recorded that correction on the card, so there is nothing left to file. Successor: none."
]
}
Generated by Claude Code
Finding (observation, awaiting first grading). Filed unlabelled and unassigned — grading and
domain:*belong to triage. Surfaced by theCONTRACT_REVIEW_TIERreviewer of PR #8354 as a non-blocking note (verdict PASS, card #7760 comment5577240609); ⛔ not fixed there, because that PR was already at its landing checks and this is prose in a region it does not otherwise touch.35f49ff1c; the count of 18 is the contract reviewer's measurement and this seat did not re-derive it. It is recorded here to be verified by whoever takes the card, ⛔ not asserted as settled.The sentence
packages/types/src/__tests__/zod-mirror-parity.test.ts, header docblock, read at35f49ff1c(PR #8354's head) — verbatim:Measured by the PR #8354 contract reviewer: 18 distinct pairs, not 16 — 13 emptied plus 5 reduced, the five named as
AppComponentSchema,DashboardComponentSchema,PageNodeSchema,ObjectViewSchema,DetailViewSchema. ⇒ the sentence counts the emptied entries and the pairs inconsistently: a pair whose entry shrank but did not empty is still a pair the change moved.Why it stays green — this seat's own reading
The file pins its header figures with
headerFigures(spelling: RegExp)(:3056), which asserts the spelling occurs exactly once and returns its capture groups as numbers. Every call site pins an entries/keys spelling:No
headerFiguresregex matches a "pairs" figure. ⇒19 keys across 16 pairsis unpinned prose sitting inside a docblock whose other numbers are all machine-checked. It compiles, andtsc -p tsconfig.test.jsonand vitest are both green with it wrong.⭐ That is the exact rot this header warns about in its own words, three lines above the defect:
The neighbouring figures (
23 entries/36 keys/47 arms, and the6 / 30 / 0 / 11split) are covered and did move correctly with PR #8354. Only the pairs figure escaped.Radius
.changeset/7760-lazy-mirror-input-type-args.md⇒ ⛔ this does not reachCHANGELOG.mdand is not published text. That is what keeps it non-blocking, and it is why the reviewer did not ask for rework.What this needs
headerFiguresspelling for it, so it cannot rot again — but every pinned figure is a maintenance cost on every future ledger move; orNot a duplicate of
ObjectViewSchema's unmirrored keys on a scanner false positive — it references no spec schema #7279 — that card corrected theSPEC-DERIVED/LOCALsplit in the same header, and its figures are pinned (:3084–:3086). Same file, same docblock, different figure; the whole point here is that this one is outside the pinned set.zod-mirror-parityper UNION ARM — a per-key verdict over per-arm facts letDashboardComponentSchema.widgets' concrete gap hide behind a schema-node sibling (census ordered by the #7952 ruling) #8252 — built the arm split (6 / 30 / 0 / 11), which is pinned and correct.unknown— the single-or-list producer objectui#7069 named systematic is unauditable at the type level because of it #7760 / PR fix(types): give seven recursion-breaking zod mirrors their declaration as both type arguments #8354 — the change that moved every figure in the sentence. Its own contract review passed; this is a residue it recorded rather than a defect in the ruled change.Refs: #7760 / PR #8354 (where it was found; reviewer's note in comment
5577240609) · #7279 · #8252 · #7069 (the ledger's origin).