instar 1.3.106 → 1.3.108
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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +6 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SessionManager.d.ts +23 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +53 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/StuckInputSentinel.d.ts.map +1 -1
- package/dist/core/StuckInputSentinel.js +7 -6
- package/dist/core/StuckInputSentinel.js.map +1 -1
- package/dist/core/claudeActivityIndicators.d.ts +37 -0
- package/dist/core/claudeActivityIndicators.d.ts.map +1 -0
- package/dist/core/claudeActivityIndicators.js +45 -0
- package/dist/core/claudeActivityIndicators.js.map +1 -0
- package/dist/core/cpuStarvation.d.ts +30 -0
- package/dist/core/cpuStarvation.d.ts.map +1 -0
- package/dist/core/cpuStarvation.js +44 -0
- package/dist/core/cpuStarvation.js.map +1 -0
- package/dist/lifeline/ServerSupervisor.d.ts +25 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +71 -46
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/monitoring/CompactionSentinel.d.ts +39 -1
- package/dist/monitoring/CompactionSentinel.d.ts.map +1 -1
- package/dist/monitoring/CompactionSentinel.js +45 -1
- package/dist/monitoring/CompactionSentinel.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +17 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +4 -4
- package/upgrades/1.3.107.md +72 -0
- package/upgrades/1.3.108.md +94 -0
- package/upgrades/NEXT.eli16.md +60 -49
- package/upgrades/side-effects/compaction-busy-session-defer.md +94 -0
- package/upgrades/side-effects/failure-learning-sources-wiring-and-migration-parity.md +127 -0
- package/upgrades/side-effects/supervisor-cpu-starvation-restart-guard.md +83 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Side-Effects Review — Compaction recovery: defer re-inject while the session is actively working
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `compaction-busy-session-defer`
|
|
4
|
+
**Date:** `2026-05-29`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Spec:** `docs/specs/compaction-busy-session-defer.md` (+ `.eli16.md`)
|
|
7
|
+
**Convergence:** `docs/specs/reports/compaction-busy-session-defer-convergence.md` (fast-tracked — urgent fleet bug; see report)
|
|
8
|
+
**Second-pass reviewer:** `not required` (see §"Phase 5 trigger check")
|
|
9
|
+
|
|
10
|
+
## Summary of the change
|
|
11
|
+
|
|
12
|
+
Fixes the user-reported false "session is restarting" loop (Justin, topic 15160):
|
|
13
|
+
a live, actively-working session gets its inbound message buried under stacked
|
|
14
|
+
compaction-recovery re-injects, so the message never reaches the session and the
|
|
15
|
+
user sees repeated "restarting / starting up" narration.
|
|
16
|
+
|
|
17
|
+
Root cause (verified in running code + `logs/server.log` on Echo): `CompactionSentinel`
|
|
18
|
+
re-injects a recovery prompt when the session's JSONL doesn't grow within the
|
|
19
|
+
verify window — but a long extended-think on a large context (typical right after
|
|
20
|
+
resuming a near-full transcript) writes nothing to the JSONL until the turn
|
|
21
|
+
lands, so a healthy session reads as "stuck." Caught firing 3× in ~52s against a
|
|
22
|
+
live session.
|
|
23
|
+
|
|
24
|
+
The change teaches the recovery surfaces to recognize a mid-turn session (the
|
|
25
|
+
canonical `esc to interrupt` / `tokens · esc` footer OR a live non-baseline child
|
|
26
|
+
process) and DEFER instead of re-injecting, bounded by `maxWorkingDefers`.
|
|
27
|
+
|
|
28
|
+
## Files touched
|
|
29
|
+
|
|
30
|
+
- `src/core/claudeActivityIndicators.ts` (**new**) — `CLAUDE_WORKING_INDICATORS`
|
|
31
|
+
single source of truth + pure `paneShowsClaudeWorking`.
|
|
32
|
+
- `src/core/StuckInputSentinel.ts` — its `ACTIVITY_INDICATORS` now imports the
|
|
33
|
+
shared const (no behavior change; removes drift risk).
|
|
34
|
+
- `src/core/SessionManager.ts` — `paneShowsActiveWork` + `isSessionActivelyWorking`;
|
|
35
|
+
`verifyInjection` skips the recovery Enter while the pane is actively working.
|
|
36
|
+
- `src/monitoring/CompactionSentinel.ts` — `isActivelyWorking?` dep,
|
|
37
|
+
`maxWorkingDefers` config, `workingDefers` state, `deferForActiveWork()` +
|
|
38
|
+
`scheduleVerify()`, `compaction:deferred` event, `deferring` status.
|
|
39
|
+
- `src/commands/server.ts` — wires `isActivelyWorking` to
|
|
40
|
+
`sessionManager.isSessionActivelyWorking`.
|
|
41
|
+
- Tests: `tests/unit/CompactionSentinel.test.ts` (+6 cases),
|
|
42
|
+
`tests/unit/claudeActivityIndicators.test.ts` (new),
|
|
43
|
+
`tests/unit/session-active-work.test.ts` (new),
|
|
44
|
+
`tests/integration/compaction-busy-defer-wiring.test.ts` (new),
|
|
45
|
+
`tests/e2e/compaction-busy-defer-lifecycle.test.ts` (new).
|
|
46
|
+
|
|
47
|
+
## Decision-point inventory
|
|
48
|
+
|
|
49
|
+
- **Inject vs defer (CompactionSentinel)** — *modify, additive*. Was "no jsonl
|
|
50
|
+
growth → re-inject/fail." Now: "no growth AND actively working AND defer budget
|
|
51
|
+
remains → wait without injecting; else unchanged." A presence check, not a
|
|
52
|
+
judgment. Both branches test-covered. With the dep absent or
|
|
53
|
+
`maxWorkingDefers: 0`, behavior is byte-for-byte the old path.
|
|
54
|
+
- **Recovery-Enter vs skip (verifyInjection)** — *modify, additive*. Was "marker
|
|
55
|
+
at prompt → fire Enter." Now: "marker at prompt AND actively working → skip
|
|
56
|
+
this tick, keep polling." Skipping is strictly less action; the input is
|
|
57
|
+
correctly queued by Claude Code and submits when the turn ends.
|
|
58
|
+
- **Working signal** — *read-only*. Reuses the existing footer indicators +
|
|
59
|
+
`hasActiveProcesses`. No new file format, no write, no new tmux command.
|
|
60
|
+
`isSessionActivelyWorking` is wrapped so a capture/ps failure resolves to
|
|
61
|
+
`false` (cannot throw into recovery).
|
|
62
|
+
|
|
63
|
+
## Blast radius
|
|
64
|
+
|
|
65
|
+
- **No new authority, no new gate, no new detector, no new API route, no
|
|
66
|
+
external surface.** A defer behind an existing, already-rate-guarded recovery
|
|
67
|
+
primitive.
|
|
68
|
+
- **Hot paths unchanged.** `getTopicForSession` and the message-inject hot path
|
|
69
|
+
are untouched. `isSessionActivelyWorking` runs only inside the recovery
|
|
70
|
+
lifecycle (already off the hot path).
|
|
71
|
+
- **Strictly less aggressive.** The change can only PREVENT a re-inject/Enter; it
|
|
72
|
+
never adds one. It cannot starve a real recovery: an idle or wedged session
|
|
73
|
+
shows no working tell → no defer. Bounded by `maxWorkingDefers`.
|
|
74
|
+
- **No agent-installed files changed** — no `.claude/settings.json` hooks, no
|
|
75
|
+
`.instar/config.json` defaults, no CLAUDE.md template, no hook scripts, no
|
|
76
|
+
skills. So **no PostUpdateMigrator entry is required**: pure `src/` logic that
|
|
77
|
+
reaches every agent through the normal dist update.
|
|
78
|
+
|
|
79
|
+
## Phase 5 trigger check (second-pass reviewer)
|
|
80
|
+
|
|
81
|
+
Second pass **not required**: no new authority, no destructive operation
|
|
82
|
+
introduced (the only behavioral deltas REMOVE actions — a re-inject and a
|
|
83
|
+
recovery Enter — against working sessions), no external surface, no migration.
|
|
84
|
+
Additive, read-only signal behind an existing rate-guarded recovery primitive,
|
|
85
|
+
with full three-tier coverage.
|
|
86
|
+
|
|
87
|
+
## Verification
|
|
88
|
+
|
|
89
|
+
- Unit: `CompactionSentinel.test.ts`, `claudeActivityIndicators.test.ts`,
|
|
90
|
+
`session-active-work.test.ts` (+ existing `StuckInputSentinel.test.ts` still
|
|
91
|
+
green after the shared-const refactor) — 125 pass across the related set.
|
|
92
|
+
- Integration: `compaction-busy-defer-wiring.test.ts` — 4 pass.
|
|
93
|
+
- E2E: `compaction-busy-defer-lifecycle.test.ts` — 4 pass (incl. WIRED guard).
|
|
94
|
+
- `tsc --noEmit` clean.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Side-effects review — Failure-Learning sources wiring + migration-parity assertion
|
|
2
|
+
|
|
3
|
+
## What changed
|
|
4
|
+
|
|
5
|
+
Two structural backstops shipped together, both targeting recurring bug classes
|
|
6
|
+
named in the 2026-05-29 pipeline post-mortem.
|
|
7
|
+
|
|
8
|
+
1. **Failure-Learning Loop: unimplemented-source warning + wiring-integrity test.**
|
|
9
|
+
`monitoring.failureLearning.sources.regression` and
|
|
10
|
+
`monitoring.failureLearning.sources.degradation` are config flags shipped
|
|
11
|
+
without implementations: setting them on did absolutely nothing. The flag
|
|
12
|
+
created a false sense of coverage — exactly the "specced but not wired"
|
|
13
|
+
class that produced PR #530 (AttributionResolver Phase 2 specced, never
|
|
14
|
+
wired, every burn alarm read 100% pre-attribution). A new boot-time
|
|
15
|
+
`console.warn` names the unimplemented sources, says they are silent
|
|
16
|
+
no-ops, and tells the operator to set them back off until impl ships.
|
|
17
|
+
A new unit-tier test asserts the CiFailurePoller and RevertDetector are
|
|
18
|
+
constructed iff their flags are on, and that the unimplemented-source
|
|
19
|
+
warning fires on the right configurations and stays quiet on the right
|
|
20
|
+
ones — so a future regression in the gating logic cannot silently
|
|
21
|
+
disable the loop.
|
|
22
|
+
|
|
23
|
+
2. **Migration parity: a static assertion that fresh-init hook installs match
|
|
24
|
+
auto-update hook installs.** Per the "Migration parity skip" class
|
|
25
|
+
(telegram-reply.sh 403, autonomous-stop-hook broken path, the
|
|
26
|
+
slack-channel-context.sh divergence we found mid-PR in #542). The test
|
|
27
|
+
reads `installHooks()` (src/commands/init.ts) and `migrateHooks()`
|
|
28
|
+
(src/core/PostUpdateMigrator.ts), extracts the set of hook files each
|
|
29
|
+
writes, and asserts neither side has a file the other doesn't —
|
|
30
|
+
except for a small documented allowlist of migrator-only deferred-install
|
|
31
|
+
files. The allowlist's soft cap (10) trips a regression alarm if the
|
|
32
|
+
gap widens.
|
|
33
|
+
|
|
34
|
+
## Why
|
|
35
|
+
|
|
36
|
+
The May 2026 fix-rate to main was ~19% (150 of 781 commits over 14 days). The
|
|
37
|
+
Failure-Learning Loop had captured exactly **one** event total — a manual
|
|
38
|
+
agent-diagnosed entry I posted on the morning of the post-mortem itself. The
|
|
39
|
+
loop's substrate (CiFailurePoller, RevertDetector, FailureLedger,
|
|
40
|
+
FailureAttributionEngine, /failures routes) was already shipped (slice 1 in
|
|
41
|
+
late April, slice 1b around #484), but every ingestion source was switched OFF
|
|
42
|
+
in `ConfigDefaults.ts`. So the meta-trace lever was unpulled.
|
|
43
|
+
|
|
44
|
+
I cannot flip a config default to a fleet-wide ON in a single PR — per-agent
|
|
45
|
+
opt-in is the model the spec sets. What I CAN ship is the missing structural
|
|
46
|
+
backstop: unimplemented sources must surface their gap visibly, and the
|
|
47
|
+
wiring-integrity test makes any future regression in the gating impossible to
|
|
48
|
+
miss. Plus the migration-parity test catches THE specific class that produced
|
|
49
|
+
the silent failure that initiated this whole conversation (a fresh-init agent
|
|
50
|
+
missing a hook the migrator installs, including telegram-topic-context.sh —
|
|
51
|
+
the exact file behind the 2026-05-29 incoherent-reply incident).
|
|
52
|
+
|
|
53
|
+
Echo's local config is being flipped to `sources.ci: true, sources.revert: true`
|
|
54
|
+
out-of-band as a dogfooding step; that's an agent-local change, not part of
|
|
55
|
+
this PR.
|
|
56
|
+
|
|
57
|
+
## Risk surface
|
|
58
|
+
|
|
59
|
+
- **Boot-time `console.warn` for unimplemented sources** — only fires when the
|
|
60
|
+
agent has explicitly turned on `sources.regression: true` or
|
|
61
|
+
`sources.degradation: ["..."]`. Default-off agents see nothing. The warning
|
|
62
|
+
text is plain-English and tells the operator the explicit fix (set them
|
|
63
|
+
back off). No state mutation; no API surface change.
|
|
64
|
+
- **Wiring-integrity test** — pure unit test, uses an in-process mock
|
|
65
|
+
`SessionManager`. Doesn't construct an HTTP server, doesn't touch the
|
|
66
|
+
network, doesn't read git. Risk: zero.
|
|
67
|
+
- **Migration-parity test** — static-analysis-only test. Parses
|
|
68
|
+
`src/commands/init.ts` and `src/core/PostUpdateMigrator.ts` source files
|
|
69
|
+
with a small regex (`fs.writeFileSync(path.join(hooksDir|instarHooksDir,
|
|
70
|
+
'<filename>'), …)`) and compares the resulting sets. Doesn't run either
|
|
71
|
+
function. Risk: zero.
|
|
72
|
+
- **Allowlist semantics** — the test treats the allowlist as deliberate
|
|
73
|
+
accepted technical debt; growing it requires explicit code change + a
|
|
74
|
+
documented rationale per entry. The soft cap (10) protects against silent
|
|
75
|
+
drift.
|
|
76
|
+
|
|
77
|
+
## Bug surfaces eliminated
|
|
78
|
+
|
|
79
|
+
- `sources.regression: true` previously silently no-op'd → now warns loudly.
|
|
80
|
+
- `sources.degradation: ["x"]` previously silently no-op'd → now warns loudly.
|
|
81
|
+
- A future PR that adds a `migrateHooks()` write without an `installHooks()`
|
|
82
|
+
counterpart → caught at commit time by the parity test.
|
|
83
|
+
- A future PR that adds an `installHooks()` write without a `migrateHooks()`
|
|
84
|
+
counterpart → caught at commit time by the parity test (this is the more
|
|
85
|
+
dangerous direction — the migrator overwrites on every cycle; a fresh-init
|
|
86
|
+
file the migrator doesn't maintain drifts forever).
|
|
87
|
+
- A future regression that disables the CiFailurePoller construction when
|
|
88
|
+
`sources.ci: true` → caught by wiring-integrity test.
|
|
89
|
+
|
|
90
|
+
## Migration footprint
|
|
91
|
+
|
|
92
|
+
- No fleet migration required. The boot-warning is purely an in-process side
|
|
93
|
+
effect at AgentServer construction time; existing agents pick it up on the
|
|
94
|
+
next process restart (which auto-update will trigger anyway).
|
|
95
|
+
- Default config defaults are unchanged. Per-agent opt-in remains the
|
|
96
|
+
intended model for the sources.
|
|
97
|
+
|
|
98
|
+
## Testing
|
|
99
|
+
|
|
100
|
+
- Unit (wiring): `tests/unit/failure-learning-sources-wiring.test.ts` —
|
|
101
|
+
9 tests. Constructed iff flag set (both directions), enabled:false
|
|
102
|
+
inert across all source flags, unimplemented sources trigger one
|
|
103
|
+
consolidated warning, implemented-only configs stay quiet.
|
|
104
|
+
- Unit (migration parity): `tests/unit/migration-parity-hooks.test.ts` —
|
|
105
|
+
5 tests. Non-empty both sides, no install-only, no migrator-only outside
|
|
106
|
+
allowlist, allowlist soft cap. Positive + destructive-negative verified
|
|
107
|
+
(fake install-only write fails; fake migrator-only-without-allowlist
|
|
108
|
+
fails).
|
|
109
|
+
|
|
110
|
+
## Test count
|
|
111
|
+
|
|
112
|
+
14 new tests across the unit tier. Existing related tests
|
|
113
|
+
(`CiFailurePoller.test.ts: 12`, `RevertDetector.test.ts: 9`,
|
|
114
|
+
`PostUpdateMigrator-time-injection.test.ts: 13`) remain green.
|
|
115
|
+
|
|
116
|
+
## Follow-ups (NOT in this PR — surfaced explicitly so the allowlist doesn't
|
|
117
|
+
hide them indefinitely):
|
|
118
|
+
|
|
119
|
+
- The `INSTALL_VS_MIGRATE_KNOWN_GAPS` allowlist enumerates 6 currently-accepted
|
|
120
|
+
migrator-only hooks. Each entry is technical debt that should be closed by
|
|
121
|
+
moving the file into `installHooks()` (or removing it from the migrator if
|
|
122
|
+
it's no longer needed). Tracked in the allowlist comments so the soft cap
|
|
123
|
+
surfaces drift.
|
|
124
|
+
- The `regression` and `degradation` source IMPLEMENTATIONS still need to
|
|
125
|
+
ship. Those are separate features (the post-mortem's lever B —
|
|
126
|
+
real-world-state fixture tests — closely related). This PR's warning
|
|
127
|
+
closes the misleading-flag gap until then.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Side-Effects Review — Supervisor CPU-starvation restart guard
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `supervisor-cpu-starvation-restart-guard`
|
|
4
|
+
**Date:** `2026-05-29`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Spec:** `docs/specs/supervisor-cpu-starvation-restart-guard.md` (+ `.eli16.md`)
|
|
7
|
+
**Convergence:** `docs/specs/reports/supervisor-cpu-starvation-restart-guard-convergence.md` (fast-tracked — urgent fleet bug; see report)
|
|
8
|
+
**Second-pass reviewer:** `not required` (see §"Phase 5 trigger check")
|
|
9
|
+
|
|
10
|
+
## Summary of the change
|
|
11
|
+
|
|
12
|
+
Fixes the overload-driven server restart loop that drops user messages (Justin,
|
|
13
|
+
topic 15160: "Session restarting, message never lands"). Under CPU starvation
|
|
14
|
+
the live server can't answer `/health`, so `ServerSupervisor` restarted it every
|
|
15
|
+
~60s — but a fresh server is starved too, so it just dropped the in-flight
|
|
16
|
+
message and looped. Verified in `logs/server.log`: 6 server restarts in ~35min
|
|
17
|
+
during a load spike (load ratio ~2× cores).
|
|
18
|
+
|
|
19
|
+
The guard makes the supervisor load-aware: while the box is CPU-starved
|
|
20
|
+
(`loadavg[0]/cpuCount > 1.5`, the same signal SleepWakeDetector uses), an
|
|
21
|
+
alive-but-unresponsive server's restart is DEFERRED (up to a 5-minute hard cap)
|
|
22
|
+
instead of bounced.
|
|
23
|
+
|
|
24
|
+
## Files touched
|
|
25
|
+
|
|
26
|
+
- `src/core/cpuStarvation.ts` (**new**) — shared `cpuLoadRatio` / `isCpuStarved`
|
|
27
|
+
+ `DEFAULT_MAX_LOAD_RATIO`.
|
|
28
|
+
- `src/lifeline/ServerSupervisor.ts` — extracted the two identical
|
|
29
|
+
health-failure branches into `evaluateUnhealthyServer()`; added
|
|
30
|
+
`deferRestartForCpuStarvation()`, `maxLoadRatio`, `starvationRestartThreshold`,
|
|
31
|
+
and an injectable `loadRatioProvider`.
|
|
32
|
+
- `tests/unit/cpu-starvation.test.ts` (new), `tests/unit/supervisor-cpu-starvation-defer.test.ts` (new).
|
|
33
|
+
|
|
34
|
+
## Decision-point inventory
|
|
35
|
+
|
|
36
|
+
- **Restart vs defer (alive + unresponsive)** — *modify, additive*. Was: restart
|
|
37
|
+
at `processAliveThreshold` (6 failures). Now: if CPU-starved and below the hard
|
|
38
|
+
cap, defer; else unchanged. A pure presence check on system load, not a
|
|
39
|
+
judgment. All branches covered by tests driving the real method.
|
|
40
|
+
- **Branch extraction** — *refactor, behavior-preserving*. The two duplicated
|
|
41
|
+
failure paths (unhealthy `/health`, thrown check) now call one
|
|
42
|
+
`evaluateUnhealthyServer()`. The non-starved/dead/below-threshold behavior is
|
|
43
|
+
byte-for-byte the same; verified by the existing `supervisor-health-check`
|
|
44
|
+
test still passing + new real-method tests.
|
|
45
|
+
- **Load read** — *read-only*. `os.loadavg()` / `os.cpus().length` via the shared
|
|
46
|
+
helper; returns 0 on error (fails toward "not starved" → restart, the safe
|
|
47
|
+
legacy behavior). No new file, no write, no new external surface.
|
|
48
|
+
|
|
49
|
+
## Blast radius
|
|
50
|
+
|
|
51
|
+
- **No new authority, no new gate, no new API route, no external surface.** A
|
|
52
|
+
defer behind the existing restart decision.
|
|
53
|
+
- **Strictly less aggressive.** Can only DEFER a restart it would otherwise do,
|
|
54
|
+
only while genuinely CPU-starved, only up to a 5-min hard cap. Dead process →
|
|
55
|
+
immediate restart unchanged; not-starved → restart at 6 unchanged. Cannot
|
|
56
|
+
strand a hung server (hard cap) and the defer self-clears on the next healthy
|
|
57
|
+
tick.
|
|
58
|
+
- **No agent-installed files changed** — pure `src/` (lifeline + a helper). No
|
|
59
|
+
`PostUpdateMigrator` entry required; the lifeline picks it up on its next
|
|
60
|
+
restart via the existing version-skew/drift coordination.
|
|
61
|
+
|
|
62
|
+
## Pre-existing-bug note
|
|
63
|
+
|
|
64
|
+
The two health-failure branches were identical copy-paste — a latent drift risk
|
|
65
|
+
(a fix to one could miss the other). Extracting them into one method removes that
|
|
66
|
+
hazard as a side benefit.
|
|
67
|
+
|
|
68
|
+
## Phase 5 trigger check (second-pass reviewer)
|
|
69
|
+
|
|
70
|
+
Second pass **not required**: no new authority, no new destructive operation (the
|
|
71
|
+
only behavioral delta REMOVES a restart while starved), no external surface, no
|
|
72
|
+
migration. Additive, read-only load signal behind an existing restart primitive,
|
|
73
|
+
with real-method unit coverage + a wiring guard.
|
|
74
|
+
|
|
75
|
+
## Verification
|
|
76
|
+
|
|
77
|
+
- `tests/unit/cpu-starvation.test.ts` — pure helper (ratio + threshold).
|
|
78
|
+
- `tests/unit/supervisor-cpu-starvation-defer.test.ts` — REAL
|
|
79
|
+
`evaluateUnhealthyServer()`: defer-while-starved, restart-when-not,
|
|
80
|
+
force-restart-past-cap, dead→immediate, below-threshold→wait,
|
|
81
|
+
defer-not-permanent + wiring guard. 19 pass with the existing
|
|
82
|
+
`supervisor-health-check` suite.
|
|
83
|
+
- `tsc --noEmit` clean.
|