instar 1.3.665 → 1.3.667

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 (48) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +64 -9
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/MessageSentinel.d.ts +62 -1
  5. package/dist/core/MessageSentinel.d.ts.map +1 -1
  6. package/dist/core/MessageSentinel.js +123 -1
  7. package/dist/core/MessageSentinel.js.map +1 -1
  8. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  9. package/dist/core/PostUpdateMigrator.js +9 -0
  10. package/dist/core/PostUpdateMigrator.js.map +1 -1
  11. package/dist/core/SessionManager.d.ts +9 -0
  12. package/dist/core/SessionManager.d.ts.map +1 -1
  13. package/dist/core/SessionManager.js +31 -0
  14. package/dist/core/SessionManager.js.map +1 -1
  15. package/dist/core/paneTail.d.ts +9 -0
  16. package/dist/core/paneTail.d.ts.map +1 -1
  17. package/dist/core/paneTail.js +12 -0
  18. package/dist/core/paneTail.js.map +1 -1
  19. package/dist/core/types.d.ts +11 -0
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/core/types.js.map +1 -1
  22. package/dist/monitoring/PermissionPromptAutoResolver.d.ts +215 -0
  23. package/dist/monitoring/PermissionPromptAutoResolver.d.ts.map +1 -0
  24. package/dist/monitoring/PermissionPromptAutoResolver.js +475 -0
  25. package/dist/monitoring/PermissionPromptAutoResolver.js.map +1 -0
  26. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  27. package/dist/monitoring/PresenceProxy.js +18 -0
  28. package/dist/monitoring/PresenceProxy.js.map +1 -1
  29. package/dist/monitoring/StuckSignatureClassifier.d.ts +1 -1
  30. package/dist/monitoring/StuckSignatureClassifier.d.ts.map +1 -1
  31. package/dist/monitoring/StuckSignatureClassifier.js +24 -0
  32. package/dist/monitoring/StuckSignatureClassifier.js.map +1 -1
  33. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  34. package/dist/monitoring/guardManifest.js +15 -0
  35. package/dist/monitoring/guardManifest.js.map +1 -1
  36. package/dist/monitoring/guardPosture.d.ts.map +1 -1
  37. package/dist/monitoring/guardPosture.js +19 -0
  38. package/dist/monitoring/guardPosture.js.map +1 -1
  39. package/dist/server/routes.d.ts.map +1 -1
  40. package/dist/server/routes.js +10 -5
  41. package/dist/server/routes.js.map +1 -1
  42. package/package.json +1 -1
  43. package/scripts/lint-guard-manifest.js +1 -0
  44. package/src/data/builtin-manifest.json +64 -64
  45. package/upgrades/1.3.666.md +131 -0
  46. package/upgrades/1.3.667.md +32 -0
  47. package/upgrades/side-effects/operator-channel-sacred.md +31 -0
  48. package/upgrades/1.3.665.md +0 -68
