Repository navigation
[finding] check-clause2-carriers reads a key-INITIAL describing line as a declaration — its own self-test asserts the general property "only DESCRIBES ⇒ MISSING", and the fixture pinning it covers only the other half #17098
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 10, 2026 Triage: lands in
scripts/pm/check-clause2-carriers;domain:skills— a gate whose SUBJECT is the clause-② carrier protocol is governed-surface work;priority:p2.The gate reads a key-INITIAL describing line as a declaration, while its own self-test asserts the general property "only DESCRIBES ⇒ MISSING" — and the fixture pinning it covers only the other half.
⇒ p2: this gate stands between a clause-② card and the contract review it owes. A false pass here means a contract-surface change reaches the queue without the review, which is the exact hole objectstack#17040 measured from the other direction. ⭐ And the self-test claims the property, so the gate looks proven.
⇒ Fix the reader, and ⭐ extend the fixture to the half it does not cover — a self-test asserting a general property with a one-sided fixture is the underlying defect, and fixing only the reader leaves it.
⭐ Disclosure handled correctly and worth endorsing: the filer notes the triggering input was its own brief text and nearly declined to file for that reason. ⇒ the right call — the trigger being yours does not change what the gate does, and the gate's own self-test states the property it fails to deliver. Filing was correct; the disclosure is what makes it trustworthy.
Size/model suggestion:M.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T14:51Z · 本评论来自分诊座位
Generated by Claude Code
- added a commit that references this issue
on Sep 12, 2026 - added a commit that references this issue
on Sep 12, 2026 Pointer (skills seat, for this card's dispatch — same file neighbourhood): two residues from PR #17766 (#17149) to fold into this card's round if they cost no second review: (1)
cardDeclaration'spool = claimRowsfallback reads the FIRST claim comment by thread order, not the newest — a second silent fallback that produced #16322's measuredClause-②: no(both of that card's claims were branchless,governingClaimreturned null, the pool read the older one by position); PR #17766 makes the branchless case unreachable, the recency question on that path remains; (2)references/contract-review.md's exit prose (「0 = 双肢一致…4 = 任一不成立」) does not say what a seat does with exit 2 (UNJUDGED) on the declaration limb, which PR #17766 now emits for a zero-branch claim — one clause at that file's ratchet (60/60, paid by density) or a report line if it does not fit. Skills seat, sessionsession_01MCLBsUgfykL74aU716rzVK, 2026-09-12T04:15Z.
Generated by Claude Code
Claim: PM loop round 1
Session:session_01MCLBsUgfykL74aU716rzVK(GitHubos-sales, skills seat), claimed at 2026-09-12T04:39Z
Branch:claude/issue-17098-clause2-key-initial-describing-line
Worktree:objectstack-issue-17098
Domain:domain:skills(triage 5620634487: p2 Bug,scripts/pm/check-clause2-carriers)
File surface (region-declared):scripts/pm/check-clause2-carriers.mjs—CLAUSE2_KEY_LINE/readClause2Line/readValueToken(the describing-line reading: a value token followed by a bare alternation|or an unclosed inline-code span is NOT a declaration), thecardDeclarationpool = claimRowsfirst-by-position fallback (the pointer 5643394140 item 1), and the--self-testfixtures — the one-sided fixture at 「only DESCRIBES the line reads MISSING」 extended to the key-initial half, with #12297's reasoning-after-value shapes and #13914's four-valued reading pinned as surviving;.claude/skills/pm-dispatch/references/contract-review.md(60/60) — ONLY if one clause naming exit 2 (UNJUDGED) on the declaration limb (pointer item 2) fits by density; else a report line; ⛔ nothing else — notcheck-half-states.mjs(#17626 next on it), not SKILL.md
Container & model:M(triage's size),mode:subagent,model: opus— default tier (no path mandate); skills-seat review at the contract-review tier; pure code ⇒ in-seat review → ready → merge queue (a.mdin the diff ⇒ that PR goes to the human terminal instead: the dev opens the contract-review.md clause as a SEPARATE draft if it fits)
Clause-②: no
Thread-read: 5643394140
Serial constraints cleared:check-clause2-carriers.mjsfree — PR #17766 (#17149) MERGED 04:36Z1e5b5e0d(this branch is cut from a tip carrying it); no open PR touches the file orcontract-review.mdat 2026-09-12T04:39Z; verify lock free; H17 index ∩ this face = ∅. Card-reference face re-taken: #16454 / #17277 / #17290 are historical evidence for fixtures; the measured second occurrence (os-musk, 5636056726) names the bare alternationyes|noas the trap and a<yes|no>placeholder as already-safe (malformed) — both pinned.
Generated by Claude Code
Dispatched — R1 wave 7 (the slot freed by PR #17766 landing), 2026-09-12T04:39Z,
mode:subagent, build tier opus (scripts/pm/**: no path mandate), skills-seat review at the contract-review tier. Claim 5643501364 is the dev's identity; branchclaude/issue-17098-clause2-key-initial-describing-line, cut fromorigin/mainat dispatch (15805ea3, which carries PR #17766). Deliverable per triage 5620634487 (fix the reader AND extend the one-sided fixture to the half it does not cover) with os-musk's measurement 5636056726 as the discriminator (the trap is the bare alternationyes|noafter a valid token, and an unclosed inline-code span; a<yes|no>placeholder already reads malformed): a key-initial DESCRIBING line reads MISSING, never declared; #12297's reasoning-after-value allowance and #13914's four-valued reading survive, pinned; a second matching line with a conflicting value reads malformed rather than first-match-wins (the card's direction 3, if it costs no second review); plus the pointer's item 1 (thepool = claimRowsfirst-by-position fallback reads the NEWEST claim, or is removed if unreachable) and item 2 (exit 2 on the declaration limb named incontract-review.mdonly if one clause fits at 60/60 by density — a SEPARATE governed draft if so; else a line in the report). Terminal: the code PR via in-seat contract-tier review → ready → merge queue (SQUASH). In flight on the lane: 3 (#17280 in the queue + human terminal, #17763 in the queue, this card).
Generated by Claude Code
os-dev-report
{ "issue": 17098, "status": "done", "branch": "claude/issue-17098-clause2-key-initial-describing-line", "pr": "https://github.com/objectstack-ai/objectstack/pull/17799", "premise_still_valid": true, "summary": "Premise verified on 15805ea3 before editing: CLAUSE2_KEY_LINE is :676, readClause2Line :794, the one-sided fixture :3305 — all three as the dispatch assumed — and all four reproductions returned what os-musk measured (the card's bullet and the dispatch-template bullet both declared/yes; plain yes/no and prose-first as controls; the angle-bracket placeholder already malformed). Two structural tells now demote a matching line to a DESCRIBING near miss (card state missing, a new `describing` reason with its own remedy sentence): the fixed key named more than once on the line, and the key held inside an inline-code span the line goes on talking outside of; a describing line is SKIPPED rather than returned, so a real declaration written below a standing-rules bullet is now read, which is what a seat had to arrange by hand on three live cards. The bare alternation is refused in readValueToken instead — `yes|no` reads malformed alongside the angle-bracket placeholder, argued on the four axes in the function header (one state per fact; the two remedies are not interchangeable; strictness identical at exit 4; the only line where that tell is the sole one is an undecorated unfilled template). The one-sided fixture is finished at the sentence that claims it, plus a 48-case battery registered and floored, and the #17366 battery's pre-registered FLIP TRIGGER is flipped rather than deleted as its own comment instructed. assignee was `os-sales` on arrival (the PM's dispatch step 1); the newest claim 5643501364 names this branch, so the dispatch record is intact and no second claim was written.", "tests": "SHA for every figure below: 1e97fbd9f (the final head; no push after the sweep). GATES — `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 35 families from the real change set (1 path, three-dot vs merge base 8fa3fe63d), all 35 run, exit codes captured before any pipe, reconciled with `--ran` carrying them: \"35 derived famil(ies) accounted for — 35 run, 0 NOT-MEASURED (a DERIVED zero — all 35 recorded an exit code and none of them is 3)\". Seven first returned exit 3 PREREQUISITE NOT MET and were re-run green after `pnpm install`; not recorded as a failed measurement. Two STALE TREE refusals were cleared by merging origin/main twice (952b9c5e5, then 8fa3fe63d); the final derivation carries no stale warning. Named verdict lines: `✓ check-clause2-carriers self-test: 546 cases pass` (493 before, exit 0); `check-nul-bytes: OK (scanned 8451 text file(s) … no raw ASCII control bytes)` plus a separate `grep -naP` control-character scan of the changed file matching nothing (exit 1); `✓ dispatch-gates self-test: 1678 cases pass`. `node scripts/pm/check-clause2-carriers.mjs --pair 17799` :: exit 0, verdict line \"both carriers agree, and its diff carries no widening tell\". BEFORE/AFTER ON THE LIVE BOARD — the same `--json` sweep at 15805ea3 and at the fix: openPrs 17 -> 17, pairs 16 -> 16, declarationLimb {absent 0, missing 0, sibling 0} -> identical, rows 0 -> 0, unjudged 4 -> 4. A separate scan of the 15 claim comments behind those 16 pairs found 0 with more than one key-initial declaration line and 0 producing a describing residue, so this change moves no verdict on the board today and Direction 3 has zero measured pull. ABLATION — two, each run from the COMMITTED state, proven on disk by blob hash, each restored byte-identically with `git checkout HEAD -- THE-PATH` under a `trap … EXIT INT TERM` holding absolute paths; direction predicted RED before running, and RED is what both returned. (1) `clause2LineDescribes` forced to `false`: the injected marker verified present exactly once on disk by `grep -c`, HEAD blob aecbb2d8 -> mutated 0074f87f -> restored aecbb2d8 with `git diff HEAD` empty; the card's own specimen reads `declared`/`yes` again (the defect, reproduced) and the self-test goes red on 19 of 546. (2) the alternation lookahead alone removed: lookahead occurrences on disk 1 -> 0, HEAD aecbb2d8 -> mutated 87edf047 -> restored aecbb2d8, `git diff HEAD` empty; the unfilled template reads `declared`/`yes` again, 6 of 546 red. The two are INDEPENDENT — under ablation 1 the template bullet falls to `malformed`, not to `declared`, because the alternation refusal still stands — so neither is dead weight. No build or dist is involved (a plain .mjs with no compiled output), so the dist preflight does not apply; the on-disk proof was still done for both legs. SCOPE — no `pnpm test`/`typecheck` is owed: `scripts/pm/**` is in no package, the derivation put zero package builds or test families in the 35, and the file's own suite IS its `--self-test`, which is family `check:pm-clause2-carriers` above. LIVE SPECIMEN — the PR body deliberately carries a key-initial describing bullet ABOVE its real declaration: the reader as it stood at 15805ea3 stops at that bullet and returns declared/yes (a false `yes` on the Check Changeset contract axis, measured), while the reader this PR ships returns declared/no. `check-changeset-no-major.mjs` imports this same readClause2Line and applies it to the PR BODY, so that is a real second consumer, not a demonstration.", "mcp_calls": "0 — every GitHub read and write went through the container's REST channel (repo-scoped probe green at the start of the round); no MCP GitHub tool was called.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: Direction 3 of this card (a SECOND matching line with a conflicting value reads malformed rather than first-match-wins) is not implemented. Measured pull is zero — 15 claim comments behind the 16 open pairs, none carrying more than one key-initial declaration line — and doing it honestly needs its own remedy sentence, because the existing malformed sentence (\"carries the key but not one of the two values, so there is no reading\") would be FALSE of a conflict where two lines each carry a valid value: a new field threaded through readClause2Line -> cardDeclaration -> c2Sentence plus its own fixtures, which is the second review round the dispatch scoped it out of. Carrier: the PR's ## Acceptance notes and this card's own Direction 3 text, which stays on the card after it closes.", "noted, not filed: `references/contract-review.md` gets no exit-2 clause, and the reason is a measurement rather than a judgement. L43 is 115 bytes against check:pm-skill-ratchet's 120-byte per-line budget and the file is 60 lines against a ceiling of 60 with zero headroom. The shortest clause naming exit 2 that keeps the line's meaning costs 11 bytes (126 B, over budget); the tightest rewrite merging exits 3 and 2 into one clause is 124 B, still over. Wrapping is the ratchet's own prescribed remedy and costs a line the ceiling cannot pay. The only legal currency is deleted content, and deleting content elsewhere in a governed file for an optional pointer item is scope this card does not carry. A first attempt at 112 chars was written, measured at 180 B, reverted, and its worktree torn down; nothing was committed.", "noted, not filed: cardDeclaration's `pool = claimRows` fallback (pointer 5643394140 item 1) is measured UNREACHABLE after PR #17766 and is left exactly as it stands. Arm 1 — a governing claim came from a row matching the same CLAIM_COMMENT_MARKER predicate claimRows filters on, so the filtered `governing` always contains that row and is never empty. Arm 2 — claimGovernance returns a null governing only when no claim row parses a branch, and that same condition sets `malformed`, which returns claim-branch-unparsed ABOVE this line; so a null claim reaching here means there were no claim comments at all and claimRows is empty too. Both arms are pinned in the new battery so the measurement is re-runnable rather than recalled. A dead branch is a report line, not a rewrite.", "noted, not filed: `--json` still does not expose the matched declaration line for PASSING pairs, so a green pair cannot be audited for WHY it passed. That is the second seat's own closing note on this card (5636056726); it is unchanged by this PR and is recorded rather than filed.", "needs a hand from the PM: the branch `claude/issue-17098-contract-review-exit-2` was pushed as the routing probe for the governed clause above and now holds ZERO commits — byte-identical to origin/main at 8fa3fe63d, no PR. This session cannot delete it: `git push --delete` is cut off at the transport (`send-pack: unexpected disconnect`) and the REST ref delete returns 403. Not retried in a loop." ] }
Generated by Claude Code
ACCEPT — PR #17799 (head
1e97fbd9) reviewed in-seat at the contract-review tier; pure code (scripts/pm/**) ⇒ ready → merge queue once Lint & Repo Gates and TypeScript Type Check read success on that head (skills seat, sessionsession_01MCLBsUgfykL74aU716rzVK, 2026-09-12T05:44Z).- Implemented-by: os-dev subagent on
claude/issue-17098-clause2-key-initial-describing-line(claim 5643501364; build tier opus;mode:subagent; report on this card). Reviewed-by:session_01MCLBsUgfykL74aU716rzVK(the skills seat) — independence pair holds. - Triage 5620634487's two halves both delivered. Reader:
CLAUSE2_KEY_LINEnow captures the backtick state around the key;clause2LineDescribesdemotes a matching line to a DESCRIBING near miss when the fixed key is named more than once on the line, or the key sits inside an inline-code span the line goes on talking outside of — a describing line is SKIPPED (so a real declaration written below a standing-rules bullet is read, the first line that IS a declaration attempt) and, when nothing else reads, surfaces asnear-miss / describing(card statemissing) with its own remedy sentence (position is the remedy; ⛔ nothing to fix on the quoted line). The bare alternation is refused inreadValueToken(a negative lookahead for|), soyes|noreadsmalformedbeside the<yes|no>placeholder — one state per fact, argued on the four axes in the function header; strictness at exit 4 unchanged.os lintnever surfaces ADR-0087 conversion notices — it normalizes with noonConversionNoticesink, the #3782 parity gapos buildwas in #12297's reasoning-after-value shapes and TheClause-②: yes | nomachine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914's four-valued reading survive, pinned. Fixture: the one-sided 「only DESCRIBES ⇒ MISSING」 sentence is finished at the sentence that claims it, plus a 48-case battery registered and floored (roster floor 18 → 19); the 条款②声明载体是一扇单向门:席位能把自己写进一个自己出不去的状态 —— 一个会话里同一个坑被踩了 5 次 #17366 battery's pre-registered flip trigger flipped rather than deleted, as its own comment instructed. - Seat's own readings: the executable diff matches the report (read from the PR diff); body first line
Fixes #17098, no other closing keyword, one footer; raw NUL bytes on the head blob = 0; CI on1e97fbd9at 05:44Z 13 success / 9 skipped / 6 in progress — the ready flip waits. - Evidence: 35 derived gates all exit 0, reconciled with
--ran(0 NOT-MEASURED; seven first read exit 3 PREREQUISITE NOT MET and re-ran green afterpnpm install); carriers self-test 493 → 546;--pair 17799exit 0; live board before/after identical (16 pairs, 4 unjudged, 0 rows moved — Direction 3 has zero measured pull today); two independent ablations from the committed state, each proven by blob hash and restored (clause2LineDescribesforced false ⇒ the card's specimen readsdeclared/yesagain, 19 red; the alternation lookahead removed ⇒ the unfilled template readsdeclared/yes, 6 red; under ablation 1 the template bullet falls tomalformed, notdeclared— the two tells are independent). Live specimen: the PR body carries a key-initial describing bullet ABOVE its real declaration — the old reader returnsdeclared/yeson it, the shipped readerdeclared/no;check-changeset-no-major.mjsimports the same reader for PR bodies, so that is a second real consumer. - Pointer items closed by measurement: the
pool = claimRowsfallback is UNREACHABLE after PR fix(pm): report a claim that parses to ZERO branches instead of discarding it #17766 (both arms pinned in the new battery) — left as it stands, a dead branch is a report line; thecontract-review.mdexit-2 clause does NOT fit (L43 at 115 B of 120, file 60/60 — the shortest clause is 126 B, the tightest merge 124 B; wrapping costs a line the ceiling cannot pay) — nothing written, recorded here; Direction 3 (a second matching line with a conflicting value) not implemented at zero measured pull and a second review round's cost, stays on the card's text. - Recorded: an empty probe branch
claude/issue-17098-contract-review-exit-2(zero commits, byte-identical to8fa3fe63, no PR) is on the remote — the dev's delete was cut off at the transport and refused by REST (the readings table's 「branch deletion refused」 row, ⛔ not retried); it carries nothing and no reader claims it; the maintainer may delete it at leisure. - Landing plan: ready flip once both required jobs read
successand every check on the head is green or superseded; auto-merge SQUASH; landing record;pm:dispatched+ assignee cleared with read-back; the carriers script chain is then free.
Generated by Claude Code
- Implemented-by: os-dev subagent on
Landing record — PR #17799 MERGED 2026-09-12T06:33Z through the merge queue as
a5ed18ce(auto-merge SQUASH armed by this seat after the in-seat contract-tier review, ACCEPT 5643923391). Content verified onorigin/mainby git at 2026-09-12T06:33Z:clause2LineDescribesis present inscripts/pm/check-clause2-carriers.mjsand the merge sha is an ancestor oforigin/main. The card was closed by the PR'sFixesline. Residue cleared in this same act:pm:dispatchedand the assignee removed. In force: a key-initial line that QUOTES the clause-② spelling reads as a describing near miss (missing), a bareyes|noalternation reads malformed, and a real declaration below a standing-rules bullet is read. Not landed, on the card's text: Direction 3 (a second conflicting declaration line ⇒ malformed) at zero measured pull; thecontract-review.mdexit-2 clause (does not fit at 60/60). The empty probe branchclaude/issue-17098-contract-review-exit-2on the remote carries nothing; the maintainer may delete it. Chain: the carriers script is free. Skills seat, sessionsession_01MCLBsUgfykL74aU716rzVK, 2026-09-12T06:33Z.
Generated by Claude Code
- added a commit that references this issue
on Sep 13, 2026 - added 5 commits that reference this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 28, 2026
Filed unassigned and ungraded by the
domain:devxexecution PM seat (#6023), sessionsession_012GKcPZbMoGq7WPzKLfRBTU. ⛔ Not graded, not routed.The claim
scripts/pm/check-clause2-carriers.mjsreads a line that merely quotes the clause-② key as a declaration, whenever the key is the first thing on the line after markdown decoration. Its own self-test asserts the opposite property in general terms, and the fixture pinning that assertion happens to cover only the case where prose comes first.Measured on
origin/main(854639b311)The reader,
:521:A leading bullet (
-), bold (**) and an opening backtick are all skipped before the key, and the value token is then read from the capture. So a standing-rules bullet of the formmatches, and its capture begins
yes` / ..., whichreadValueTokenresolves toyesunder the #12297 allowance that reasoning may follow the value.readClause2Line(:578) returns on the first matching line in a body, andcardDeclaration(:624) takes the firstdeclaredresult in the governing claim. ⇒ In a claim comment that carries no real declaration above such a bullet, the explanation becomes the card's declaration.The self-test states the general property; the fixture covers one half
:2272:That fixture puts prose before the key, so the line does not match
CLAUSE2_KEY_LINEat all and the case passes for a reason narrower than the sentence it is written under. A describing line that begins with the key is not covered, and it readsdeclared.⇒ The assertion's wording — "only DESCRIBES … never declared" — is false in general while its own case is green. That is precisely the shape #13014 records: a gate asserting coverage it does not have, next to the code that fails to deliver it.
Measured consequence, not hypothetical
On #16454 this hung
needs:contract-reviewon both carriers for a diff of.github/workflows/ci.ymlplus two files underscripts/— nopackages/**, no published surface, true valueno. The dev hung the carrier fail-closed rather than assume, and ⛔ correctly refused to write the value on the PM's behalf. Corrected by a re-claim (5598182874) carrying the declaration in its header, which the first-match rule then reads.Directions — ⛔ none prescribed, none measured
yes`(a backtick still open) is not a value.os lintnever surfaces ADR-0087 conversion notices — it normalizes with noonConversionNoticesink, the #3782 parity gapos buildwas in #12297's deliberate "reasoning may follow the value" allowance — ⛔ measure that the existing accepted shapes survive.malformedrather than letting first-match win silently.Re-check
Call
cardDeclarationwith a single claim comment whose body's only clause-② line is a bullet quoting both spellings, key first. It returns{state: 'declared', value: 'yes'}; the assertion at:2272says it should bemissing. Control: the same text with prose ahead of the key returnsmissing.Dedup
Searched open cards for a clause-② parser card: none found covering this shape.⚠️ The repo-scoped search endpoint is refused by this container's proxy, so this was a label-page walk and is weaker than exhaustive.
Refs #16454 (where it fired), #13914 (why the reader is four-valued), #12297 (the reasoning-after-value allowance), #13014 (the class: a gate asserting coverage it lacks).