You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(lint): lesson-fail-open-catch-ban — // totem-context: escape does not suppress in diff-scoped otem lint --base main (pushes authors to matcher-evasion) #2214
The Tenet-4 rule lesson-fail-open-catch-ban (ast-grep kind: catch_clause with no throw_statement) has a sanctioned inline escape: // totem-context: <reason>. The escape does NOT suppress in the diff-scoped branch-lint path (totem lint --base main), even when the directive is correctly positioned (immediately above / inline on the catch line — the position #1889 establishes as the one that works in full-tree lint).
Effect: an author who adds a legitimately fail-soft catch at an IO/LLM boundary, annotates it with the sanctioned // totem-context: escape, and runs the branch-lint gate, still sees the rule FIRE. The escape works full-tree but not diff-scoped. This pushes authors toward matcher-evasion (rewriting catch (e) {} as .catch(() => …) so it parses as a call_expression, not a catch_clause) — strictly worse for the corpus than a recognized, attested escape. It hits every live IO/LLM adapter cohort-wide (the ADR-111 5b live-adapter family is the live exhibit).
#1889 is the position axis: isSuppressed (packages/core/src/rule-engine.ts) checks only the matched line + the immediately-preceding line, so a directive above try (multi-line body) misses the catch line in full-tree lint. This issue is the diff-scope axis: a correctly positioned directive is honored full-tree but not under totem lint --base main. Same rule + same escape, different failure surface. Sibling to #1614 (terminal-handler false-positive) and the #1444 fail-open cluster.
Diff-anchoring nuance (for the fix investigation)
Observed across ADR-111 slices 2 + 5b-ii (totem-claude journals claude-0115/0116): the suppression appears diff-addition-anchored — it can read as un-suppressed on a dirty/uncommitted tree and pass once the change is committed, yet was re-confirmed still firing in the committed --base main diff-scoped path during 5b-ii. The grandfathered extract-shared.ts:tryParseJson bare catch only passes because it is not in the diff. So the fix needs to pin down exactly how the diff-scoped lint maps suppression directives onto changed-line ranges — likely the directive line falls outside the diff hunk window the rule matched within, so isSuppressed's two-line check never sees it. First step: extend the #1889 fixture (packages/cli/src/_tenet4_verify.ts) with a diff-scoped --base main case to lock the exact trigger before touching the engine.
Second half — recognized fail-soft carve-out (strategy concurrence)
Per the Tenet-4 fault-line ruling (strategy-claude, 2026-06-20; doctrine half tracked mmnto-ai/totem-strategy#702), the durable principle is fail-soft on EXPECTED operational failures, fail-loud on UNEXPECTED bugs. Two patterns are canonical:
rethrow-unexpected (if (!(err instanceof SyntaxError)) throw err; return <fail-soft>) — the rule working; keep it.
blanket .catch(() => …) on an all-expected IO/LLM boundary — legitimate only when licensed by a loud global backstop (e.g. ADR-111 fold-C assertPipelineProductive: all-items-failed ⇒ throw).
Today the second pattern passes lint only by syntax evasion (it's a call_expression, not a catch_clause). The fix should let legitimacy be recognized, not dodged: consider a structured annotation (e.g. // totem-context: fail-soft backstop=<assertion>) the rule recognizes for an all-expected boundary guarded by a named loud backstop. strategy will align the #702 doctrine sharpening to whatever this lint fix ships as.
Acceptance
A correctly-positioned // totem-context: escape suppresses identically in full-tree lint AND totem lint --base main diff-scoped lint (fixture-locked, both paths).
The diff-anchoring behavior is characterized + tested (dirty vs committed; in-diff vs grandfathered).
(Optional, paired with Epic: Integrity Audit (Phase 2) #702) a recognized fail-soft carve-out so the all-expected .catch backstop pattern is attested rather than syntax-dodged.
Surface
The Tenet-4 rule
lesson-fail-open-catch-ban(ast-grepkind: catch_clausewith nothrow_statement) has a sanctioned inline escape:// totem-context: <reason>. The escape does NOT suppress in the diff-scoped branch-lint path (totem lint --base main), even when the directive is correctly positioned (immediately above / inline on thecatchline — the position #1889 establishes as the one that works in full-tree lint).Effect: an author who adds a legitimately fail-soft catch at an IO/LLM boundary, annotates it with the sanctioned
// totem-context:escape, and runs the branch-lint gate, still sees the rule FIRE. The escape works full-tree but not diff-scoped. This pushes authors toward matcher-evasion (rewritingcatch (e) {}as.catch(() => …)so it parses as acall_expression, not acatch_clause) — strictly worse for the corpus than a recognized, attested escape. It hits every live IO/LLM adapter cohort-wide (the ADR-111 5b live-adapter family is the live exhibit).Distinct from #1889
#1889 is the position axis:
isSuppressed(packages/core/src/rule-engine.ts) checks only the matched line + the immediately-preceding line, so a directive abovetry(multi-line body) misses thecatchline in full-tree lint. This issue is the diff-scope axis: a correctly positioned directive is honored full-tree but not undertotem lint --base main. Same rule + same escape, different failure surface. Sibling to #1614 (terminal-handler false-positive) and the #1444 fail-open cluster.Diff-anchoring nuance (for the fix investigation)
Observed across ADR-111 slices 2 + 5b-ii (totem-claude journals claude-0115/0116): the suppression appears diff-addition-anchored — it can read as un-suppressed on a dirty/uncommitted tree and pass once the change is committed, yet was re-confirmed still firing in the committed
--base maindiff-scoped path during 5b-ii. The grandfatheredextract-shared.ts:tryParseJsonbare catch only passes because it is not in the diff. So the fix needs to pin down exactly how the diff-scoped lint maps suppression directives onto changed-line ranges — likely the directive line falls outside the diff hunk window the rule matched within, soisSuppressed's two-line check never sees it. First step: extend the #1889 fixture (packages/cli/src/_tenet4_verify.ts) with a diff-scoped--base maincase to lock the exact trigger before touching the engine.Second half — recognized fail-soft carve-out (strategy concurrence)
Per the Tenet-4 fault-line ruling (strategy-claude, 2026-06-20; doctrine half tracked
mmnto-ai/totem-strategy#702), the durable principle is fail-soft on EXPECTED operational failures, fail-loud on UNEXPECTED bugs. Two patterns are canonical:if (!(err instanceof SyntaxError)) throw err; return <fail-soft>) — the rule working; keep it..catch(() => …)on an all-expected IO/LLM boundary — legitimate only when licensed by a loud global backstop (e.g. ADR-111 fold-CassertPipelineProductive: all-items-failed ⇒ throw).Today the second pattern passes lint only by syntax evasion (it's a
call_expression, not acatch_clause). The fix should let legitimacy be recognized, not dodged: consider a structured annotation (e.g.// totem-context: fail-soft backstop=<assertion>) the rule recognizes for an all-expected boundary guarded by a named loud backstop. strategy will align the #702 doctrine sharpening to whatever this lint fix ships as.Acceptance
// totem-context:escape suppresses identically in full-tree lint ANDtotem lint --base maindiff-scoped lint (fixture-locked, both paths)..catchbackstop pattern is attested rather than syntax-dodged.Related
// totem-context:position does not suppress for multi-line try bodies #1889 (position axis, same rule/escape), lesson-fail-open-catch-ban: don't flag catch blocks whose single call is a terminal handler () #1614 (terminal-handler FP), core+cli: fail-open cluster — sweep remaining silent-degradation paths for Tenet 4 compliance #1444 (fail-open cluster sweep), chore: migrate remainingtotem-ignoredirectives tototem-context:(ADR-071) #1457 (totem-contextmigration)mmnto-ai/totem-strategy#702(Tenet-4 sharpening — doctrine half, BLIND-gated)