Skip to content

fix(pm): C5 reports a widening tell instead of refusing on it - #19495

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-19490-c5-report-only
Sep 21, 2026
Merged

os-steve merged 1 commit into
mainfrom
claude/issue-19490-c5-report-only

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #19490

What this changes

scripts/pm/check-clause2-carriers.mjs's C5 limb turned a widening tell into a hard refusal: a card declaring Clause-②: no whose diff carried tells made --pair N exit 4, and PD #14 makes --pair at 0 a conjunct of the landing predicate. C5 is now report-only — the row still prints, with its file:line evidence and its remedy text, and it no longer contributes to a non-zero exit.

⛔ C5 and nothing else. C6 stays a hard refusal, as do C2, C3, C8, C9 and the UNJUDGED exit-2 path.

The ruling this stands on

Maintainer ruling of 2026-09-21, recorded at #18917 (comment). Quoted verbatim, untranslated, because paraphrasing a ruling rewrites it:

阻碍我的pr落地,导致 agent 开发满就是负面因素,哪怕挡住几个bug,但是出现bug也是可以重新修改的。

The operative distinction that record draws, and which the code now carries in its own words (pairReportRows's docblock):

A limb that judges 「is this diff widening a contract」 — which check-widening-tells itself says it cannot prove (its header: 「a tell, never a proof」, 「cannot tell an array element from a call argument」) — does not hold a hard gate. A limb that asks 「does a review of record exist」 does.

The mechanism

  • pairReportRows(pair, repo) is new and is where the demotion is: the C5 row is built there instead of being pushed into rows, and rows.length === 0 is the exit-0 condition. A row that is not in that list cannot refuse.
  • renderPair is split into the pure pairRendering (every line, its stream, and the exit, computed before a byte is printed) and a printer. That split is what lets the self-test pin what a run prints beside what it exits — "the row still prints" and "the exit is 0" are two claims, and a case pinning only the second stays green over a row that has gone silent.
  • The C5 row's text gains C5_REPORT_ONLY_DISPOSITION: it says the row is report-only, that the at-tier contract review is what rules on the tell, and ⛔ that report-only is not a clearance and not a verdict that the tell is false. REFUSAL_SENTENCE's remedy is carried verbatim, unchanged.
  • The report row prints under , never — a refusal marker beside an exit of 0 is the exit register's banned "0-with-a-message", inverted.
  • greenPairLine gains a wideningReported branch, so an exit-0 line on a pair that carries tells says so instead of going quiet. Without it a 0 would read as "narrow", which is the silence this file exists against.
  • The report and its file:line list print on every exit this path can answer — including the exit-2 gap path, where the tell would otherwise be lost.

Measured, before and after

Exit codes captured BEFORE any pipe.

run before after
--pair 19314 exit 4, sole adverse row ✗ C5, 7 tells exit 0, row ⚑ C5, the same 7 tells
--pair 19438 exit 4, row ✗ C6 exit 4, row ✗ C6 — untouched

Both halves of requirement 1 verified: 19314 reaches 0 because C5 no longer refuses, not because any tell disappeared. grep -c 'T2 packages/spec/src/stack.zod.ts' reads 7 on the before log and 7 on the after log — stack.zod.ts:3412 through :3418, each with its T2 explanation and its + source line, in both.

The after run's exit-0 line says so itself, rather than reading clean:

⚠️ Its diff DOES carry widening tell(s), printed in full above with their file:line evidence: since the maintainer ruling of 2026-09-21 row C5 is REPORT-ONLY and moves no exit code, so this 0 says the clause-② limbs are LEGIBLE and ⛔ does NOT say the diff is narrow. The at-tier contract review is what rules on those tells.

Pins, and the ablation for each

A new self-test battery, ⭐ the 2026-09-21 ruling: C5 REPORTS its tell, C6 still REFUSES, 24 cases, declared in SELF_TEST_BATTERIES at its measured floor. Self-test: 1075 → 1099 cases, exit 0.

Six ablations, each through scripts/ablation-replace.mjs so the mutation's landing is the tool's own verdict (anchor count 1 to 0, blob hash before and after) and the restore is proved by blob == HEAD plus an empty git diff HEAD:

# ablation red cases what it proves is not vacuous
1 put C5 back into rows 6 the exit-0 half: "C5 present reaches 0"
2 delete the printReports body 9 the printed half: row, file:line, evidence list, remedy, disposition, marker
3 delete greenPairLine's wideningReported branch 1 the exit-0 line says the diff carries tells
4 delete the C6 row push in pairRows 14 C6 still refuses, and the mixed case's 4 is C6's
5 make pairReportRows return [] 8 the report exists at all, including the structural case
6 make wideningUnjudged return null 4 the UNJUDGED exit-2 path is untouched

Every ablation restored byte-identically before the next; git diff HEAD empty and git status --porcelain clean after each. The two ⛔ CONTROL cases on the clean-diff pair stay green under all six by design — they are the non-vacuity bracket for the reported/clean pair of readings, not pins on code.

Gates

node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-clause2-carriers.mjsexit 0, NOT governed — ordinary queue landing applies to a PR with exactly this file list. Size: 292 changed lines (+260 / -32), under the 5000-line human-merge threshold.

Every command's exit code captured BEFORE any pipe:

  • node scripts/pm/check-clause2-carriers.mjs --self-test → 0 (1099 cases)
  • node --check scripts/pm/check-clause2-carriers.mjs → 0
  • pnpm exec eslint scripts/pm/check-clause2-carriers.mjs → 0
  • node scripts/check-self-test-wired.mjs → 0
  • node scripts/check-scripts-symbol-anchors.mjs → 0
  • node scripts/pm/check-governed-merges.mjs --test → 0
  • plus the full family list derived by node scripts/pm/dispatch-gates.mjs --commands (35 commands, derived from the merge base, three-dot) — all 0.

维护者速读(草稿)

改了什么 — 把全仓落地谓词里的 C5 一肢从「拒绝」降为「只报告」。一张卡声明 Clause-②: no、而 diff 带了扩面迹象时,这条行照旧打印,连同它的 file:line 证据和补救文字;但它不再让 --pair 返回非零。C6(本轮是否留下达档复核记录)以及 C2/C3/C8/C9、UNJUDGED 的 exit 2 一律原样不动。

为什么改 — 2026-09-21 的裁决:挡住 PR 落地、让开发 agent 排满,本身就是负面因素,哪怕代价是漏几个 bug,因为 bug 还能再改。裁决给出的分界是问题的种类而非严重程度:判断「这个 diff 是不是在扩接口」的肢,其工具自己写着「只是迹象,不是证明」「分不清数组元素和调用实参」,就不该持硬闸;问「有没有一条复核记录」的肢可以。现场证据:check-widening-tells.mjs 14 天改了 18 次,挂着 5 张缺陷卡且全部是误报;PR #19314 全绿、达档复核 PASS 已判定它那 7 条迹象全部为假,仍被这 7 条卡了 12 小时。

风险与代价(含回滚) — 代价是真实的:一个确实扩了接口却声明 no 的 PR,机器不再拦它,改由达档复核去判——这正是裁决接受的那笔交易。风险被三处收窄:行照打不误、证据照列、exit-0 那行自己写明「本 0 不代表 diff 是窄的」。回滚成本一行:把 pairRendering 里的 const reports = pairReportRows(pair, repo) 改回推进 rows,6 条用例会立刻转红提示。本 PR 保持 draft,不合并、不入队、不动标签,等维护者看过。

席位意见

你要做的 — 读一眼上面的分界是否就是你的本意(C5 报告、C6 仍拒);同意就把这个 PR 标 ready 并走队列。合并后 --pair 19314 立刻读 0,那张被挡 12 小时的 PR 就能合法落地,不需要任何人自查放行。

Acceptance notes

  • No card existed for this when the work started; this session created fix(pm): C5 reports a widening tell instead of refusing on it #19490 itself. The brief that dispatched it predicted issue creation would be refused. It was not: POST /repos/objectstack-ai/objectstack/issues returned 201. The branch therefore carries a real issue-NNNNN segment and CLAIM_BRANCH_SHAPE is satisfied — the predicted exit-2 UNJUDGED consequence does not apply.
  • --pair on THIS PR is adverse on C2, and deliberately so. Card fix(pm): C5 reports a widening tell instead of refusing on it #19490 carries no Claim: comment, because writing one is the owning seat's act and not a dispatched executor's. A seat that wants that reading to clear posts the claim naming this branch. scripts/pm/** is not a governed surface, so PD feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14's --pair-at-0 conjunct does not gate this PR's landing either way.
  • No labels were added or removed, per the brief. scripts/pm/** is on the changeset fast track (not published), so skip-changeset applies on the merits; this PR does not hang that label.
  • The demotion broke no existing self-test case: the full suite was green on the edited file before a single new case was added. The old C5-refuses behaviour was pinned nowhere at the rendering level, which is itself worth knowing about this file.
  • check-widening-tells.mjs, check-half-states.mjs, check-governed-merges.mjs, AGENTS.md and all skill text are untouched. The tell is not the defect being fixed here — its force is.

Generated by Claude Code

`check-clause2-carriers.mjs`'s C5 limb turned a widening TELL into a hard
refusal: a card declaring `Clause-②: no` whose diff carried tells made
`--pair N` exit 4, and PD #14 makes `--pair` at 0 a conjunct of the landing
predicate. On the maintainer's ruling of 2026-09-21, C5 is demoted to
report-only -- the row still prints, with its file:line evidence and its
remedy text, and it no longer contributes to a non-zero exit.

The operative distinction, from the ruling record: a limb that judges
「is this diff widening a contract」 -- which the matcher itself says it
cannot prove (「a tell, never a proof」, 「cannot tell an array element from
a call argument」) -- does not hold a hard gate. A limb that asks 「does a
review of record exist」 does. So C6 is untouched and stays a hard refusal,
as do C2, C3, C8, C9 and the UNJUDGED exit-2 path.

`renderPair` is split into the pure `pairRendering` (every line, its stream
and the exit, computed before a byte is printed) and a printer, so the
self-test can pin what a run PRINTS beside what it EXITS -- "the row still
prints" and "the exit is 0" are two claims, and a case pinning only the
second stays green over a row that went silent. The exit-0 line gains a
`wideningReported` branch so a 0 carrying tells never reads as "narrow".

Claude-Session: https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2
Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(pm): C5 reports a widening tell instead of refusing on it

2 participants