instar 1.3.639 → 1.3.640

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.
@@ -0,0 +1,97 @@
1
+ # Side-Effects Review — Resilient Degradation Ladder Increment 2 (never-silent tracking)
2
+
3
+ **Slug:** `resilient-degradation-ladder-increment-2` · **Tier:** 2 (spec-driven; converged +
4
+ operator-approved). **Spec:** `docs/specs/resilient-degradation-ladder.md` §4, D6.
5
+
6
+ ## Summary of the change
7
+
8
+ The never-silent half of the operator's principle ("never silently remain degraded indefinitely"),
9
+ dark/dev-gated:
10
+ - `DegradationReporter` gains an open-degradation lifecycle (§4): `openDegradation` /
11
+ `resolveDegradation` / `sweepOpenDegradations` / `configureNeverSilent`, keyed on
12
+ `(component, framework)`, bounded by MAX_OPEN, O(1) per open/resolve.
13
+ - `IntelligenceRouter` gains `onHeuristicFallthrough` (fired at each NON-gating throw — the caller
14
+ will use its heuristic) + `onResolved` (fired at each successful real-LLM answer) hooks.
15
+ - Server wires the hooks to the reporter + calls `configureNeverSilent` (dev-gated via
16
+ `resolveDevAgentGate`) + starts a 60s `unref`'d sweep timer.
17
+ - `DEV_GATED_FEATURES` registration (`degradationLadderNeverSilent`).
18
+
19
+ ## Decision-point inventory
20
+
21
+ Frozen in spec §5 D6: escalateMs 15m, TTL 30m, MAX_OPEN 500, key (component, framework),
22
+ liveness-gated (≥1 retry to escalate; TTL-close otherwise), deduped per episode.
23
+
24
+ ## 1. Over-block / false positive
25
+
26
+ The big false-positive class (round-1): a run-once / idle component that degrades once and never
27
+ calls again. Closed by the LIVENESS gate — a degradation with 0 retries since open AUTO-CLOSES at
28
+ the TTL instead of escalating (tested). Only a degradation that genuinely RE-attempted and still
29
+ fell to heuristic (≥1 retry) escalates. Escalation is deduped per episode (re-escalates only after
30
+ another full window — tested).
31
+
32
+ ## 2. Under-block
33
+
34
+ Tracking only opens on a NON-gating heuristic fallthrough (where the heuristic actually runs); a
35
+ gating fail-closed is NOT tracked (it's the safe outcome, not a heuristic — tested:
36
+ onHeuristicFallthrough does NOT fire for a gating call). No-op when disabled.
37
+
38
+ ## 4. Signal vs authority
39
+
40
+ The escalation is an attention SIGNAL (a deduped fixed-template Telegram line), never a gate. It
41
+ takes no destructive action. The auto-resolve is observational (a successful call clears the open
42
+ state). Consistent with Signal-vs-Authority.
43
+
44
+ ## 5. Interactions — THE WEDGE-CRITICAL SECTION
45
+
46
+ §4 extends the exact `DegradationReporter` subsystem that caused the 2026-06-21 event-loop wedge
47
+ (`gateHealthAlert`→`toneGate.review`→router→`report`→`reportEvent`→recursion + growing-array
48
+ JSON.stringify). This increment is designed to NOT repeat it:
49
+ - The sweep NEVER calls `report()`/`reportEvent()`/`gateHealthAlert` — it surfaces an attention item
50
+ via `telegramSender` DIRECTLY (no toneGate, no events-array growth, no recursion). Tested: the
51
+ escalation path sends via the fake telegramSender, never through report.
52
+ - The open map is BOUNDED (MAX_OPEN; oldest evicted — tested) — no unbounded growth.
53
+ - open/resolve are O(1) Map mutations — no full-map serialize per event.
54
+ - The existing `_gatingHealthAlert` reentrancy guard + `MAX_EVENTS` cap are untouched.
55
+
56
+ ## 6. External surfaces
57
+
58
+ No new route. The only new egress is the deduped escalation line (the operator's explicit want).
59
+ The new config (`intelligence.degradationLadder.neverSilent`) shipped in Increment 1's types.
60
+
61
+ ## 6b. Operator-surface quality
62
+
63
+ N/A — no dashboard/approval surface. `openDegradationCount()` is a read-only observability getter.
64
+
65
+ ## Framework generality
66
+
67
+ Framework-agnostic — the hooks key on the resolved framework; works for whichever framework the
68
+ component routes to.
69
+
70
+ ## 7. Multi-machine posture
71
+
72
+ Machine-local: each machine's reporter tracks its own degradations + its own sweep timer. No
73
+ replicated state. (A sustained per-machine degradation could double-alert on a 2-machine setup for
74
+ one provider outage — accepted as a minor local-dedup limitation, noted in the spec §6.)
75
+
76
+ ## 8. Rollback cost
77
+
78
+ Trivial: dark on the fleet (`configureNeverSilent({enabled:false})` when not dev/configured) — the
79
+ lifecycle methods are all `if (!enabled) return` no-ops and no sweep timer is started. The router
80
+ hooks are no-ops downstream when disabled. Revert = remove an unused-on-fleet code path.
81
+
82
+ ## Evidence pointers
83
+
84
+ - `tests/unit/degradation-never-silent.test.ts` (6): open→resolve duration; run-once TTL-auto-close
85
+ (NO escalation); stuck (≥1 retry) escalates once past the window + deduped + re-escalates after a
86
+ new window; (component,framework) keying (no cross-resolution); bounded (MAX_OPEN evict);
87
+ disabled=no-op.
88
+ - `tests/unit/degradation-ladder.test.ts` (+3 hook cases): onResolved on success; onHeuristicFallthrough
89
+ on a non-gating exhaustion; onHeuristicFallthrough does NOT fire for a gating call.
90
+ - The `DEV_GATED_FEATURES` both-sides wiring test confirms `degradationLadderNeverSilent` resolves
91
+ live-on-dev / dark-on-fleet. Full `npm run lint` (incl. lint-dev-agent-dark-gate) + `tsc` green.
92
+
93
+ ## Conclusion
94
+
95
+ Delivers the never-silent guarantee — a heuristic fallback can no longer silently persist
96
+ indefinitely: it auto-resolves on recovery and escalates if genuinely stuck — built carefully to NOT
97
+ repeat the wedge it extends. Dark/dev-gated, no-op when off. Ship.