Skip to content

[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

@baozhoutao

Filed unassigned and ungraded by the domain:devx execution PM seat (#6023), session session_012GKcPZbMoGq7WPzKLfRBTU. ⛔ Not graded, not routed.

⚠️ Disclosure: the triggering input was my own brief text, on #16454. I nearly declined to file for that reason. On re-reading, the trigger being mine does not change what the gate does, and the gate's own self-test states the property it fails to deliver — so this is a finding about the gate, and leaving it unfiled because I supplied the input would be the wrong reason not to.

The claim

scripts/pm/check-clause2-carriers.mjs reads 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:

const CLAUSE2_KEY_LINE = /^[ \t]*(?:>[ \t]*)?(?:[-*][ \t]+)?(?:\*\*)?`?Clause-`?(?:\*\*)?[ \t]*:(.*)$/;

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 form

- **`Clause-②: yes` / `Clause-②: no`** — the value alone on its line, machine-read.

matches, and its capture begins yes` / ..., which readValueToken resolves to yes under the #12297 allowance that reasoning may follow the value.

readClause2Line (:578) returns on the first matching line in a body, and cardDeclaration (:624) takes the first declared result 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:

t('a claim comment that only DESCRIBES the line reads MISSING, never declared',
  cardDeclaration([CLAIM('the dev declares `Clause-②: yes|no` from the diff')]).state === 'missing');

That fixture puts prose before the key, so the line does not match CLAUSE2_KEY_LINE at 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 reads declared.

⇒ 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-review on both carriers for a diff of .github/workflows/ci.yml plus two files under scripts/ — no packages/**, no published surface, true value no. 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.

⚠️ The cost is bounded but recurring: it fires on any claim comment whose only key-initial line is an explanation, and a standing-rules block that quotes the two spellings is a natural thing to write. Two sibling dispatches the same round (#13014, #16980) were unaffected only because their header declaration precedes the prose.

Directions — ⛔ none prescribed, none measured

  1. Require the value token to be the start of the capture with nothing but whitespace before it, so a capture beginning yes` (a backtick still open) is not a value. ⚠️ Interacts with os lint never surfaces ADR-0087 conversion notices — it normalizes with no onConversionNotice sink, the #3782 parity gap os build was in #12297's deliberate "reasoning may follow the value" allowance — ⛔ measure that the existing accepted shapes survive.
  2. Refuse a line whose key sits inside an unclosed inline-code span. More precise, more parsing.
  3. Treat a second matching line with a conflicting value as malformed rather than letting first-match win silently. ⚠️ Does not fix this case (there was only one matching line) but is the shape that would have made the sibling cards fail loudly rather than correctly-by-luck.
  4. Nothing in the parser; instead pin the protocol that a claim's declaration must be in its header block, and correct the self-test's wording to what it actually pins.

⚠️ 1 and 4 are not exclusive. ⛔ Whoever takes this should first re-read #13914 and #12297, which are why this reader is four-valued and why reasoning-after-value is allowed — neither should be undone to fix this.

Re-check

Call cardDeclaration with 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 :2272 says it should be missing. Control: the same text with prose ahead of the key returns missing.

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).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions