instar 1.3.671 → 1.3.673

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 (40) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +76 -2
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/MessagingToneGate.d.ts +44 -0
  5. package/dist/core/MessagingToneGate.d.ts.map +1 -1
  6. package/dist/core/MessagingToneGate.js +92 -18
  7. package/dist/core/MessagingToneGate.js.map +1 -1
  8. package/dist/monitoring/EnforcedTerminationWatchdog.d.ts +91 -0
  9. package/dist/monitoring/EnforcedTerminationWatchdog.d.ts.map +1 -0
  10. package/dist/monitoring/EnforcedTerminationWatchdog.js +172 -0
  11. package/dist/monitoring/EnforcedTerminationWatchdog.js.map +1 -0
  12. package/dist/monitoring/enforcedTermination.d.ts +105 -0
  13. package/dist/monitoring/enforcedTermination.d.ts.map +1 -0
  14. package/dist/monitoring/enforcedTermination.js +105 -0
  15. package/dist/monitoring/enforcedTermination.js.map +1 -0
  16. package/dist/monitoring/enforcedTerminationWiring.d.ts +18 -0
  17. package/dist/monitoring/enforcedTerminationWiring.d.ts.map +1 -0
  18. package/dist/monitoring/enforcedTerminationWiring.js +80 -0
  19. package/dist/monitoring/enforcedTerminationWiring.js.map +1 -0
  20. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  21. package/dist/monitoring/guardManifest.js +10 -0
  22. package/dist/monitoring/guardManifest.js.map +1 -1
  23. package/dist/server/AgentServer.d.ts +3 -0
  24. package/dist/server/AgentServer.d.ts.map +1 -1
  25. package/dist/server/AgentServer.js +1 -0
  26. package/dist/server/AgentServer.js.map +1 -1
  27. package/dist/server/outboundGateBudget.d.ts +1 -1
  28. package/dist/server/outboundGateBudget.d.ts.map +1 -1
  29. package/dist/server/outboundGateBudget.js +15 -1
  30. package/dist/server/outboundGateBudget.js.map +1 -1
  31. package/dist/server/routes.d.ts +3 -0
  32. package/dist/server/routes.d.ts.map +1 -1
  33. package/dist/server/routes.js +33 -11
  34. package/dist/server/routes.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/data/builtin-manifest.json +47 -47
  37. package/upgrades/1.3.672.md +63 -0
  38. package/upgrades/1.3.673.md +50 -0
  39. package/upgrades/side-effects/enforced-termination-watchdog.md +95 -0
  40. package/upgrades/side-effects/tone-gate-graceful-degradation.md +211 -0
