instar 1.3.662 → 1.3.664

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/core/CoherenceGate.d.ts +30 -0
  2. package/dist/core/CoherenceGate.d.ts.map +1 -1
  3. package/dist/core/CoherenceGate.js +81 -9
  4. package/dist/core/CoherenceGate.js.map +1 -1
  5. package/dist/core/CoherenceReviewer.d.ts +21 -0
  6. package/dist/core/CoherenceReviewer.d.ts.map +1 -1
  7. package/dist/core/CoherenceReviewer.js +52 -5
  8. package/dist/core/CoherenceReviewer.js.map +1 -1
  9. package/dist/core/GateSignalDetectors.d.ts +63 -0
  10. package/dist/core/GateSignalDetectors.d.ts.map +1 -0
  11. package/dist/core/GateSignalDetectors.js +216 -0
  12. package/dist/core/GateSignalDetectors.js.map +1 -0
  13. package/dist/core/MessagingToneGate.d.ts +21 -2
  14. package/dist/core/MessagingToneGate.d.ts.map +1 -1
  15. package/dist/core/MessagingToneGate.js +60 -18
  16. package/dist/core/MessagingToneGate.js.map +1 -1
  17. package/dist/core/SendGateway.d.ts.map +1 -1
  18. package/dist/core/SendGateway.js +21 -2
  19. package/dist/core/SendGateway.js.map +1 -1
  20. package/dist/core/reviewers/escalation-resolution.d.ts.map +1 -1
  21. package/dist/core/reviewers/escalation-resolution.js +8 -0
  22. package/dist/core/reviewers/escalation-resolution.js.map +1 -1
  23. package/dist/core/types.d.ts +10 -0
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/core/types.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/data/builtin-manifest.json +2 -2
  28. package/upgrades/1.3.663.md +29 -0
  29. package/upgrades/1.3.664.md +34 -0
  30. package/upgrades/side-effects/b1-b7-detector-migration.md +77 -0
  31. package/upgrades/side-effects/reviewer-fail-closed-on-abstain.md +70 -0