@@ -0,0 +1,131 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ An Instar session could be silently, permanently wedged by an agent-framework approval
9
+ prompt it cannot answer. Claude Code 2.1.176-177 added a hardcoded Bash safety prompt —
10
+ `cd <dir> && <cmd>` combined with a redirect/pipe pops "Do you want to proceed? ❯ 1. Yes
11
+ / 2. No / Esc to cancel" — that runs BEFORE all permission rules and PermissionRequest
12
+ hooks, so `--dangerously-skip-permissions` and `permissions.allow` cannot suppress it.
13
+ A remote-driven session (Telegram/dashboard) cannot press a terminal key, so it freezes
14
+ while still *looking* busy (PresenceProxy could even report "actively working"). The
15
+ empirically-confirmed reason it bit the fleet: the existing PromptGate/AutoApprover
16
+ pipeline was persisted `enabled:false` on deployed agents (a stale-`false` config wins
17
+ over a newer `true` default), so nothing answered the prompt.
18
+
19
+ - **New `src/monitoring/PermissionPromptAutoResolver.ts`** — an always-on, deterministic,
20
+ zero-LLM safety floor driven once per monitoring tick. It detects a live, focused
21
+ approval menu in a session's tmux tail and sends the approve keystroke itself.
22
+ - **Layer 2 (auto-answer):** requires ≥2 distinct registered prose patterns AND a live
23
+ glyph-led `❯`-cursor-on-an-approve-option line at the genuine bottom of the tail
24
+ while not generating; sends **`Enter`** only (confirm the highlighted "Yes"), with a
25
+ re-capture-before-send check. Because a text capture cannot *perfectly* distinguish a
26
+ real TUI selector from displayed content containing the same characters, this is
27
+ bar-raising mitigation (stated honestly), bounded by Enter-only (a false match is a
28
+ benign empty submit), a per-episode `MAX_ATTEMPTS=3` (reset-on-clear so distinct
29
+ prompts are independent), and a terminal defect.
30
+ - **Layer 3 (broad detector):** a prose-agnostic `detectPersistingMenu` surfaces ANY
31
+ persisting glyph-led menu the auto-answer declines (cursor-not-on-approve, or a future
32
+ prompt whose wording drifts) as a single deduped Attention defect — so nothing is ever
33
+ silently stranded, and it doubles as the prompt-string drift detector.
34
+ - **`src/core/paneTail.ts`** gains a pure `leadGlyphsOf(line)` helper so the detector can
35
+ test for the `❯` selector glyph (U+276F) specifically.
36
+ - **`StuckSignatureClassifier`** gains an `approval-prompt-waiting` kind; **`PresenceProxy`**
37
+ never surfaces it as a user message (the sole user-facing surface is the terminal
38
+ defect), killing the false "actively working" report for a prompt-wedged session.
39
+ - **The floor is unconditional — no persisted `enabled:true` to rot to `false`** (the
40
+ exact trap that caused the bug). The only opt-out is `monitoring.permissionPromptAutoResolver.emergencyDisable`
41
+ (absent ⇒ on). A computed guard-posture key (`guardPosture.ts`) + a runtime
42
+ `guardStatus()` + a `GUARD_MANIFEST` entry make a disabled floor a visible incident in
43
+ `GET /guards`.
44
+ - Existing wedged sessions recover automatically: the server restart that deploys the
45
+ update restarts the monitor loop, which auto-clears any session currently parked on the
46
+ prompt. No new setting, no manual step.
47
+
48
+ The resolver answers a UI-flow prompt only; it never widens what commands are allowed
49
+ (the destructive `dangerous-command-guard` denylist remains an independent guard). The
50
+ auto-approve is accepted under the operator full-machine-access trust model. Converged
51
+ through 6 spec-review rounds (6 internal reviewers + codex/gemini cross-model).
52
+
53
+ CMT-1785: the idle-error detector that decides "did this paused session stall on a transient API
54
+ error (nudge it) or stop normally (leave it)?" is migrated from a brittle bare-substring scan to a
55
+ tail-gated, frame-discriminated SIGNAL.
56
+
57
+ - New pure modules `src/core/paneTail.ts` (shared `liveTail`/`stripLineLead`/`wasGlyphLed` — ONE
58
+ definition of "the live tail", extracted so it is no longer copied per consumer) and
59
+ `src/core/IdleErrorClassifier.ts` (`classifyIdleError`).
60
+ - The old gate `TERMINAL_ERROR_PATTERNS.some(p => recentOutput.includes(p))` (a bare `.includes()`
61
+ over the whole capture) is replaced by `classifyIdleError`, which fires only when a terminal-error
62
+ token sits in the live tail (last 20 non-empty lines) on a Claude-EMITTED error frame — a two-tier
63
+ begins-with rule: **Tier A** the line begins with `API Error:`; **Tier B** the line is glyph-led
64
+ (`⏺`/`⎿`/…) and begins with one of the 11 patterns. The match set is a strict subset of the old
65
+ buffer-wide match, so the change can only SUPPRESS spurious fires, never add one.
66
+ - The idle-error capture is widened 30→45 rows (`RATE_LIMIT_SETTLED_CAPTURE_LINES`): Claude Code's
67
+ input box + footer render 15-25 rows BELOW the error, so a 30-row capture could miss it entirely.
68
+ - `StuckSignatureClassifier` is migrated onto the shared `liveTail` (behavior-preserving via
69
+ `.join('\n')`; its existing test corpus is the characterization guard).
70
+ - A once-per-idle-episode structured record (`{event:'idle-error-classify', result:'fired'|'suppressed', …}`)
71
+ lands in `logs/server.log` so a wave of suppressions on genuine errors (the under-fire risk) is
72
+ observable. Cleared in both the re-arm block and `sessionComplete` (no leak on the death path).
73
+
74
+ The signal still feeds the existing `apiErrorAtIdle` → `rateLimitSentinel` recovery actuator and gains
75
+ no blocking authority (Signal vs Authority).
76
+
77
+ ## What to Tell Your User
78
+
79
+ Your agent will no longer get silently stuck on a pop-up it can't answer. A recent
80
+ update to the coding tool added a safety pop-up ("Do you want to proceed? Yes / No") that
81
+ freezes the session, because nobody can press a key on it from your phone — and there's no
82
+ setting to turn it off. Now a small always-on layer answers that pop-up itself, only ever
83
+ pressing the harmless "Yes", and it's built into the code with no on/off switch that could
84
+ get stuck "off" (a stuck-off setting is exactly what caused the freeze). If it ever hits a
85
+ pop-up it genuinely can't clear (say a future version changes the wording), it stops and
86
+ sends you one "please take a look" notice instead of freezing silently. Nothing for you to
87
+ do; your agent just stays reachable.
88
+
89
+ Nothing you need to do. Under the hood, the agent got more reliable at telling a session that genuinely
90
+ stalled on an API error (which it nudges back to life) apart from one that's merely *showing* an old or
91
+ quoted error on screen. The old check was a blunt text search that sometimes kicked off a needless
92
+ recovery on a healthy session, and — more importantly — sometimes read too little of the screen and
93
+ missed a real stall. The new check looks at the live bottom of the screen, reads enough of it to clear
94
+ the input box, and only counts a line the tool actually emitted as an error. Net: fewer false recoveries
95
+ AND fewer missed real ones, so paused sessions get the right treatment faster. No new setting; behavior
96
+ is otherwise unchanged, and it's a one-line revert if ever needed.
97
+
98
+ ## Summary of New Capabilities
99
+
100
+ - An always-on safety floor that auto-answers an un-answerable framework approval prompt,
101
+ so a remote-driven session is never silently wedged.
102
+ - Honest, bounded detection (live glyph-led menu, Enter-only, re-capture, attempt cap) +
103
+ a prose-agnostic backstop so a drifted/unrecognized prompt surfaces rather than strands.
104
+ - Visible in `GET /guards` (`on-confirmed`); an audit trail at
105
+ `logs/permission-prompt-resolver.jsonl` (matched-pattern names only, never raw tail);
106
+ emergency opt-out via `monitoring.permissionPromptAutoResolver.emergencyDisable`.
107
+
108
+ - `paneTail` + `IdleErrorClassifier` — a precise, tail-gated, frame-discriminated idle-error signal
109
+ replacing the bare buffer-wide substring scan; widened capture (45 rows) so the input-box chrome can
110
+ no longer hide a real error; structured once-per-episode observability of fired-vs-suppressed
111
+ decisions. Internal robustness improvement to session-stall recovery; no user-facing surface to
112
+ configure.
113
+
114
+ ## Evidence
115
+
116
+ - `tests/unit/IdleErrorClassifier.test.ts` (28) — both sides of every boundary, pinned to REAL captured
117
+ render fixtures: fires on `⏺ API Error:` / ` ⎿ API Error:` / glyph-led network tokens / wrapped
118
+ errors; suppresses stale-scrollback, prose mentions, quoted source literals (the self-collision case),
119
+ a tool's own `Error: …ECONNREFUSED`; the 45-vs-30 input-box-chrome capture test; parametrized over all
120
+ 11 patterns; bounded audit fields; paneTail helpers.
121
+ - `tests/integration/idle-error-classifier-production-wiring.test.ts` (7) — the REAL exported
122
+ `TERMINAL_ERROR_PATTERNS` produce correct decisions on real panes; the SessionManager call-site is
123
+ wired to the classifier, the 45-row capture, the dual once-per-episode clears, and the structured record.
124
+ - `tests/e2e/idle-error-classifier-lifecycle.test.ts` (2) — the real SessionManager constructs and the
125
+ `apiErrorAtIdle` recovery handoff is attachable; the production-pattern live decision is correct.
126
+ - `tests/unit/monitoring/StuckSignatureClassifier.test.ts` (13) — unchanged, the characterization guard
127
+ proving the shared-`liveTail` migration is behavior-preserving.
128
+ - `npm run build` + `tsc --noEmit` clean; full unit suite green.
129
+ - Driven by the converged + approved spec; convergence report at
130
+ `docs/specs/reports/idle-error-tailgate-corroboration-convergence.md` (4 rounds; the review caught a
131
+ CRITICAL under-fire-on-the-common-error-form flaw before any code).
@@ -0,0 +1,32 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Fixed the "session paused" lockout (topic 28130): `MessageSentinel` was consuming the operator's benign inbound messages whenever its LLM classifier returned — or capacity-shed to — `'pause'`, and the "send a message to resume" recovery routed back through the same failing gate, an inescapable loop. New constitutional standard **"The Operator Channel Is Sacred — Critical-Path Gates Fail Toward Delivery"** (in `STANDARDS-REGISTRY.md`) + its exemplar fix:
9
+
10
+ - `'pause'` now consumes a message ONLY on a DETERMINISTIC fast-path match; a bare-LLM or capacity-shed `'pause'` routes THROUGH (delivered to the agent), never consumed.
11
+ - Before routing a non-deterministic result through, a non-word-count-gated stop-token scan rescues a long-form genuine "stop" to a kill (so a real stop is never dropped).
12
+ - A per-topic circuit-breaker bounds blast radius (a misclassification stream can't permanently lock the operator out).
13
+ - Both inbound consume paths (`onSentinelIntercept` + `/internal/telegram-forward`) decide via one shared `MessageSentinel.decideInboundDisposition`, so policy can't diverge.
14
+ - `/sentinel/stats` now exposes disposition counters (consumed / routed-through / breaker-recovered).
15
+
16
+ ## What to Tell Your User
17
+
18
+ If your agent ever appeared "paused" and ignored your messages — replying "Session paused. Send a message to resume." in a loop — that's fixed. A safety check could mislabel an ordinary message as a "pause" command and silently eat it (worse, under load it defaulted to pausing). Now your messages are delivered unless you *unambiguously* typed a pause/stop command, and a genuine "stop" is always honored. The channel to your agent can no longer be sealed by a wrong guess.
19
+
20
+ ## Summary of New Capabilities
21
+
22
+ - Standard "The Operator Channel Is Sacred" — a gate on the operator's inbound channel must fail toward DELIVERY; a brittle signal may not consume/block a message; recovery must not route through the failing gate; decision-gates have bounded blast radius.
23
+ - `MessageSentinel.decideInboundDisposition` + `hasStopToken` + disposition observability counters.
24
+
25
+ ## Evidence
26
+
27
+ - `tests/unit/MessageSentinel-operator-channel-sacred.test.ts` (13) — deterministic pause consumes; benign LLM-pause + capacity-shed route through; long-form stop rescued to kill; circuit-breaker auto-recovers; per-topic isolation; counters; hasStopToken both-sides.
28
+ - `tests/integration/operator-channel-sacred-wiring.test.ts` (3) — both consume sites use the shared disposition helper; old consume-on-any-pause gone.
29
+ - `tests/integration/telegram-forward-sentinel-intercept.test.ts` — updated to the disposition contract incl. a new route-through-delivers test.
30
+ - `tests/e2e/operator-channel-lockout-regression.test.ts` (4) — no benign stream (even with the LLM mislabeling every message, or sustained capacity-shed) can lock the channel; recovery is escapable; a genuine stop still honored.
31
+ - `npm run build` + `tsc --noEmit` clean; consume-path + MessageSentinel suites green.
32
+ - Converged + approved spec; convergence report at docs/specs/reports/operator-channel-sacred-convergence.md.
@@ -0,0 +1,31 @@
1
+ # Side-Effects Review — Operator Channel Is Sacred (MessageSentinel pause-lockout fix)
2
+
3
+ Spec: docs/specs/operator-channel-sacred.md (converged + approved). Standard integrated into docs/STANDARDS-REGISTRY.md.
4
+ Change: MessageSentinel `'pause'` consumes a message ONLY on a deterministic match; a bare-LLM or capacity-shed `'pause'` routes THROUGH (with a stop-token rescue); a per-topic circuit-breaker bounds blast radius; both inbound consume paths (server.ts onSentinelIntercept + routes.ts /internal/telegram-forward) decide via one shared `decideInboundDisposition`.
5
+
6
+ ## 1. Over-block
7
+ The change can ONLY reduce consumption (more messages delivered). The one residual: `hasStopToken` is intentionally conservative (a whole-word "stop" anywhere → rescue to kill), so a benign message that the classifier already called 'pause' AND contains a whole-word stop ("non-stop") would be killed. Accepted: a kill is RECOVERABLE ("send a new message to start fresh"), unlike the pause lockout; and it only triggers on an already-pause-classified message, not normal traffic.
8
+
9
+ ## 2. Under-block
10
+ A genuine pause directive that is non-deterministic (LLM-only) is now NOT honored (routes through). Accepted per the standard + the code's own comment ("pause's value is politeness, not safety"). A genuine STOP is preserved: deterministic fast-path stop fires instantly; a long-form/capacity-shed stop is rescued by `hasStopToken`.
11
+
12
+ ## 3. Level-of-abstraction fit
13
+ The standard lives in ONE place (`MessageSentinel.decideInboundDisposition`); both consume sites call it, so policy cannot diverge between the poll path and the lifeline-forward path. Signal-vs-Authority: the classifier signals; the deterministic gate holds consume authority.
14
+
15
+ ## 4. Signal vs authority compliance
16
+ Compliant + the exemplar of the new standard. A brittle (LLM/capacity-shed) signal no longer has authority to consume the operator's message; only a deterministic match (or a rescued stop) actuates.
17
+
18
+ ## 5. Interactions
19
+ - detectRateLimited / throttle path: unchanged (separate).
20
+ - The capacity-shed return (llmClassify catch, confidence 0.4) now routes through instead of consuming — reconciled with the fork-bomb/No-Silent-Degradation fail-closed posture as a NAMED inbound exception (the deterministic emergency-stop pre-check + the stop-token rescue keep a real stop from ever being dropped).
21
+ - Emergency-stop kill path + resume-queue stop custody: unchanged for `disposition: 'kill'`.
22
+ - Circuit-breaker is in-memory shared across both paths (same MessageSentinel instance); in-memory is safe because the PRIMARY guard is deterministic-only-consume — a restart resetting the breaker cannot reintroduce the lockout. (Reasoned deviation from the spec's "durable" FD2: the build showed deterministic-only-consume makes breaker durability non-load-bearing; documented here.)
23
+
24
+ ## 6. External surfaces
25
+ The user-facing change: benign messages that were being eaten as "Session paused" now reach the agent. `getStats()` (/sentinel/stats) now also exposes `disposition` counters (pause.consumed / .routed-through / breaker.recovered) — additive. No new route, no config schema change (the existing `externalOperations.sentinel.enabled` still gates the whole sentinel).
26
+
27
+ ## 7. Multi-machine posture
28
+ Machine-local: each machine's MessageSentinel decides for its own inbound; both consume paths on a machine share its one instance + breaker. No cross-machine state. (Lifeline-owned agents hit the routes.ts path; poll-mode hits onSentinelIntercept — both on the same machine, same sentinel.)
29
+
30
+ ## 8. Rollback cost
31
+ Single-commit revert restores the prior classify-then-consume logic. No migration, no durable state. The interim safeguard (`externalOperations.sentinel.enabled:false`) can stay until this merges, then be re-enabled (this fix makes the sentinel safe to re-enable).
@@ -1,68 +0,0 @@
1
- # Upgrade Guide — vNEXT
2
-
3
- <!-- assembled-by: assemble-next-md -->
4
- <!-- bump: patch -->
5
-
6
- ## What Changed
7
-
8
- CMT-1785: the idle-error detector that decides "did this paused session stall on a transient API
9
- error (nudge it) or stop normally (leave it)?" is migrated from a brittle bare-substring scan to a
10
- tail-gated, frame-discriminated SIGNAL.
11
-
12
- - New pure modules `src/core/paneTail.ts` (shared `liveTail`/`stripLineLead`/`wasGlyphLed` — ONE
13
- definition of "the live tail", extracted so it is no longer copied per consumer) and
14
- `src/core/IdleErrorClassifier.ts` (`classifyIdleError`).
15
- - The old gate `TERMINAL_ERROR_PATTERNS.some(p => recentOutput.includes(p))` (a bare `.includes()`
16
- over the whole capture) is replaced by `classifyIdleError`, which fires only when a terminal-error
17
- token sits in the live tail (last 20 non-empty lines) on a Claude-EMITTED error frame — a two-tier
18
- begins-with rule: **Tier A** the line begins with `API Error:`; **Tier B** the line is glyph-led
19
- (`⏺`/`⎿`/…) and begins with one of the 11 patterns. The match set is a strict subset of the old
20
- buffer-wide match, so the change can only SUPPRESS spurious fires, never add one.
21
- - The idle-error capture is widened 30→45 rows (`RATE_LIMIT_SETTLED_CAPTURE_LINES`): Claude Code's
22
- input box + footer render 15-25 rows BELOW the error, so a 30-row capture could miss it entirely.
23
- - `StuckSignatureClassifier` is migrated onto the shared `liveTail` (behavior-preserving via
24
- `.join('\n')`; its existing test corpus is the characterization guard).
25
- - A once-per-idle-episode structured record (`{event:'idle-error-classify', result:'fired'|'suppressed', …}`)
26
- lands in `logs/server.log` so a wave of suppressions on genuine errors (the under-fire risk) is
27
- observable. Cleared in both the re-arm block and `sessionComplete` (no leak on the death path).
28
-
29
- The signal still feeds the existing `apiErrorAtIdle` → `rateLimitSentinel` recovery actuator and gains
30
- no blocking authority (Signal vs Authority).
31
-
32
- ## What to Tell Your User
33
-
34
- Nothing you need to do. Under the hood, the agent got more reliable at telling a session that genuinely
35
- stalled on an API error (which it nudges back to life) apart from one that's merely *showing* an old or
36
- quoted error on screen. The old check was a blunt text search that sometimes kicked off a needless
37
- recovery on a healthy session, and — more importantly — sometimes read too little of the screen and
38
- missed a real stall. The new check looks at the live bottom of the screen, reads enough of it to clear
39
- the input box, and only counts a line the tool actually emitted as an error. Net: fewer false recoveries
40
- AND fewer missed real ones, so paused sessions get the right treatment faster. No new setting; behavior
41
- is otherwise unchanged, and it's a one-line revert if ever needed.
42
-
43
- ## Summary of New Capabilities
44
-
45
- - `paneTail` + `IdleErrorClassifier` — a precise, tail-gated, frame-discriminated idle-error signal
46
- replacing the bare buffer-wide substring scan; widened capture (45 rows) so the input-box chrome can
47
- no longer hide a real error; structured once-per-episode observability of fired-vs-suppressed
48
- decisions. Internal robustness improvement to session-stall recovery; no user-facing surface to
49
- configure.
50
-
51
- ## Evidence
52
-
53
- - `tests/unit/IdleErrorClassifier.test.ts` (28) — both sides of every boundary, pinned to REAL captured
54
- render fixtures: fires on `⏺ API Error:` / ` ⎿ API Error:` / glyph-led network tokens / wrapped
55
- errors; suppresses stale-scrollback, prose mentions, quoted source literals (the self-collision case),
56
- a tool's own `Error: …ECONNREFUSED`; the 45-vs-30 input-box-chrome capture test; parametrized over all
57
- 11 patterns; bounded audit fields; paneTail helpers.
58
- - `tests/integration/idle-error-classifier-production-wiring.test.ts` (7) — the REAL exported
59
- `TERMINAL_ERROR_PATTERNS` produce correct decisions on real panes; the SessionManager call-site is
60
- wired to the classifier, the 45-row capture, the dual once-per-episode clears, and the structured record.
61
- - `tests/e2e/idle-error-classifier-lifecycle.test.ts` (2) — the real SessionManager constructs and the
62
- `apiErrorAtIdle` recovery handoff is attachable; the production-pattern live decision is correct.
63
- - `tests/unit/monitoring/StuckSignatureClassifier.test.ts` (13) — unchanged, the characterization guard
64
- proving the shared-`liveTail` migration is behavior-preserving.
65
- - `npm run build` + `tsc --noEmit` clean; full unit suite green.
66
- - Driven by the converged + approved spec; convergence report at
67
- `docs/specs/reports/idle-error-tailgate-corroboration-convergence.md` (4 rounds; the review caught a
68
- CRITICAL under-fire-on-the-common-error-form flaw before any code).