@@ -0,0 +1,211 @@
1
+ # Side-Effects Review — Tone-Gate Graceful Degradation (Postmortem F4)
2
+
3
+ **Version / slug:** `tone-gate-graceful-degradation`
4
+ **Date:** `2026-06-25`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `required (outbound block/allow decision + "gate") — verdict + resolution appended at the end`
7
+
8
+ ## Summary of the change
9
+
10
+ `MessagingToneGate.review()` is the outbound block/allow authority on the Telegram
11
+ reply path. Its provider-exhaustion catch branch was **fail-CLOSED by default**:
12
+ when the LLM backend was unavailable (breaker-open / rate-limit / transport error),
13
+ it HELD every message unconditionally. Tonight that turned a `claude -p` rate-limit
14
+ into a multi-hour silent outbound outage — the user saw delivery receipts but no
15
+ replies. This change makes the outage path **degrade to an in-process deterministic
16
+ leak floor** (`detectGateSignals` B1–B7 + `detectInternalIdLeak`, both pre-existing
17
+ pure detectors): a clean message SENDS, a leaked artifact still HOLDS.
18
+
19
+ The same rate-limit outage has **two manifestations**, and this change covers BOTH:
20
+ the FAST throw (breaker open → `review()` rejects) degrades inside `review()`; the
21
+ SLOW stall (the gate waits up to 120s for a rate-limit window and overruns the
22
+ outbound route budget — the DOCUMENTED 2026-06-08 production failure) degrades at
23
+ the route seam in `reviewWithinBudget`, via the shared `buildDegradedToneResult`.
24
+ The capacity-shed (fork-bomb P3) path and the discipline-failure path are UNCHANGED.
25
+
26
+ Files: `src/core/MessagingToneGate.ts` (the shared `buildDegradedToneResult` +
27
+ `degradedToDeterministic` result flag + `DegradeReason`), `src/server/outboundGateBudget.ts`
28
+ (budget-timeout degrade callback), `src/server/routes.ts` (three-valued route disposition),
29
+ `tests/unit/MessagingToneGate.test.ts`, `tests/unit/spawn-cap-fail-closed-gates.test.ts`,
30
+ `tests/unit/outbound-gate-budget.test.ts`, plus the spec + ELI16 + this artifact.
31
+
32
+ ## Decision-point inventory
33
+
34
+ - `MessagingToneGate.review()` provider-exhaustion catch branch — **modify** — was pure-hold; now degrades to the deterministic floor by default (clean sends, leak holds).
35
+ - `reviewWithinBudget` budget-timeout disposition (route seam) — **modify** — the slow-stall timeout was pure-hold (`GATE_TIMEOUT`) under the default; now degrades to the SAME floor by default (the slow sibling of the fast throw). Pure-hold/fail-open overrides still reachable.
36
+ - `MessagingToneGate.review()` capacity-shed branch (`isCapacityUnavailable`) — **pass-through** — still pure-hold (fork-bomb P3 invariant preserved).
37
+ - `MessagingToneGate.review()` discipline-failure branch (unparseable/contradictory after one re-prompt) — **pass-through** — still pure-hold (a model that produced an unusable verdict is not an infra outage).
38
+ - `messaging.toneGate.failClosedOnExhaustion` config flag — **modify** — was 2-valued (default-true hold / false open); now 3-valued (unset = degrade, true = pure-hold, false = open) on BOTH the throw and the budget-timeout paths.
39
+
40
+ ---
41
+
42
+ ## 1. Over-block
43
+
44
+ **What legitimate inputs does this change reject that it shouldn't?**
45
+
46
+ The change strictly REDUCES over-block on the provider-exhaustion path: messages
47
+ that were previously held during a backend outage now send if they carry no
48
+ deterministic leak. The only inputs still held on the degraded path are those
49
+ carrying a real B1–B7 artifact (a literal CLI command, file path, config key,
50
+ code block, API endpoint, env var, cron/slug) or a B20 internal-id leak — the
51
+ exact high-stakes content that must never escape. A message that legitimately
52
+ *discusses* such an artifact in prose (e.g. "I'll run the migration for you")
53
+ does NOT trip the detectors (verified by the GateSignalDetectors negative tests)
54
+ and sends normally.
55
+
56
+ ---
57
+
58
+ ## 2. Under-block
59
+
60
+ **What failure modes does this still miss?**
61
+
62
+ On the degraded path the behavioral rules (B11–B20 tone/self-stop/false-blocker/
63
+ parked-on-user/jargon) are NOT evaluated — they require LLM judgment that is, by
64
+ definition, unavailable during the outage that triggers this path. So during a
65
+ backend outage a slightly-off-tone or self-stop-shaped message could reach the
66
+ user unchecked. This is a deliberate, bounded trade: a leak (the dangerous class)
67
+ is still caught deterministically; a tone slip (the recoverable class) reaching
68
+ the user beats silence (the F4 goal). When the backend recovers, full LLM review
69
+ resumes automatically. Operators who want the strict behavior restore pure-hold
70
+ with `failClosedOnExhaustion: true`.
71
+
72
+ ---
73
+
74
+ ## 3. Level-of-abstraction fit
75
+
76
+ The degrade floor is the right layer: it REUSES the existing low-level
77
+ deterministic detectors (`detectGateSignals`, `detectInternalIdLeak`) that the
78
+ gate already calls to build its LLM prompt — it does not re-implement detection.
79
+ The authority (send/hold) stays where it belongs, inside `review()`. No higher
80
+ gate exists that should own this; `review()` IS the outbound authority. The
81
+ change feeds an existing primitive into an existing authority's fallback path —
82
+ no new abstraction introduced.
83
+
84
+ ---
85
+
86
+ ## 4. Signal vs authority compliance
87
+
88
+ **Required reference:** `docs/signal-vs-authority.md`
89
+
90
+ - [x] No — this change reuses EXISTING signal-producers (`detectGateSignals`,
91
+ `detectInternalIdLeak`) feeding the EXISTING smart authority (`review()`). It
92
+ adds no new brittle blocking check.
93
+
94
+ The deterministic detectors are pure signal-producers; the gate is the authority.
95
+ On the degraded path the authority consults those signals deterministically
96
+ (LLM unavailable) and fails CLOSED on any positive signal. This is the
97
+ signal-vs-authority pattern applied exactly as intended — the brittle detectors
98
+ never gained new authority; they inform a fallback disposition that defaults to
99
+ holding on any leak signal.
100
+
101
+ ---
102
+
103
+ ## 5. Interactions
104
+
105
+ - **Shadowing:** the degrade branch runs ONLY in the provider-exhaustion catch,
106
+ after the LLM call throws. It cannot shadow the normal-verdict path (that path
107
+ `return`s before the catch) or the capacity-shed branch (which `return`s first
108
+ inside the catch). Ordering verified: capacity-shed check precedes the
109
+ provider-error disposition.
110
+ - **Double-fire:** none — a single `review()` call returns exactly one
111
+ disposition.
112
+ - **Races:** none — `review()` is a pure per-call function over its `text` +
113
+ live-read config; it shares no mutable state with concurrent calls.
114
+ - **Feedback loops:** none — the floor reads the candidate text only; it does not
115
+ feed any system that feeds back into the gate.
116
+ - The downstream retry path (`failClosedOnExhaustion` hold → queued for retry) is
117
+ unchanged for the held cases; a now-SENT clean message simply skips that queue.
118
+
119
+ ---
120
+
121
+ ## 6. External surfaces
122
+
123
+ - **Other agents / users:** none directly. The install-base effect is the intended
124
+ one: during a backend outage, outbound replies degrade-and-send instead of
125
+ silently holding.
126
+ - **External systems (Telegram):** a clean message now reaches Telegram during an
127
+ outage that previously held it. The held-leak case is byte-identical to before
128
+ (still `pass:false`, queued for retry).
129
+ - **Persistent state:** none — no new state written.
130
+ - **Operator surface:** no NEW operator surface. The change extends the value
131
+ semantics of the existing `messaging.toneGate.failClosedOnExhaustion` config
132
+ flag (a config edit, already documented) — it adds the `unset = degrade`
133
+ default and keeps `true`/`false` working. No new PIN-gated route or dashboard
134
+ form.
135
+ - **Result shape:** adds an OPTIONAL `degradedToDeterministic?: boolean` to
136
+ `ToneReviewResult`. Existing consumers that don't read it are unaffected (the
137
+ `pass`/`rule`/`issue`/`failedClosed` contract is preserved).
138
+
139
+ ---
140
+
141
+ ## 6b. Operator-surface quality
142
+
143
+ No operator surface — not applicable. The change touches no dashboard renderer,
144
+ approval page, or grant/revoke/secret-drop form. The only operator-facing element
145
+ is the pre-existing `failClosedOnExhaustion` config flag (a JSON edit), whose
146
+ default and overrides are documented in the spec.
147
+
148
+ ---
149
+
150
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
151
+
152
+ **machine-local BY DESIGN.** The tone gate is a pure, stateless, per-message
153
+ function evaluated on whichever machine is serving that outbound reply. It holds
154
+ no durable state, emits no cross-machine notice, and generates no URL. Given the
155
+ same candidate text + the same `failClosedOnExhaustion` config, the disposition
156
+ is identical on every machine — so there is nothing to replicate or proxy. It
157
+ does not strand on topic transfer (no state), needs no one-voice gating (it does
158
+ not itself emit a user notice; it gates the agent's own reply, which is already
159
+ single-voiced per topic), and creates no machine-bound link.
160
+
161
+ ---
162
+
163
+ ## 8. Rollback cost
164
+
165
+ Cheap and instant, two independent levers:
166
+ 1. **Config (no deploy):** set `messaging.toneGate.failClosedOnExhaustion: true`
167
+ to restore the exact legacy pure-hold behavior on the provider-error path.
168
+ Read live per review — no restart.
169
+ 2. **Code:** revert the single commit. The change is isolated to one method's
170
+ catch branch + one shared helper + the route seam + one optional result field;
171
+ no migration, no state, no schema. No data repair or agent-state repair needed.
172
+
173
+ ---
174
+
175
+ ## Phase 5 — Second-pass review (independent reviewer)
176
+
177
+ An independent reviewer subagent audited the change (code diff, `GateSignalDetectors`,
178
+ `internal-id-leak`, the artifact, both test files, and ran the suites).
179
+
180
+ **Verdict:** Concur with the core change — leak-safe, control-flow-correct, tests
181
+ honest — with ONE substantive concern.
182
+
183
+ - **Leak safety — PASS.** The degraded floor (B1–B7 + B20) is exactly the tone
184
+ gate's high-stakes artifact-leak class; everything else is behavioral/tone
185
+ (recoverable). Secrets/API-keys are redacted separately on BOTH paths (not a
186
+ regression). The floor is strictly *stricter* than the LLM on the leak class
187
+ (holds on any positive detection), so degrade can only over-hold, never
188
+ under-send a leak.
189
+ - **Control flow — PASS.** Capacity-shed returns before the provider-error
190
+ disposition; the degrade branch cannot be reached for a discipline failure
191
+ (that path returns inside the `try`).
192
+ - **3-valued flag — PASS.** `true`→hold, `false`→open, `undefined`→degrade; a
193
+ throwing config getter yields `{}`→`undefined`→degrade (safe default).
194
+ - **Tests — PASS.** Non-vacuous inputs; the override test holds a message that
195
+ would otherwise send.
196
+
197
+ **Concern raised (resolved in this change):** the original revision fixed only the
198
+ provider-*throw* manifestation; the provider-*slow* (budget-timeout) sibling — the
199
+ DOCUMENTED 2026-06-08 production failure — still pure-held under the default, so
200
+ the F4 goal was only partially met and the artifact overstated the fix.
201
+
202
+ **Resolution:** rather than defer (no-deferrals standard), the slow path was closed
203
+ in this same change. `reviewWithinBudget` now takes a `budgetDegrade` callback; the
204
+ route passes it under the default disposition so a budget timeout degrades to the
205
+ SAME `buildDegradedToneResult` floor (clean sends, leak holds). Two new tests in
206
+ `outbound-gate-budget.test.ts` prove the slow-path clean-send and leak-hold. The
207
+ artifact summary + decision inventory above were corrected to describe both paths.
208
+ The reviewer's two minor notes (dead capacity-arm in the degrade helper; B12
209
+ health-jargon passing on degrade) were also addressed — the dead arm was removed in
210
+ the `buildDegradedToneResult` refactor, and the B12 behavioral drop is documented as
211
+ accepted in §2.