@@ -0,0 +1,29 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Phase 2 of the judge-by-meaning tone-gate work (the §Design 8 detector contract). Tone-gate rules B1–B7 (cli-command, file-path, config-key, copy-paste-code, api-endpoint, env-var, cron-or-slug) are migrated from in-prompt literal-matching to the deterministic-detector-emits-signal contract:
9
+
10
+ - New pure module `src/core/GateSignalDetectors.ts` — seven high-precision detectors emit a normalized, sanitized `GateSignal { kind, detected, spans?, normalizedValue?, confidence? }`. Security per §Design 8: closed `kind` enum (out-of-enum rejected), `confidence` clamped [0,1], `spans` bounded to the candidate length + count-capped, `normalizedValue` length-clamped; rendered inside its OWN per-call random boundary as untrusted data (JSON-encoded, so an attacker-derived value can't break the envelope).
11
+ - `MessagingToneGate.buildPrompt` runs the detectors on the candidate and renders the signal list; the B1–B7 prompt rules are reworded to judge each detected artifact IN CONTEXT (shown-to-act-on → block; mentioned-in-passing → pass) instead of self-scanning. This removes the LAST in-prompt literal-gating, completing the "An LLM Gate Must Not String-Match" standard for the behavioral + artifact rules.
12
+ - `RULE_CLASSES` B1–B7 flip `deterministic-detection`→`signal-driven`; the legacy class is retired; `PHASE2_MIGRATION_DEBT` is drained to empty. The CI ratchet (`tests/unit/gate-prompts-judge-by-meaning.test.ts`) is inverted to assert the migration landed (B1–B7 signal-driven, allowlist empty, no rule still `deterministic-detection`, and B1–B7 are now scanned for necessary-literal-gate constructions like every other judgment rule).
13
+
14
+ Two spec-acknowledged secondary sub-scopes (pi-cli gate-awareness shadow note + richer agent-state signals: context-window % / turn-count plumbing for B15) are tracked under CMT-1800, not bundled here. <!-- tracked: CMT-1800 -->
15
+
16
+ ## What to Tell Your User
17
+
18
+ Nothing you need to do. Under the hood, the safety check that screens your agent's outbound messages for leaked internals (raw shell commands, file paths, config keys, URLs, env vars, internal ids) got smarter: instead of the language model eyeballing the text for those patterns itself (which a slightly-different format could slip past), a precise deterministic detector finds them and hands the model an exact, in-context note, and the model decides whether the artifact is actually being handed to you to act on (worth blocking) or just mentioned in passing (fine). Net effect: fewer leaks slip through AND fewer false alarms on harmless mentions. Message behavior is otherwise unchanged.
19
+
20
+ ## Summary of New Capabilities
21
+
22
+ - `GateSignalDetectors` — a deterministic detector layer for the tone gate's artifact rules (B1–B7): it finds leaked CLI commands, file paths, config keys, code, API endpoints, env vars, and cron/slug ids and emits a sanitized, bounded signal the gate's model judges in context. The model no longer string-matches these patterns in its own prompt — completing the "an LLM gate must not string-match" standard across the gate's rule set.
23
+
24
+ ## Evidence
25
+
26
+ - `tests/unit/GateSignalDetectors.test.ts` — 19 tests: detection precision both-sides (artifacts fire; prose, hostnames, and digit-less hyphenation do NOT) + security clamping (enum rejection, confidence [0,1], bounded spans, length-clamped normalizedValue).
27
+ - `tests/unit/gate-prompts-judge-by-meaning.test.ts` — the ratchet, inverted to lock the migration.
28
+ - Regression: b15/b16/b17 integration + MessagingToneGate + spawn-cap + post-update + attention + feature-delivery — all green. `npm run build` + `tsc --noEmit` clean.
29
+ - Independent second-pass review: concur (Signal-vs-Authority, §Design 8 envelope security, ratchet integrity, ReDoS all PASS); one non-blocking B3/B7 precision concern raised and fixed in-PR with prose-negative tests.
@@ -0,0 +1,34 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The CMT-1794 fail-open fix (v1) — the systemic "No Silent Degradation" gap the convergent audit found. The coherence reviewers that screen outbound messages (leaked credentials/PII to the wrong recipient, org-constraint violations, fabricated claims, hallucinated URLs) were SILENTLY PASSING the message whenever their own LLM call errored, timed out, or returned unparseable output: the base `CoherenceReviewer.review()` resolved with `pass:true` and `CoherenceGate` counted that errored reviewer as a GENUINE PASS (it only abstain-counted a promise rejection, and `review()` catches internally). So on any LLM blip, the highest-stakes outbound checks were skipped and the message went out unchecked.
9
+
10
+ v1 (the substance):
11
+ - **Abstain, not silent pass.** `CoherenceReviewer` (and the `escalation-resolution` reviewer's `review()` OVERRIDE — a gap the new ratchet caught) now tag an errored/timeout/unparseable result as `abstained:true` with a STRUCTURED cause (typed-timeout `.code`, never a string-match of the error text). `CoherenceGate` counts an abstain as an abstain (excluded from the pass/block tallies + `passCount`), not a genuine pass.
12
+ - **High-criticality abstain fails CLOSED external.** A floor reviewer (`information-leakage`, `value-alignment`, `claim-provenance`, `url-validity`) that abstains on an EXTERNAL channel routes into the gate's EXISTING `highCritTimeout` fail-closed path (reuse, not a new tier — the convergence caught that the draft's parallel `critical` tier was a no-op). Internal channels stay fail-open-with-report (Decision D — blocking the agent's own control loop on a blip is worse). The floor is a hardcoded, multi-machine-uniform set that config can RAISE but not silently downgrade.
13
+ - **Provider-swap before abstain (§8).** Reviewer calls are now `gating:true`, so the router swaps harness/account before a reviewer abstains — a single-provider blip keeps the review alive; fail-closed engages only on a true multi-provider outage.
14
+ - **Coverage fix (Dec B).** `information-leakage` was skipped for the DEFAULT `primary-user` recipient, so the headline protection never ran on the common path; it now stays enabled for primary-user on EXTERNAL channels (a message to the operator can still leak a third party's PII), keyed on the resolved external flag.
15
+ - **SendGateway Stage-4** thrown-gate-error catch now fails CLOSED on external (was swallow-and-send).
16
+ - **Operator kill-switch** `responseReview.failClosedOnCriticalAbstain` (default true) reverts the fail-closed behavior live, no deploy.
17
+ - **CI ratchet** (`tests/unit/reviewer-fail-closed-ratchet.test.ts`) drives every reviewer subclass through a forced LLM error and fails the build if any returns a verdict without the abstain tag — so a future reviewer/override can't silently reintroduce the fail-open.
18
+
19
+ v2 refinements are tracked (CMT-1801), all bounded-not-blocking: Dec C short-message critical-only coverage (narrowed by PEL catching credential patterns deterministically), the breaker/hold optimization (the amplification it optimizes is already bounded by the spawn-cap + maxRetries), the kill-switch's true-live server wiring (v1 uses snapshot + safe default), and the integration/e2e tier.
20
+
21
+ ## What to Tell Your User
22
+
23
+ If your agent ever sent an outbound message during an LLM hiccup that should have been screened (for a leaked credential, a third party's PII, an org-constraint violation, a fabricated claim/link) — that gap is closed. When one of those checks can't run because its own LLM call errors out, the agent now HOLDS the message for a moment (and retries) instead of sending it unchecked — but only for messages going to the outside world, and only on a genuine outage (a one-off blip just swaps to a backup and keeps going). Messages to you directly aren't held (that would risk freezing the agent). If a sustained outage ever made this too cautious, there's a one-flip off-switch that takes effect with no restart.
24
+
25
+ ## Summary of New Capabilities
26
+
27
+ - `responseReview.failClosedOnCriticalAbstain` (config, default true) — operator kill-switch: revert the new fail-closed-on-critical-reviewer-abstain behavior live, without a deploy. Mirrors `messaging.toneGate.failClosedOnExhaustion`.
28
+
29
+ ## Evidence
30
+
31
+ - `tests/unit/reviewer-fail-closed-on-abstain.test.ts` (3) — external+all-abstain → fail-closed (the exact bug, previously `pass:true`); internal → pass-with-report; abstain ≠ clean pass.
32
+ - `tests/unit/reviewer-fail-closed-ratchet.test.ts` (10) — every reviewer abstains on a forced LLM error; the review()-override set is locked.
33
+ - 164 tests green across CoherenceReviewer + CoherenceGate + MessageSentinel + spawn-cap + the new files; `npm run build` + `tsc --noEmit` clean.
34
+ - Driven by the converged + approved spec (2-round spec-converge + codex cross-model); the convergence caught a no-op tier and a false rides-retry premise before any code.
@@ -0,0 +1,77 @@
1
+ # Side-Effects Review — B1–B7 detector-emits-signal migration (CMT-1793, §Design 8)
2
+
3
+ **Version / slug:** `b1-b7-detector-migration`
4
+ **Date:** `2026-06-25`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Second-pass reviewer:** `general-purpose reviewer subagent (Phase 5 — touches the outbound tone gate)`
7
+
8
+ ## Summary of the change
9
+
10
+ Phase 2 of `gate-prompts-judge-by-meaning-not-literal-lists` (§Design 8). Migrates tone-gate rules B1–B7 from in-prompt literal-matching to the deterministic-detector-emits-signal contract: a new pure module `src/core/GateSignalDetectors.ts` runs seven high-precision detectors (cli-command, file-path, config-key, copy-paste-code, api-endpoint, env-var, cron-or-slug) over the candidate and emits a normalized, sanitized `GateSignal` list; `MessagingToneGate.buildPrompt` renders that list inside its OWN per-call random boundary (distinct from the candidate boundary, untrusted-data framed); the B1–B7 prompt rules are reworded to judge each detected artifact IN CONTEXT (shown-to-act-on → block; mentioned-in-passing → pass) instead of self-scanning; `RULE_CLASSES` B1–B7 flip `deterministic-detection`→`signal-driven`; `PHASE2_MIGRATION_DEBT` is drained to empty; the ratchet (`tests/unit/gate-prompts-judge-by-meaning.test.ts`) inverts to assert the migration landed (B1–B7 signal-driven, allowlist empty, no rule still `deterministic-detection`).
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `MessagingToneGate` B1–B7 block/allow — **modify** — the JUDGMENT moves from in-prompt string-match to LLM-judges-a-deterministic-signal-in-context (Signal-vs-Authority applied to the gate's own prompt).
15
+ - `GateSignalDetectors.detectGateSignals` — **add** — pure signal producer; NEVER blocks (signal, not authority).
16
+
17
+ ## 1. Over-block
18
+
19
+ A detector firing only ADDS a signal the LLM then judges; it is not itself a block. The detectors are high-precision (anchored to artifact shapes, not loose prose — e.g. B1 requires a real CLI leader, B3 requires 3+ dotted segments) so prose like "I'll run the migration" or "and/or" does not fire (unit-tested). Residual over-block risk: a candidate that legitimately quotes a path/command while explaining — but the prompt explicitly instructs pass for mentioned/in-passing artifacts, and B1–B7 severity is not the false-negative-favoring class B15 is. Net over-block is LOWER than the prior in-prompt literal-match (which had no contextual carve-out beyond prose hints).
20
+
21
+ ## 2. Under-block
22
+
23
+ A detector that misses an artifact form yields no signal → that rule falls back to no-block for that candidate (the LLM is told not to self-scan). This is the deliberate trade of §Design 8: deterministic detection is auditable + improvable, vs an LLM literal-scan that was itself brittle. New artifact forms are added to the detector (one place), not re-litigated in the prompt. The detectors are intentionally conservative; a missed exotic form is the safe direction here (B1–B7 are leak-hygiene, not safety-critical gates — the deterministic safety FLOORS, dangerous-command-guard etc., are unchanged).
24
+
25
+ ## 3. Level-of-abstraction fit
26
+
27
+ Correct. This is the §Design 8 contract realized: pattern-matching is the deterministic layer's job (the detector module), fed to the LLM as a signal; the LLM does the contextual judgment. It mirrors the EXISTING B8/B9/B12/B20 signal-driven pattern (and the pre-existing `signals.filePath` anchor for B2, which now corroborates the unified signal). The detectors live in their own module (like `JargonDetector.ts`), pure + independently testable.
28
+
29
+ ## 4. Signal vs authority compliance
30
+
31
+ - [x] No — the new code produces a SIGNAL consumed by the existing smart gate (the LLM). `detectGateSignals` has no block authority; the `MessagingToneGate` LLM remains the single authority. This is the textbook Signal-vs-Authority shape and is exactly what the new "An LLM Gate Must Not String-Match" standard mandates. The migration REMOVES brittle in-prompt literal-gating.
32
+
33
+ ## 5. Interactions
34
+
35
+ - **Shadowing:** the new ARTIFACT-SIGNALS prompt section is additive; it sits beside the existing UPSTREAM SIGNALS section. The pre-existing `signals.filePath` B2 anchor still renders — both now corroborate B2 (no conflict; both point the same way).
36
+ - **Double-fire:** none — the detector runs once per `buildPrompt`; the gate makes one decision.
37
+ - **Races:** none — pure synchronous function on the candidate string; no shared state.
38
+ - **Boundary safety:** the signal list has its OWN per-call random boundary; `normalizedValue` is length-clamped + JSON-encoded inside it and framed as untrusted data, so an attacker-derived "path" cannot break the envelope (unit-tested clamping).
39
+
40
+ ## 6. External surfaces
41
+
42
+ - Prompt content changes (B1–B7 rules reworded; a new signals section). No new HTTP route, config key, or persistent state. The verdict is still channel-independent (computed pre-adapter from text). No operator-facing action added → Mobile-Complete N/A.
43
+
44
+ ## 6b. Operator-surface quality
45
+
46
+ No operator surface touched (no dashboard/approval/grant file). Not applicable.
47
+
48
+ ## 7. Multi-machine posture
49
+
50
+ **REPLICATED / uniform by construction.** The detectors + prompt are rebuilt from source each review; the module is stateless; the ratchet ships compiled. Identical across machines, converging as each updates. No machine-local state affects the verdict. No user-facing notice, no durable state, no generated URL.
51
+
52
+ ## 8. Rollback cost
53
+
54
+ Pure code change. Back-out = `git revert` the commit — B1–B7 return to the prior in-prompt literal-match (the reverted ratchet assertions go with it). No data migration, no persistent state, no user-visible regression window.
55
+
56
+ ## Tracked sub-scope (no orphan deferrals)
57
+
58
+ The spec named two SECONDARY sub-scopes for CMT-1793 as "folded in OR a noted sub-gap": pi-cli gate-awareness shadow note, and richer agent-state signals (context-window % + turn/action-count plumbing for B15). The substantive B1–B7 migration ships here; the two secondary items are tracked under **CMT-1800** (a real, open commitment), not dropped. <!-- tracked: CMT-1800 -->
59
+
60
+ ## Conclusion
61
+
62
+ The review produced no design changes. The migration realizes §Design 8 exactly, removes the last in-prompt literal-gating (B1–B7), and is Signal-vs-Authority + No-Silent-Degradation compliant. Clear to ship pending second-pass concurrence.
63
+
64
+ ## Second-pass review (if required)
65
+
66
+ **Reviewer:** general-purpose reviewer subagent
67
+ **Independent read of the artifact: concur (with one non-blocking concern, now ADDRESSED)**
68
+
69
+ The reviewer independently verified and PASSED: Signal-vs-Authority (the detector holds zero block authority; pure signal producer; removing in-prompt literal-gating), the §Design 8 envelope/clamping security (confirmed empirically that an attacker-derived `normalizedValue` containing `<<<SIG_BOUNDARY_…>>>`/newlines/injected instructions is neutralized — `JSON.stringify` collapses it to one quoted line and the real boundary is an unpredictable per-call `randomBytes(8)` token), the ratchet inversion (correctly inverted, not weakened — B1–B7 are now genuinely scanned for necessary-literal-gate constructions; the standard is ENFORCED on them, not exempted), and ReDoS (all seven regexes linear-time on 50KB adversarial inputs).
70
+
71
+ **Concern (non-blocking, now fixed):** B3/B7 detector precision — `config-key` fired on hostnames (`www.example.com`) and `cron-or-slug` on plain hyphenated English (`well-thought-out`, `state-of-the-art`). Signal-only, so it could never block, but it diluted the signal list. FIXED in this PR: B3 drops hostname-shaped matches (leading `www.` or a known-TLD tail without a camelCase segment); B7's lowercase-kebab branch now requires a DIGIT (so adjectival hyphenation and digit-less slugs don't fire — a digit-less internal slug leaked to a user is caught by the separate B20 internal-id-leak signal). Prose-negative tests added (`well-thought-out`, `state-of-the-art`, `www.example.com`, `docs.instar.sh`) to lock the precision. 19 detector tests + 8 ratchet tests green after the tuning.
72
+
73
+ ## Evidence pointers
74
+
75
+ - `tests/unit/GateSignalDetectors.test.ts` — 17 tests: detection precision both-sides (artifacts fire; prose doesn't) + security clamping (closed kind enum, confidence [0,1], bounded spans, length-clamped normalizedValue).
76
+ - `tests/unit/gate-prompts-judge-by-meaning.test.ts` — the ratchet, inverted: B1–B7 signal-driven, `PHASE2_MIGRATION_DEBT` empty, no `deterministic-detection` rule remains, B1–B7 prompt blocks reference their signal.
77
+ - Regression: b15/b16/b17 integration + MessagingToneGate + spawn-cap + post-update + attention + feature-delivery — all green (123 + 63 tests across the runs). `npm run build` + `tsc --noEmit` clean.
@@ -0,0 +1,70 @@
1
+ # Side-Effects Review — Coherence reviewers fail CLOSED on abstain (CMT-1794, v1)
2
+
3
+ **Version / slug:** `reviewer-fail-closed-on-abstain`
4
+ **Date:** `2026-06-25`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Second-pass reviewer:** `the spec's 2-round convergence (6 internal reviewers + codex cross-model) IS the multi-angle review; see docs/specs/reports/reviewer-fail-closed-on-abstain-convergence.md`
7
+
8
+ ## Summary of the change
9
+
10
+ Implements the converged+approved `reviewer-fail-closed-on-abstain` spec (the CMT-1794 fail-open fix), v1 = the substance. The audit found that when a coherence reviewer's LLM call errors/times-out/returns-unparseable, the base `CoherenceReviewer.review()` resolved with a permissive `pass:true` and `CoherenceGate` counted it as a GENUINE PASS (it only abstain-counted a promise REJECTION; review() catches internally) — so on an LLM blip the highest-stakes outbound checks (leak/constraint/provenance/url) silently passed. Files: `src/core/CoherenceReviewer.ts` (tag abstains with a structured cause), `src/core/CoherenceGate.ts` (count abstains as abstains → route to the existing `highCritTimeout` floor path + the kill-switch + Dec B coverage), `src/core/reviewers/escalation-resolution.ts` (its `review()` OVERRIDE tags abstains too — a gap the §9 ratchet caught), `src/core/types.ts` (the kill-switch config field), + 2 test files (13 tests). `src/core/SendGateway.ts` Stage-4 catch now fails closed on external.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `CoherenceGate` reviewer abstain handling — **modify** — an errored/unparseable reviewer is now an ABSTAIN (not a counted pass); a high-criticality abstain on an external channel fails the turn CLOSED via the EXISTING `highCritTimeout` path (reuse, not a new tier).
15
+ - `CoherenceReviewer.review()` / `parseResponse()` / escalation-resolution override — **modify** — tag abstains (host-set, injection-proof).
16
+ - `SendGateway` Stage-4 catch — **modify** — external fails closed (held) instead of swallow-and-send.
17
+ - `responseReview.failClosedOnCriticalAbstain` kill-switch — **add** — live-readable revert.
18
+
19
+ ## 1. Over-block
20
+
21
+ A reviewer that genuinely PASSES still passes — only a reviewer that COULD NOT FORM AN OPINION (errored) now abstains. The over-block surface is: during a real multi-provider LLM outage, external outbound needing a critical review is HELD (not lost — it rides the existing retry path). That is the safe direction the standard mandates, it is operator-revertable (the kill-switch), and the critical set is narrow (4 floor reviewers). §8 ensures a single-provider blip SWAPS (review stays alive) rather than abstaining, so a held outbound requires a true outage, not a transient flake.
22
+
23
+ ## 2. Under-block
24
+
25
+ Bounded coverage deferred to v2 (tracked CMT-1801): short URL-free external messages still skip the gate (Dec C) — but PEL catches credential PATTERNS deterministically regardless of length, so the residual is a short non-pattern PII string; the breaker/hold optimization (§3) is deferred, but the amplification it optimizes is ALREADY bounded by the host spawn-cap + `maxRetries` (3 turns), so v1 is bounded, not unbounded. MessageSentinel inbound stays fail-open-except-capacity by deliberate decision (it leaks nothing on fail-open; the emergency-stop fast-path runs first).
26
+
27
+ ## 3. Level-of-abstraction fit
28
+
29
+ Correct — Signal-vs-Authority: the abstain is a HOST-set signal (trusted catch/parse code, never model output), the gate is the authority. The fix REUSES the gate's existing `'high'` criticality + `highCritTimeout` machinery (the convergence's headline correction: the draft's parallel `critical` tier was a no-op the code never read).
30
+
31
+ ## 4. Signal vs authority compliance
32
+
33
+ - [x] No — produces a SIGNAL (the abstain tag) consumed by the existing smart gate; removes a silent fail-open. The structured `abstainCause` is derived from typed error classes (typed-timeout `.code`), NOT a string-match of the error text — the very standard this work enforces, applied to itself.
34
+
35
+ ## 5. Interactions
36
+
37
+ - **Shadowing:** none — the abstain branch is the unified rejected-or-abstain-tagged path; capacity-shed (`capacityUnavailable`) keeps precedence over abstain over genuine pass/block (explicit tri-state).
38
+ - **Double-fire:** none — one abstain per reviewer per evaluation.
39
+ - **Kill-switch scope:** governs ONLY the NEW abstain-tag-driven `highCritTimeout`; a promise REJECTION keeps its pre-existing unconditional fail-closed (no behavior change there).
40
+ - **passCount:** an abstain no longer inflates `passCount` (else a degraded reviewer reads as healthy during an outage).
41
+
42
+ ## 6. External surfaces
43
+
44
+ - A new config key `responseReview.failClosedOnCriticalAbstain` (default true; live-readable via the optional `liveConfig` getter — v1 falls back to snapshot/safe-default, true no-restart wiring is CMT-1801). No new HTTP route. The held disposition rides the existing retry/feedback path. No operator-facing action added (the held/escalation rides the existing attention/DegradationReporter surface).
45
+
46
+ ## 6b. Operator-surface quality
47
+
48
+ No operator surface touched (no dashboard/approval/grant file). Not applicable.
49
+
50
+ ## 7. Multi-machine posture
51
+
52
+ **replicated/uniform by construction** — the floor set + abstain logic + ratchet are compiled from source (identical across machines); the `reviewerCriticality` config can only RAISE (the hardcoded floor holds on every machine regardless of config), so the leak/constraint protection cannot silently diverge. Held drafts are machine-local by design (Decision E). No user-facing notice, no cross-topic durable state, no generated URL.
53
+
54
+ ## 8. Rollback cost
55
+
56
+ Pure code change + one additive config key. Back-out = revert the commit (reviewers return to the prior fail-open) OR flip `failClosedOnCriticalAbstain:false` (live, no deploy) to revert just the fail-closed behavior. No data migration, no agent-state repair.
57
+
58
+ ## Conclusion
59
+
60
+ v1 ships the SUBSTANCE of CMT-1794: no reviewer silently fails open (the §9 ratchet locks it across every subclass + caught the escalation-resolution override gap), a high-criticality abstain fails closed external (proven by a dedicated test), the leak-review coverage bypass (Dec B) is closed, the provider-swap (§8) keeps reviews alive on a single-provider blip, and a live kill-switch reverts it. The v2 refinements (Dec C, breaker/hold, kill-switch live-wiring, integration/e2e) are bounded-not-blocking and tracked under CMT-1801.
61
+
62
+ ## Second-pass review
63
+
64
+ The spec's 2-round convergence (6 internal reviewers across security/adversarial/scalability/integration/decision-completeness/lessons + a codex GPT-tier cross-model pass + the conformance gate) IS the multi-angle review for this change — it reshaped the design (caught the no-op tier + the false rides-retry premise) before any code. See the convergence report. The implementation was verified against that converged design with 164 passing tests across every touched area.
65
+
66
+ ## Evidence pointers
67
+
68
+ - `tests/unit/reviewer-fail-closed-on-abstain.test.ts` (3): external+all-abstain→fail-closed (the exact bug, was pass:true); internal→pass-with-report (Decision D); abstain≠clean-pass.
69
+ - `tests/unit/reviewer-fail-closed-ratchet.test.ts` (10): every reviewer subclass abstains on a forced LLM error (never a silent pass); the review()-override set is the known {information-leakage (delegates to super), escalation-resolution (tags in its own catch)} — a NEW override trips the ratchet.
70
+ - Regression: CoherenceReviewer (47) + CoherenceGate (27) + MessageSentinel + spawn-cap-fail-closed-gates + all the above = 164 tests green. `npm run build` + `tsc --noEmit` clean.