Found while implementing #8476 (PR #8527). Filed unassigned; not fixed there, because that card's scope is the H7 blocking check.
The gap
scripts/pm/check-half-states.mjs carries a --self-test with 79 cases — the H1 through H7 predicates, the seat-sticker parser, the transport classifier and its measured container classes. Nothing in CI runs any of them:
$ grep -n "half-states" package.json .github/workflows/*.yml
(no matches)
There is no check:* entry in package.json and no workflow step. The self-test executes only when a human or an agent types it, which makes it a check whose coverage is a function of who remembered.
This is the same shape as three cards already closed this week — the dispatch-gates self-test (#8162), check:skill-frame-freshness (#8110) and check:dev-prereqs (#8170) — each of which was wired after being found the same way. This file is the remaining member of that family that I tripped over.
Why it is worth more than it was yesterday
Until today the sweep was report-only, so a silent break in a predicate degraded a patrol report. As of PR #8527 the H7 predicate (h7PartOfWithClosingKeyword, plus stripMarkdownCode beneath it) is imported by a PR-scoped blocking gate, so a break there now reddens or, worse, silently greens every PR in the repo.
Partial mitigation already landed, and its boundary should be stated so nobody reads it as closure: the new gate's own self-test pins the H7 behaviours it depends on (the three code-formatting arms, the per-card binding, the reused finding sentence), and that self-test does run in lint.yml. So H7 has incidental CI coverage now. H1 through H6, the seat-sticker parser and the whole transport classifier have none.
Shape of the fix (not prescriptive)
Presumably a check:pm-half-states entry running --self-test only, plus an unconditional lint.yml step next to the other three pm-tooling gates. The live sweep must not be gated — it is deliberately report-only, exits 0 on any completed sweep, and needs API transport that most containers do not have. Whether it wants a thin gate file of its own (the dispatch-gates precedent, which exists because that tool's self-test fixtures are path strings that pollute watch hints) or can be pointed at directly is worth one measurement: this file's fixtures are issue-shaped objects and prose, not paths, so the pollution that forced a separate file there may not apply here.
Found while implementing #8476 (PR #8527). Filed unassigned; not fixed there, because that card's scope is the H7 blocking check.
The gap
scripts/pm/check-half-states.mjscarries a--self-testwith 79 cases — the H1 through H7 predicates, the seat-sticker parser, the transport classifier and its measured container classes. Nothing in CI runs any of them:There is no
check:*entry inpackage.jsonand no workflow step. The self-test executes only when a human or an agent types it, which makes it a check whose coverage is a function of who remembered.This is the same shape as three cards already closed this week — the dispatch-gates self-test (#8162),
check:skill-frame-freshness(#8110) andcheck:dev-prereqs(#8170) — each of which was wired after being found the same way. This file is the remaining member of that family that I tripped over.Why it is worth more than it was yesterday
Until today the sweep was report-only, so a silent break in a predicate degraded a patrol report. As of PR #8527 the H7 predicate (
h7PartOfWithClosingKeyword, plusstripMarkdownCodebeneath it) is imported by a PR-scoped blocking gate, so a break there now reddens or, worse, silently greens every PR in the repo.Partial mitigation already landed, and its boundary should be stated so nobody reads it as closure: the new gate's own self-test pins the H7 behaviours it depends on (the three code-formatting arms, the per-card binding, the reused finding sentence), and that self-test does run in
lint.yml. So H7 has incidental CI coverage now. H1 through H6, the seat-sticker parser and the whole transport classifier have none.Shape of the fix (not prescriptive)
Presumably a
check:pm-half-statesentry running--self-testonly, plus an unconditionallint.ymlstep next to the other three pm-tooling gates. The live sweep must not be gated — it is deliberately report-only, exits 0 on any completed sweep, and needs API transport that most containers do not have. Whether it wants a thin gate file of its own (the dispatch-gates precedent, which exists because that tool's self-test fixtures are path strings that pollute watch hints) or can be pointed at directly is worth one measurement: this file's fixtures are issue-shaped objects and prose, not paths, so the pollution that forced a separate file there may not apply here.