Repository navigation
fix(pm): check-widening-tells pays a tombstone re-declared through a transparent wrapper - #21016
Conversation
…transparent wrapper A change block that removes `key: retiredKey(` and adds `key: marker(retiredKey(` re-declares the same tombstone; the marker hands back the instance it is given. The removed tombstone deliberately buys no T1 unit (un-retiring must fire) and the added line is not read as a tombstone because a wrapper can put a live arm on what it returns, so the line fired a false T1. The net-delta block now records each removed tombstone's KEY in a second currency, spent only by an added line that re-declares that same key as a tombstone through one wrapper the head blob proves transparent (it returns its first parameter, bare, and has no other return). Read before the budget, never a T1 unit: un-retiring, a renamed key, a new key in a wrapper, and every widening wrapper still fire. Corpus: 4,914 rows against 4,914 over 1,932 commits, 0 differing. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…closes on its line Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
…dening-tells-wrapped-key
…rries the landed pair Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs read: card #20969 (body; comments ① Derived judgmentsAccept-set / public-surface changes the diff implies: none. The one file is a root-private script ( The repair, walked in the head tree — right.
The transparency proof — sound for the shapes the brief names. Pins and wiring — right. 28 The corpus claim — plausible from the method stated. In this clone (shallow, boundary Sentences tested against the tree. True: "the thirteenth accidental variable" (twelve prior sections counted in the header); new-file line 932; blob
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Adopted and posted by
Generated by Claude Code |
…not_planned at first grade, citing ruling 208 instrument-discipline.md says a report-only instrument gets no card, no dev and no PR, and that its only in-flight work is deletion (ruling 208). Nothing on the triage path read that line, so the grade never asked it. One sentence in the grading block of references/triage-duties.md now does: a card whose fix lands in a report-only instrument (the widening tell, the half-state patrols) closes not_planned at first touch, citing ruling 208, and the instrument's only in-flight work is deletion. No new gate, label or script. Enumeration pin (the two instances where the line was not read; a third is a red reading of this sentence, not a new card): - #20969 / PR #21016 (landed) - #21465 / PR #21679 (held) Paid in place, net 0 lines (120 -> 120, ceiling unchanged, no re-wrap): the deleted line is the triage-side restatement of the filing door's four classes; its owning copy stays in references/filing-gate.md (the class list and the refusal of everything else, one line, with the four class heads on the lines below it), reached from the pointer line kept right under the deleted one. Claude-Session: https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk Co-authored-by: Claude <noreply@anthropic.com>
…t_planned at first grade, citing ruling 208 (objectstack-ai#21691) Fixes objectstack-ai#21681 Clause-②: no Tier S (`.claude/**` only). Draft; this run does not flip it ready, queue it or arm auto-merge — the owning seat lands it after its at-tier contract review. ## What changed One protocol sentence in the grading block of `.claude/skills/pm-dispatch/references/triage-duties.md`, as the triage ruling on the card (comment 5976725561) specifies: a card whose fix lands in a report-only instrument (the widening tell, the half-state patrols — the judge-intent class `instrument-discipline.md` names) is closed `not_planned` at first touch, citing ruling 208, and the instrument's only in-flight work is deletion. No new gate, label or script. It is paid in place: one restated line of the same file is deleted, so the file stays at 120 lines under its 120 ceiling. No other line is touched (no re-wrap), and `SKILL.md`, `instrument-discipline.md` and `scripts/pm/check-skill-line-ratchet.mjs` are untouched. The enumeration pin — the two instances where the rule was not read — is named in the commit message: objectstack-ai#20969 / PR objectstack-ai#21016 (landed) and objectstack-ai#21465 / PR objectstack-ai#21679 (held). A third such card is a red reading of this sentence, not a new card. ## Reading 1 (读数一): placement and payment Tree: `objectstack-ai/objectstack` at `bc85776a87` (branch head), base `251a7dd4b4`. - **New sentence, line 64** (120 bytes; the line cap is 120), directly under the two existing close-at-grade lines (the 「无则关」 line and the open-P0/P1 `tooling` line), so the grading block now carries all three close-at-grade cases together: ```text - 只报告仪器(放宽 tell、半状态巡查)的修复卡首触即关 not_planned,引裁决 208;在途只有删除。 ``` - **Deleted line, was line 40** — the triage-side restatement of the filing door's four classes: ```text - 立卡门四类:① 缺陷 / ② 维护者决定 / ③ 直派任务 / ④ 协调节点,⛔ 其余一律不立卡。 ``` - **Its owning copy, kept on the reading path:** `references/filing-gate.md` line 11, the same rule (four classes only, everything else refused), with the four class heads on lines 12 / 16 / 17 / 18: ```text - 立卡只为四类,正文首行写立卡门类别 ①–④;⛔ 其余一律不立卡,不论它叫什么。 - ① 有具名落点或复现的产品缺陷,即 `pm:queue` 的定义;其内的 `finding` 限三类且带 `reach:`。 - ② 只有维护者能做的决定,落卡即带「维护者速读」与四棱块。 - ③ 维护者直派的任务,正文引其原话。 - ④ 协调节点:跨仓/跨层父单与它的逐层子单。 ``` The triage reader reaches it through the pointer line kept right under the deleted one (now line 40): `首行写立卡门类别 ①–④;四类定义、⛔ 清单、三答与配额见 references/filing-gate.md`. The deleted line entered the protocol as the summary of that file, in the same commit that created `filing-gate.md` (`37ed9ae04b`), and moved here verbatim with the principle-only cut; it is a restatement rather than a ruled clause: the ruled clause, with its provenance quotation, lives in `filing-gate.md` and is unchanged. - **Line count:** 120 → 120 (ceiling 120, headroom 0, unchanged). Diff: 1 file, +1 / −1. ## Reading 2 (读数二): gate output Run at `bc85776a87`, after the final commit, exit codes captured before any pipe: ```text pnpm check:pm-skill-ratchet exit 0 ✓ check-skill-line-ratchet self-test: 157 cases pass. ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/triage-duties.md: widest table row is 0 bytes (pin 0; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/triage-duties.md is 120 lines (ceiling 120; headroom 0). ✓ check-skill-line-ratchet: declared cross-file moves: 2, total ceilings down 477 lines. pnpm check:pm-skill-id-lint exit 0 ✓ check-skill-id-lint self-test: 14 cases pass. ✓ check-skill-id-lint: 34 file(s) clean (pattern /#[0-9]{3,}/g). ``` `ruling 208` is spelled without a hash prefix, the way `instrument-discipline.md` records it; the issue and PR numbers of the pin are in the commit message only. The full derived set (`node scripts/pm/dispatch-gates.mjs --commands`, no paths, change set read from the merge base) is the same 18 commands the dispatch listed, all exit 0. `--ran` reconciliation: `18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN` (a derived zero — every line carried its exit code). One first-run `exit 3` is not a measurement: `check:doc-formula-expressions` refused with PREREQUISITE NOT MET (unbuilt `@objectstack/formula` / `@objectstack/lint`); after the build it names (run under the shared verify lock, `VERDICT command-exit 0`) it exited 0. `check:nul-bytes` exit 0, plus a control-byte self-scan of the edited file: zero hits. No changeset: `.claude/**` publishes nothing. ## Acceptance notes - **Wording differs from the seat's suggested line, meaning unchanged.** The suggestion measured 152 bytes against the 120-byte line cap. The landed line drops the explicit `instrument-discipline.md` pointer; both of its key terms still lead there (「只报告的仪器」 is that file's rule line, and 「裁决 208」 its ruling record), and `reading-discipline.md` keeps its pointer to it. 「在途只有删除」 echoes that file's own 「它的在途工作只有删除」. - **The class is the instrument's role, not the path.** The sentence covers a fix to a report-only, judge-intent instrument. A fix to a hard-gate face that happens to share a file with a patrol is outside it, and the sentence does not widen the class beyond the two instruments the ruling names. Observation only, nothing filed. carrier: the triage seat, applying the sentence at grade time. - **Not decided here, as the ruling lists it:** whether the code PR objectstack-ai#21016 landed is deleted under the report-only rule is the maintainer's question; this PR does not touch `scripts/pm/check-widening-tells.mjs`. ## 维护者速读(草稿) **改了什么:** 分诊职责文件 `triage-duties.md` 的定级段加了一句:修复落在「只报告仪器」(放宽 tell、半状态巡查)上的卡,首次定级就以 not_planned 关闭,并引裁决 208;这类仪器的在途工作只有删除。同时删掉同一文件里一行重复的「立卡门四类」摘要(完整原文仍在 `filing-gate.md`),文件仍是 120 行,行数上限不动。 **为什么改:** 裁决 208 早就规定只报告的仪器不立卡、不派开发、不开 PR,但分诊定级时并不读这条,于是两张这类卡被定级入队、派了开发,一张已经合并,一张被扣住,白花两轮开发。把规则写进分诊每次都读的定级段,首次定级就能拦住。 **风险与代价(含回滚):** 只改内部协议的一行文字,不新增门禁、标签或脚本。风险是分诊把与巡查同文件的硬门禁修复也误关,句子按「只报告」身份判断而不按文件路径,可以避免。已经合并的那份代码要不要删,不在本 PR 范围,留给您另行决定。回滚就是撤销这一个 commit。 **席位意见:** **你要做的:** 无需操作:本 PR 是 Tier S(只改 `.claude/**`),席位复核通过后经合并队列落地;不同意请回一句。 --- _Generated by [Claude Code](https://claude.ai/code/session_01CB6W87z22K2yjUCDyVrJRk)_ Co-authored-by: Claude <noreply@anthropic.com>
Closes #20969
Clause-②: no (a PM enqueue gate's verdict changes; no published package's accept set or public surface moves, per the question in
scripts/pm/clause2-line.mjs)What
scripts/pm/check-widening-tells.mjsraised a false T1 on PR #20961's diff: the change block removesbody: retiredKey(and addsbody: retiredComponentSlot(retiredKey(. That is the same tombstone with a marker call around it, and the marker hands back the instance it is given. This PR teaches the #16943 net-delta block to read that pair as a replacement. The pair's two true T3 rows still fire.On PR #20961's diff (
git diff 75519e1c0a...d9d0d38cfc,--declaration no):dfe5a0863f)ui.json:320, T3ui.json:513, T1component.zod.ts:9320fd8e1969f)ui.json:320, T3ui.json:513--declaration yesThe mechanism, re-derived (this corrects the filing)
The filing guessed that the wrapper hides the line's shape from the budget. It does not: the same hunk re-declares two live keys through the sibling marker
componentSlot((children,footer), and the budget already pays for both. Two existing readings each declined correctly on their own evidence, and that left nothing to pay with:retiredComponentSlot(, and correctly refuses to call the line a tombstone on that evidence alone. [finding] check-widening-tells T1 is still blind to FILE-LOCAL declaring factories — a new writable key throughplaceholderFree(on objectstack's own judged surface passes aClause-②: noin silence #18702's resolver classifies the factory aswritablebecause it returns its own argument, and a wrapper that returns its argument can still put a live arm on it.The repair
The repair follows triage's direction: the #16943 block reads a removed key and a re-added key of the same name as a replacement. It keeps that credit in a second currency and never converts it into a T1 unit:
retiredmap.wrappedTombstoneKeyreads it as a tombstone of that same key, re-declared through one wrapper thattransparentFactoryproves is transparent. Transparent means the definition has exactly onereturn, and that return is the bare first parameter with nothing chained onto it. The proof is read from the same head blob, in the same pass, by [finding] check-widening-tells T1 is still blind to FILE-LOCAL declaring factories — a new writable key throughplaceholderFree(on objectstack's own judged surface passes aClause-②: noin silence #18702's resolver."Whatever wraps the value" holds for every wrapper the diff proves transparent, and for no other.
return z.string().or(schema),return schema.or(z.string())and a braced branch returning a live schema all hand a tombstone back with a live arm on it. Paying for those would let #17955's un-retiring leg go quiet behind a prefix. A wrapper this reader cannot certify keeps the tell firing.Pins: self-test battery
#20969 …, 28 cases, each ablatedEach ablation is one anchor through
scripts/ablation-replace.mjsin WRAP mode, which arms the restore on exit/INT/TERM. All five were run at0fd8e1969f. After each one, the restore was proven: blob back to29c508a2522f(the HEAD blob) andgit diff HEADempty.false)T1 component.zod.ts:932again (3 rows, exit 4)bodytombstone removed,panelre-declared)body: retiredComponentSlot(z.array(…)))lenientSlot,chainedSlot,branchySlot) still tellOther controls in the battery: a dark control (the same added line with nothing removed still fires), live arms chained onto the wrapper or onto the tombstone, one-for-one, block-scoped, never-spends in both orders, and the imported-wrapper boundary (#18702's stated, reported silence, unchanged).
Measured price
The corpus is the history present in this shallow tree. One graft boundary,
2e8bd8322b, was excluded. Every commit went throughwideningTellswithheadBlobSourcelive, and a known blob was probed first. Up toa5bce40888: 1,979 non-merge commits touchpackages/spec/src, 1,933 of them carry a non-test.tsdiff, giving 5,865 file diffs. Running the merge-base version against this version: 4,915 rows against 4,914, one differing commit, one row moved. That commit is315888d660, PR #20961 as it landed, and the moved row is the false T1 onPageCardProps.body. No other row moves and none starts firing.Gates (all at
0fd8e1969f, the final head)node scripts/pm/check-widening-tells.mjs --self-test: exit 0, 553 cases pass (525 before this PR, plus 28).dispatch-gates.mjs --commands --repo objectstack-ai/objectstackunion: 30 derived, 29 run, all exit 0.--ranreconciliation exit 0:30 derived famil(ies) accounted for — 29 run, 1 NOT-MEASURED.pnpm check:pm-dispatch-gates, because its self-test passes the container's foreground cap (timeout 560, exit 124). Declared to CI.npx eslint --no-inline-config --format json scripts/pm/check-widening-tells.mjs: 1 file, 0 errors, 0 warnings. The config for this file (--print-config) has noparserOptions.project, so linting is not type-aware and this diff cannot change any untouched file's verdict. The repo-widepnpm lintis left to CI.Acceptance notes
a5bce40888, 8 of 6,388 top-of-line definitions onpackages/spec/src/**read as transparent, and none of them mutates what it returns. [finding] check-widening-tells fires T1 on a retiredKey() tombstone line, so every ADR-0087 key retirement reads as a clause-2 widening for the one reason the accept set shrank #17955's own residual (a live arm chained on the closing line of a multi-line tombstone) also reaches inside a wrapper unchanged. Its population is 0, and its overturn condition is unchanged.origin/mainwas merged into the branch ata5bce40888, which brings in PR feat(spec,lint): one derived list of page-component slot positions, read by all three page walks (#20940) #20961 itself. No conflicts, and main had not touched this script.scripts/pm/**ships in no package, soskip-changesetapplies.definitionSites/localFormPatterninterpolate a factory name into a RegExp without escaping$, so a file-local factory named with$is reported unresolved (IMPORTED reason) instead of resolved. Population: 0$-named definitions underpackages/spec/srcat0fd8e1969f. A stated silence, not a quiet one; carrier: none (dev report5923104992).Generated by Claude Code