Description
Filed by the central triage seat (#6015, session session_01XVLjap8eh1QjiaPznpW5Ry) from the 2026-08-31 contract-review round. ⛔ Not a defect in any of the reviewed PRs — every declaration measured was honest in substance. The defect is where the declaration lives.
The measurement
Three contract reviews ran this round, each by a claude-fable-5 sub-agent with transcript-tier verification. Two of them reported, independently and unprompted, the same process gap:
| card / PR | what the review found |
|---|---|
| #13576 / PR #13870 | "the machine spelling Clause-②: yes mandated by the pm-dispatch skill does not appear verbatim anywhere on the PR/card — the declarations are prose ("FIRES on the CONTENT limb"), honest in substance but nonconforming in spelling." |
| #13623 / PR #13811 | "the claim comment on #13623 does not carry the SKILL.md fixed claim shape — in particular the mandatory Clause-②: yes | no line, the declaration limb's designated carrier; the machine spelling appears only in the PR body and the os-dev report." |
| #12297 / PR #13741 | carried it correctly — "the PM claim comment on #12297 carries Clause-②: yes with reasoning". This is the control: the shape is achievable and is sometimes achieved, so the two misses are drift, not an impossible requirement. |
⇒ 2 of 3, by two different reviewers, on two different lanes. That is a pattern in the template, not two people being careless.
Why it matters — the gate reads a spelling that is not there
.claude/skills/pm-dispatch/SKILL.md defines the declaration limb as a fixed machine criterion:
Clause-②: yes | no—— 条款②内容肢的强制申报,恒英文机器判据,恰这两种拼写:Clause-②: yes/Clause-②: no;判据:本卡改变契约接受/拒绝行为或扩大公开面吗?yes绑定入队闸门
and the enqueue gate is explicitly two-limbed, with the content limb keyed on that declaration independently of the path:
⛔ 永不把路径触发读作条款②的完整覆盖 —— 它只盖路径肢;不触契约文件而改变接受/拒绝行为的卡由内容肢的申报捕获
⇒ For a PR whose diff touches no contract path — which is exactly the case for both misses (#13870: six files, none under packages/spec/src/**; #13811: four files, same) — the declaration on the card is the ONLY limb that can fire. If it is prose, or lives in the PR body instead, the gate's predicate has nothing to read.
⭐ Both of these PRs happened to be caught anyway, because a human/seat routed them into the review chain by hand. That is not the mechanism working; that is the mechanism being substituted for. A card with a genuine content-limb change, a non-contract path, and a prose-only declaration is invisible to the gate.
What is NOT claimed
- ⛔ No claim that any declaration was dishonest. All three were substantively correct; fix(runtime): carry the producer's
userMessageat the dispatcher's PERMISSION_DENIED door #13811's was even over-inclusive in the safe direction. The auditable-false-declaration risk (a wrongno) did not occur. - ⛔ No claim about frequency beyond the measurement. Three cards, one round, one seat.
⚠️ And it is invisible when it does not get caught — a prose-only declaration on a card nobody hand-routes leaves no trace — so 2/3 is an upper bound on conformance, i.e. a lower bound on the problem. - ⛔ No claim that the reviewers should have refused. Both correctly recorded it as non-blocking and passed/reworked on substance.
Shape of a fix — for the lane PM, not prescribed
- Template side: the claim-comment template in the pm-dispatch skill emits the
Clause-②:line pre-filled as a required field, so omitting it takes effort rather than being the default. Cheapest. - Gate side: make the enqueue gate say why it cannot judge — a card whose PR touches no contract path and whose claim comment carries no
Clause-②:line is currently indistinguishable from a card that declaredno. ⭐ Those two states should not look the same; the second is a decision, the first is a missing reading. - Both — 1 stops the drift, 2 makes the residue loud.
⛔ Do not "fix" this by relaxing the spelling to accept prose. The fixed spelling is what makes it machine-readable; loosening it converts a predicate into a heuristic, and the whole point of the two-limb design is that the content limb is checkable without judgement.
Scope note
.claude/** is governed surface ⇒ human-merge only, ⛔ never armed, ⛔ never queued.
Refs
.claude/skills/pm-dispatch/SKILL.md— theClause-②declaration definition and the enqueue-gate two-limb section- [finding]
If-Match: ""silently DISABLES optimistic concurrency — a quoted-empty entity-tag is read as "no token" and the guarded write proceeds unguarded #13576 / PR fix(metadata-protocol): refuse the quoted-empty If-Match entity-tag at ingress (#13576) #13870 · APERMISSION_DENIEDrefusal never reaches the dispatcher's throw-transparent exit, so it still drops the author'suserMessage— a second door, behind #7898's trigger file #13623 / PR fix(runtime): carry the producer'suserMessageat the dispatcher's PERMISSION_DENIED door #13811 — the two misses, each recorded in its own contract-review verdict os lintnever surfaces ADR-0087 conversion notices — it normalizes with noonConversionNoticesink, the #3782 parity gapos buildwas in #12297 / PR fix(cli): os lint surfaces ADR-0087 conversion notices — console notice + a conversions key in --json #13741 — the control (carried correctly)
Activity
claude commentedon Aug 31, 2026
Remedy 2 delivered in draft PR #13944 — this card stays OPEN for remedy 1
Part of #13914, deliberately not a closing keyword: only the gate-side half landed.What landed (remedy 2)
scripts/pm/check-clause2-carriers.mjsgives the declaration limb its first reader in this repo. Before it, grep overscripts/and.github/workflows/found theClause-②token only in a label description and in prose — never in a predicate — which is why "a card with no declaration is indistinguishable from a card that declaredno" was true. It now reads four states and collapses none of them into another:declaredmisplacedmalformedClause-②:line whose value is not one of the two tokens.absentno.A fifth,
unreadable, is accounted separately as UNJUDGED so a thread this checker could not read never reads as an absent declaration.This card's own measurement, re-observed by mechanism
Swept live on 2026-08-31 across every open card/PR pair: 25 pairs, 22 with no readable declaration — 20
absentand 2misplaced. One of the two misplaced is #13623, one of this card's own two measured misses; the checker quotes back what it found there instead (### Clause ② — declared 'yes' conservatively). So this card's "2 of 3 is an upper bound on conformance" reads, on the whole open board, as 3 of 25.Clause-②: yesin the fixed spelling while neither carrier carriesneeds:contract-review. That is the state this card described as "invisible to the gate", found live, twice, on one board.⛔ Remedy 1 is NOT in this dispatch
The claim-comment template in
.claude/skills/pm-dispatch/SKILL.mdpre-filling theClause-②:line is a protocol semantic face on a governed surface — it waits for its own serial window and its own human merge. ⇒ This card stays open until that half lands. The 22absentreadings above are the argument for it: a reader makes the residue loud, and only the template stops the drift.⛔ And the spelling was not relaxed.
Clause-②: YES,Clause-②: nope, an empty value and the prose formClause ②: yesall stay unread. Reasoning after the value token is accepted, because this card records #12297's "carriesClause-②: yeswith reasoning" as the CONTROL — a reader that rejected it would have graded this card's own control as the defect, and it did, on four real claims, before that case was pinned.Generated by Claude Code
Generated by Claude Code
A reader for the clause-② gate: assert the dual carrier, and make a m…
fix(pm): give `check-clause2-carriers --pair` a legal exit for a card…
fix(pm): give `check-clause2-carriers --pair` a legal exit for a card…
fix(pm): a key-INITIAL clause-② line that QUOTES the spelling is not …
fix(pm): refuse a claim's unreadable keyed lines before post-stamped …
refactor(pm): extract the clause-② declaration reader into its own mo…