instar 1.3.635 → 1.3.637
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/core/SleepWakeDetector.d.ts +42 -1
- package/dist/core/SleepWakeDetector.d.ts.map +1 -1
- package/dist/core/SleepWakeDetector.js +50 -0
- package/dist/core/SleepWakeDetector.js.map +1 -1
- package/dist/core/storage/JsonlStore.d.ts +54 -0
- package/dist/core/storage/JsonlStore.d.ts.map +1 -0
- package/dist/core/storage/JsonlStore.js +81 -0
- package/dist/core/storage/JsonlStore.js.map +1 -0
- package/dist/utils/jsonl-rotation.d.ts +37 -0
- package/dist/utils/jsonl-rotation.d.ts.map +1 -1
- package/dist/utils/jsonl-rotation.js +86 -0
- package/dist/utils/jsonl-rotation.js.map +1 -1
- package/package.json +2 -2
- package/scripts/bounded-accumulation-retention-baseline.json +81 -0
- package/scripts/bounded-accumulation-wholefile-read-baseline.json +5 -0
- package/scripts/lint-no-wholefile-sync-read.js +128 -0
- package/scripts/lint-store-retention-declared.js +93 -0
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/state-coherence-registry.json +71 -10
- package/upgrades/1.3.636.md +59 -0
- package/upgrades/1.3.637.md +41 -0
- package/upgrades/side-effects/bounded-accumulation-increment-1.md +103 -0
- package/upgrades/side-effects/sleepwake-stall-not-sleep.md +102 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Side-Effects Review — SleepWakeDetector: per-process CPU check separates event-loop block from sleep
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `sleepwake-stall-not-sleep`
|
|
4
|
+
**Tier:** 1 (surgical bug-fix, one core file + tests; no new route, config contract, or persistence schema)
|
|
5
|
+
|
|
6
|
+
## Summary of the change
|
|
7
|
+
|
|
8
|
+
`SleepWakeDetector` inferred "sleep" from timer drift + **system loadavg**. A single
|
|
9
|
+
Node thread blocking its own event loop for tens of seconds does NOT move a 16-core
|
|
10
|
+
`loadavg` above `maxLoadRatio` (1.5), so an isolated event-loop block under normal
|
|
11
|
+
system load emitted a FALSE `wake` ("Wake detected after ~14s sleep") — even on a
|
|
12
|
+
caffeinated host where sleep is physically impossible. That laundered real wedges into
|
|
13
|
+
"sleep" and masked the actual fault.
|
|
14
|
+
|
|
15
|
+
The fix adds a per-PROCESS discriminator: `process.cpuUsage()` sampled across the drift
|
|
16
|
+
gap. A suspended (sleeping) process burns ~0 CPU; a blocked event loop burns CPU through
|
|
17
|
+
most of the gap. When `cpuBusyRatio >= cpuBlockBusyRatio` (default 0.5) the drift is a
|
|
18
|
+
BLOCK — emit a new signal-only `stall` event and SUPPRESS the false `wake`. The check
|
|
19
|
+
runs ahead of the existing load/burst/cooldown guards and, unlike the old `isLongSleep`
|
|
20
|
+
exemption, applies to LONG drifts too (a multi-minute CPU-busy drift is the wedge, never
|
|
21
|
+
sleep).
|
|
22
|
+
|
|
23
|
+
## Decision-point inventory
|
|
24
|
+
|
|
25
|
+
- **Threshold `cpuBlockBusyRatio` = 0.5.** A real wake burns ~0 CPU over the gap (ratio
|
|
26
|
+
~0); a CPU-bound block burns ~1 core (ratio ~1.0). 0.5 is the safe midpoint; set 0 to
|
|
27
|
+
disable. Tunable, defaults on.
|
|
28
|
+
- **Ordering:** the CPU check is FIRST (most authoritative). A real long sleep (≈0 CPU)
|
|
29
|
+
falls through it and still emits a wake via the existing `isLongSleep` exemption.
|
|
30
|
+
- **New `stall` event:** signal-only. No consumer = harmless (the value is the suppressed
|
|
31
|
+
false wake). Wedge watchers MAY consume it later.
|
|
32
|
+
|
|
33
|
+
## 1. Over-block (false positive — suppressing a REAL wake)
|
|
34
|
+
|
|
35
|
+
Could a genuine wake be misread as a block? On real OS suspend the process is frozen →
|
|
36
|
+
~0 CPU over the gap → ratio ~0 → NOT suppressed → wake emits correctly. A wake immediately
|
|
37
|
+
followed by heavy CPU still reads low, because the drift tick fires once at wake and the
|
|
38
|
+
gap's CPU (the frozen span) is ~0. Verified by the `genuine sleep → WAKE` test.
|
|
39
|
+
|
|
40
|
+
## 2. Under-block (missing a block)
|
|
41
|
+
|
|
42
|
+
A low-CPU stall (IO-wait, not CPU-bound) reads ratio ~0 and is NOT flagged by this check —
|
|
43
|
+
by design; it falls through to the existing load/burst/recurring guards exactly as before.
|
|
44
|
+
This change only ADDS detection for the CPU-bound case the load heuristics were blind to;
|
|
45
|
+
it removes no existing suppression path.
|
|
46
|
+
|
|
47
|
+
## 4. Signal vs authority compliance
|
|
48
|
+
|
|
49
|
+
The detector only decides whether to emit `wake` vs `stall` — both are signals to other
|
|
50
|
+
watchers; it gates nothing and takes no destructive action. Recovery authority stays with
|
|
51
|
+
the consumers (ServerSupervisor / wedge watchers), unchanged.
|
|
52
|
+
|
|
53
|
+
## 5. Interactions
|
|
54
|
+
|
|
55
|
+
`getCumulativeSleepMsBetween` (the wake-reaper's sleep-credit source) reads only EMITTED
|
|
56
|
+
wakes; a suppressed block is never credited as sleep — so a wedge can no longer inflate a
|
|
57
|
+
job's sleep credit and cause an early reap. `getStats().suppressedByReason` gains an
|
|
58
|
+
`event-loop-block` counter (additive; existing keys unchanged).
|
|
59
|
+
|
|
60
|
+
## 6. External surfaces
|
|
61
|
+
|
|
62
|
+
None. No new route, no config-file contract change (the two new config fields are optional
|
|
63
|
+
with safe defaults), no persistence, no messaging. `GET /sleep/stats` (routes.ts) returns
|
|
64
|
+
the same `SleepWakeStats` shape plus the additive reason key.
|
|
65
|
+
|
|
66
|
+
## 6b. Operator-surface quality
|
|
67
|
+
|
|
68
|
+
N/A — no operator/dashboard/approval surface is touched. Change is internal to a core
|
|
69
|
+
detector.
|
|
70
|
+
|
|
71
|
+
## Framework generality
|
|
72
|
+
|
|
73
|
+
N/A — `SleepWakeDetector` is a framework-agnostic core monitor (not part of the session
|
|
74
|
+
launch/inject abstraction). It runs per-process regardless of which agentic framework the
|
|
75
|
+
session uses.
|
|
76
|
+
|
|
77
|
+
## 7. Multi-machine posture
|
|
78
|
+
|
|
79
|
+
Per-process and per-machine by nature; each machine's detector watches its own loop. No
|
|
80
|
+
replicated state, no cross-machine contract. Safe on single- and multi-machine installs.
|
|
81
|
+
|
|
82
|
+
## 8. Rollback cost
|
|
83
|
+
|
|
84
|
+
Trivial and safe: set `cpuBlockBusyRatio: 0` to disable the new branch (reverts to the
|
|
85
|
+
prior load-only behavior), or revert the commit. The defensive CPU read fails toward the
|
|
86
|
+
old behavior, so even a provider error degrades to pre-change semantics rather than
|
|
87
|
+
breaking.
|
|
88
|
+
|
|
89
|
+
## Evidence pointers
|
|
90
|
+
|
|
91
|
+
- New tests: `tests/unit/SleepWakeDetector-cpu-block.test.ts` (5 tests — CPU-busy short
|
|
92
|
+
drift → stall; genuine sleep → wake; long CPU-busy drift → stall; stats record; throwing
|
|
93
|
+
provider never crashes the tick). Existing `SleepWakeDetector.test.ts` (10 tests) still
|
|
94
|
+
green — 15/15. `tsc --noEmit` clean.
|
|
95
|
+
- Live root cause (2026-06-21): a caffeinated, lid-open, plugged-in host logged "Wake
|
|
96
|
+
detected after ~Ns sleep" for what were event-loop blocks — the misdiagnosis this fixes.
|
|
97
|
+
|
|
98
|
+
## Conclusion
|
|
99
|
+
|
|
100
|
+
A correctness fix that makes drift classification honest: CPU-bound event-loop blocks are
|
|
101
|
+
flagged as `stall`, not laundered into a false `wake`. Defaults on, fails safe, trivially
|
|
102
|
+
reversible. Ship.
|