Repository navigation
S2.3.6 — The hard-failure block emitter - #342
Merged
dev-pmallapp merged 4 commits intoSep 23, 2026
Merged
dev-pmallapp merged 4 commits into
dev-pmallapp merged 4 commits into
Conversation
The block at docs/design/02-roles.md:276-282 had five copies in the tree — four prose bodies and a private hardFailure() in risk-mandate.ts — and a rule too loose to police any of them: /(?:- .+\n?)+/ accepts any bullets in any order, which is why the subject-line divergence went unnoticed. packages/core/src/guards/hard-failure.ts holds the spec once. A TypeScript renderer produces conformant blocks; skill-lint's new hard-failure-block rule validates prose against the same exported constants. One contract, two enforcement directions. risk-mandate.ts's private copy is deleted and rewired. Its test is UNTOUCHED and still green — it pins the output string verbatim, so it is the regression guard that the swap is behaviour-preserving. The subject line is an open slot keyed Milestone/Story/Goal per gate ruling G-a on #47: the fixed '- Story: #{story}' was unsatisfiable for goal-create, which runs before any Story exists. All four shipped bodies pass the new rule unchanged. Refs #316, #47
…rule fires checkEvidenceMandate is the reader the doctrine at references/evidence-artifacts.md:140-144 never had. It RESOLVES NOTHING: it takes an already-determined resolution and decides what to do about it, and a static assertion pins that its own source imports no fs and no path. That seam is what makes the task shippable. Build target 4's resolver cannot be built as specified (#341): 21 of 54 artifacts carry no Claims row and the other 33 use five incompatible notations, over an immutable corpus. Only one direction is sound — a claim id in ZERO artifacts is certainly uncovered — and a refusal needs only that one. Case 8 is recorded PARTIALLY discharged per gate ruling G-b, carrying the positive direction to BT4. The negative fixtures each assert TWO things: the old loose regex ACCEPTED the malformation, and the new rule rejects it. Without the first, they would only show that some rule fires on some garbage; with it, each one names a defect that would have shipped green — a missing Action-line prefix, a subject key outside the vocabulary, transposed Expected/Found. docs/design/02-roles.md's template restated per G-a: the subject is an open slot keyed Milestone/Story/Goal, and {remedy} is the trailing imperative only. Refs #316, #47, #341
Mutation M5 deleted the Action line's invariant-prefix check entirely --
startsWith(HARD_FAILURE_ACTION_PREFIX) became startsWith("- Action:") --
and the suite stayed green. The negative fixture meant to pin that check
still failed the mutant, but by accident: its Action line is SHORTER than
the 36-character prefix, so the surviving .slice(PREFIX.length) ran off the
end and yielded "", which the blank-remainder branch rejected regardless.
A length accident was standing in for a content comparison.
The added fixture miscases one word and keeps the line long enough that the
slice yields a non-blank remainder either way, so the only thing that can
reject it is a real comparison against the invariant. It is asserted to be
longer than the prefix, and its remainder asserted non-blank, so the
accident cannot come back silently.
Round: 12 mutations, 12 killed -- 11 on the first pass, M5 after this fix.
Refs #316
Verdict PASS. 963 tests / 949 pass / 0 fail / 14 skip, measured at the parent commit b0616b9; the +2 expect() and +1 claim-lint file this artifact itself adds are stated rather than measured, per #299, and both predictions were confirmed after it landed. 12 mutations, 12 killed. M5 is the round's return: it deleted the Action line's invariant check and the suite stayed green, because the fixture pinning that check was shorter than the prefix and was being rejected by a length accident instead of a content comparison. Case 8 is PARTIALLY discharged per gate ruling G-b, carrying the positive direction to build target 4 and #341. Refs #316
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Build target 7 of
docs/design/stories/47.md. AnchorsCLAIM-47.6.Evidence:
docs/evidence/316-20260923T131945Z.md.The task began by finding its own issue body wrong
#316 said "No module emits it, no lint rule requires it, and nothing parses it." Two of the three were false, and the second one would have wasted the task:
packages/core/src/guards/risk-mandate.ts:48-56already emitted a block — a private fifth copy, in TypeScript.test/skill-lint.test.ts:1369already required the block in every real skill body.So #316's first Done-when bullet — reconcile the four prose bodies to cite the block rather than restate it — proposed work a green test forbids.
skill-lint.test.ts:1362loops the whole real corpus, and those assertions encode CLAIM-41.5 and case 12 of41-plan.md, a closed Story with committed evidence. It would have retro-invalidated a discharged claim.The bullet's second branch was taken instead, and the issue body was corrected before work started. Gate rulings G-a/G-b/G-c are recorded on #47.
What shipped
One spec, two enforcement directions.
packages/core/src/guards/hard-failure.tsholds the contract once.renderHardFailureproduces conformant blocks; skill-lint's newhard-failure-blockrule (18th) validates prose against the same exported constants. A renderer and a validator that agree only by coincidence are two contracts — which is the shape this task existed to remove.risk-mandate.ts's private copy is deleted and rewired, and its test is untouched and still green — it pins the output verbatim, so leaving it alone is what makes it a regression guard. Mutation M12 confirms it is load-bearing.checkEvidenceMandateis the reader the doctrine atreferences/evidence-artifacts.md:140-144never had.The subject line is an open slot keyed
Milestone→Story→Goal(G-a). The fixed- Story: #{story}was unsatisfiable forgoal-create, which runs before any Story exists. All four shipped bodies pass unchanged.docs/design/02-roles.md's template is restated.Case 8 is partially discharged, deliberately
Build target 4's resolver cannot be built as specified — 21 of 54 artifacts carry no
| Claims |row, the other 33 use five incompatible notations, anddocs/evidence/is immutable, so the gaps can never be backfilled. Raised as #341.Only one direction is sound: a claim id in zero artifacts is certainly uncovered. A refusal needs only that one, so the refusal ships as a pure function over a supplied resolution and exports no resolver. The seam is asserted statically, because a future edit making it read the corpus would return identical verdicts while adopting the unsound direction (#322's M11 precedent).
Verification
963 tests / 949 pass / 0 fail / 14 skip / 3326
expect()· lint 0 across 98 files · typecheck clean · skill-lint 0 errors, 18 rules · claim-lint 116 files, 0 errors, 1 warning (expected) · purity 9, binding sweep 5 · both verifiers PASS at six.12 mutations, 12 killed. M5 is the return on the round: it deleted the Action line's invariant check and the suite stayed green, because the fixture pinning that check was shorter than the prefix and was being rejected by a length accident rather than a content comparison. Fixed, and the accident is now asserted against.
Refs #316, #47, #341