instar 1.3.639 → 1.3.641

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,108 @@
1
+ # Side-Effects Review — Resilient Degradation Ladder Increment 3 (queue rung)
2
+
3
+ **Slug:** `resilient-degradation-ladder-increment-3` · **Tier:** 2 (spec-driven; the spec converged +
4
+ operator-approved, rode Increments 1/2). **Spec:** `docs/specs/resilient-degradation-ladder.md` §3b.3,
5
+ §3c, D1/D5/D7.
6
+
7
+ ## Summary of the change
8
+
9
+ The third (and final) rung of the operator's degradation principle — "prefer slowing down over
10
+ falling back". Dark/dev-gated:
11
+
12
+ - `IntelligenceRouter` gains a DEFERRABLE **queue rung** (`tryDeferrableQueue`): a non-gating call
13
+ that has exhausted backoff + framework-swap now WAITS for capacity in a dedicated `LlmQueue`
14
+ (`background` lane) before dropping to the caller's heuristic. The enqueued `provider.evaluate`
15
+ honors the account-global breaker's `retryAfterMs` via `acquireOrWait` — that IS the §3b.3
16
+ rate-awareness. Inserted at BOTH fall-through points (no-swap-configured AND swap-exhausted) before
17
+ the existing `onHeuristicFallthrough` + throw.
18
+ - A structural `DeferrableQueue` interface declared in `core` (the real `LlmQueue` satisfies it) so
19
+ `core` does NOT import `monitoring` (no layering cycle).
20
+ - `LlmQueue` gains an opt-in `backgroundDispatchMinGapMs` (§3c herd guard): a jittered minimum gap
21
+ between BACKGROUND-lane dispatches; interactive bypasses. Default 0 = OFF (today's greedy drain).
22
+ - Server wires a DEDICATED `LlmQueue` for the rung (built ONLY when the gate-resolved `queueEnabled`
23
+ is true), gate-resolves `queueEnabled`/`queueAttemptTimeoutMs` via `resolveDevAgentGate`, and the
24
+ ladder object is now present when ANY rung (backoff OR queue) is active.
25
+ - `DEV_GATED_FEATURES` registration (`degradationLadderQueue`).
26
+ - Config type extended: `degradationLadder.queue` → `{ enabled?, attemptTimeoutMs?, drainMinGapMs? }`.
27
+
28
+ ## Decision-point inventory (frozen)
29
+
30
+ D1 (config + dark rollout), D5 (`gating:true` ⇒ queue-skipped, code-enforced), D7 (reason taxonomy
31
+ `queued` / `queue-rejected`). Increment-3 bounds (sensible defaults, dark/reversible, fit D1's
32
+ `{enabled, …bounds}` structure): `queueAttemptTimeoutMs` 60000, dedicated queue `maxConcurrent` 1 +
33
+ `maxDailyCents` 25, `drainMinGapMs` 0 (off).
34
+
35
+ **D9 (new, recorded here) — the §3c herd guard is opt-in, realized two ways:** (1) the PRIMARY
36
+ rate-awareness is the provider-layer `acquireOrWait` — each enqueued `evaluate` waits for the
37
+ account-global breaker window, so queued calls don't hammer a rate-limited account; (2) the dedicated
38
+ queue's `maxConcurrent: 1` serializes deferrable retries (naturally herd-safe); (3) `drainMinGapMs`
39
+ adds an OPTIONAL jittered inter-dispatch gap (off by default). Explicit pacing is opt-in so existing
40
+ `LlmQueue` callers (PresenceProxy / PromiseBeacon) see ZERO behavior change. This faithfully delivers
41
+ §3b.3/§3c without rebuilding shared infrastructure (round-1 "extend, don't rebuild").
42
+
43
+ ## 1. Over-block / false positive
44
+
45
+ The rung only ADDS a wait-for-capacity step on a DEFERRABLE call that already exhausted swap; it never
46
+ blocks an interactive/gating call. A queued call that fails or is rejected falls through to exactly
47
+ today's heuristic — strictly no worse than before. No new block surface.
48
+
49
+ ## 2. Under-block
50
+
51
+ A GATING call NEVER reaches the queue rung: `deferrable = !gating && options.deferrable === true`, so
52
+ `if (deferrable) …tryDeferrableQueue` is structurally unreachable for a gate (D5). Unit-tested
53
+ (`GATING is NEVER queued`). The gating fail-closed boundary is unchanged.
54
+
55
+ ## 4. Signal vs authority
56
+
57
+ The queue rung takes no destructive action — it waits for capacity then returns a real answer, or
58
+ falls through. The two `onDegrade` emissions (`queued` / `queue-rejected`) are observability signals
59
+ (D7), never gates. Consistent with Signal-vs-Authority.
60
+
61
+ ## 5. Interactions — wedge-safety
62
+
63
+ Reuses the EXISTING `LlmQueue` (the wedge-safe, bounded, daily-capped queue). The new drain-pacing is
64
+ a bounded `setTimeout` coalesced to at most one pending timer (no stacking), `unref`'d (never holds the
65
+ process open). No new recursion, no growing array, no `report()`/`reportEvent()` call from the rung —
66
+ the 2026-06-21 DegradationReporter wedge class cannot recur here. The dedicated queue is built only
67
+ when the rung is active, so the fleet allocates nothing.
68
+
69
+ ## 6. External surfaces
70
+
71
+ No new route. No new egress — a queued call is the SAME provider.evaluate that would otherwise have
72
+ run; it just waits for capacity. The new config (`intelligence.degradationLadder.queue.*`) is opt-in.
73
+
74
+ ## Framework generality
75
+
76
+ Framework-agnostic — the rung keys on the resolved framework and enqueues whatever provider the
77
+ component routes to.
78
+
79
+ ## 7. Multi-machine posture
80
+
81
+ Machine-local: each machine's router + its dedicated queue are independent. No replicated state, no
82
+ cross-machine contract (the dedicated queue is per-process, same posture as the existing per-sentinel
83
+ queues).
84
+
85
+ ## 8. Rollback cost
86
+
87
+ Trivial: dark on the fleet (`queueEnabled` resolves dark; no dedicated queue is built, the ladder's
88
+ `queueEnabled` is false, and `tryDeferrableQueue` returns `{ ok: false }` immediately ⇒ exactly
89
+ today's heuristic-on-exhaustion). Revert = remove an unused-on-fleet code path. `drainMinGapMs` defaults
90
+ 0 so existing `LlmQueue` callers are untouched.
91
+
92
+ ## Evidence pointers
93
+
94
+ - `tests/unit/degradation-ladder.test.ts` (+7 queue cases): queue success + onResolved; order
95
+ backoff/swap → queue → success; GATING never queued (D5); enqueue REJECTION ⇒ heuristic fallthrough
96
+ + onHeuristicFallthrough; non-deferrable never queued; queueEnabled-but-no-llmQueue no-op;
97
+ queueEnabled:false not enqueued.
98
+ - `tests/unit/LlmQueue.test.ts` (+3 pacing cases): paces a 2nd background dispatch when the gap is set;
99
+ no pacing when off (both start at once); interactive bypasses pacing.
100
+ - `tests/unit/devGatedFeatures-wiring.test.ts` auto-covers `degradationLadderQueue` (live-on-dev /
101
+ dark-on-fleet). `tests/unit/no-silent-fallbacks.test.ts` stays at baseline 476. Full `tsc` +
102
+ `lint-dev-agent-dark-gate` green.
103
+
104
+ ## Conclusion
105
+
106
+ Delivers the final rung of the operator's degradation principle — a deferrable call now WAITS for
107
+ capacity (slow down) before ever dropping to a brittle heuristic, with the provider breaker supplying
108
+ rate-awareness and an opt-in herd gap on top. Dark/dev-gated, no-op when off, wedge-safe. Ship.