pi-crew 0.11.51 → 0.11.53

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/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.53] — event-log in-flight flush await fix (macos CI catch) + Bug-030 RCA closed (2026-10-31)
4
+
5
+ **Fixed (product, caught by macos CI).** `flushOneEventLogBuffer` removed the queue from
6
+ `bufferedQueues` BEFORE its multi-turn async lock+append completed, so `flushEventLogBuffer()` /
7
+ `drainEventLogWriters()` were **no-ops for a path with an in-flight flush** — `await flushEventLogBuffer()` could
8
+ return while the batch's `appendFile` was still in flight (deterministic-await broken; the interrupt-guard
9
+ detached regression test read the log too early on the slower macos runner fs — CI run 41147573288; fast
10
+ disks hid the race locally). Same window on the exit path (`flushBufferedQueuesSync` cannot recover an
11
+ in-flight batch — documented limitation, sync-only). Fix: per-path in-flight flush tracker
12
+ (`inflightBufferFlushes`) chained single-entry per path; `flushEventLogBuffer()` and `drainEventLogWriters()`
13
+ await the in-flight flush FIRST, then flush whatever landed in the queue since. Pin:
14
+ `flushEventLogBuffer awaits an IN-FLIGHT flush` (deterministic read immediately after the await — no settling
15
+ sleep).
16
+
17
+ **Fixed (test-only).** `waitForPreGateEvents` (classifier-pre-gate) now THROWS on timeout with counts + path
18
+ instead of returning silently — the silent return turned a legit wait-timeout into a far-away
19
+ `TypeError: Cannot read properties of undefined (reading 'taskId')` on `preGateEvents(...)[0]!` (the [pg-10]
20
+ CI flake: 2 Node-22 shards, run 40797688830, 2026-10-29; rerun green). A timeout now fails AT the wait site
21
+ with diagnostics (found vs expected `task.verifier_pre_gate` events, eventsPath, budget).
22
+
23
+ **Bug-030 RCA (closed 2026-10-30): CI-runner I/O starvation, not an append-path bug.** Traced the full
24
+ delivery chain: `appendEventAsync` → per-path `asyncQueues` (in-process promise chain) →
25
+ `withEventLogLockAsync` (mkdir O_EXCL, 50ms retry, **5s lock timeout**) → `.seqlock` reserve → `appendFile`,
26
+ gated by the fire-and-forget `void` + best-effort `.catch` in `classifyVerifierPreGate`. The [pg-10] test has
27
+ no cross-process contention (per-test tmpdir, one process per test file), so lock acquisition is instant —
28
+ the missed wait was the append landing AFTER the old 3s wall-clock budget but well inside the 5s lock
29
+ timeout (the drop-path needs the lock timeout to throw AND be swallowed — a runner freeze, and the event
30
+ is best-effort diagnostic by design). The [pg-10] family has not recurred since (8 subsequent CI runs, incl.
31
+ v0.11.51/0.11.52 matrices). Hardened deterministically: `waitForPreGateEvents` now DRAINS the in-process
32
+ append queue each poll (`drainEventLogWriters(eventsPath)` — awaits the ACTUAL append instead of racing
33
+ wall-clock) and the budget is 10s — strictly above the 5s lock timeout, so a throw semantically means
34
+ "append dropped or failed", never merely "slower than 3s". New pin `[pg-11]`: the helper rejects with
35
+ diagnostics when nothing ever lands (never silent, never false-pass).
36
+
37
+ ## [0.11.52] — live-catch backlog: cross-session cancel reaches inline runs + heartbeat-watcher false-positive class (2026-10-30)
38
+
39
+ **Fixed (runtime, live-caught run `team_20261030044630`).** Two gaps filed as backlog after the
40
+ 0.11.51 real-test battery, both root-caused from the incident artifacts (not hypotheses):
41
+
42
+ 1. **Cross-session cancel never reached INLINE async runs.** The foreground-control interrupt guard
43
+ lived ONLY in background-runner's detached `main()`. Runs executed inline in the owner session
44
+ process (`executeBackgroundRun` core — also the `PI_CREW_TEST_ASYNC_INLINE` seam) never polled
45
+ `foreground-control.json`: the incident run had `manifest.async === undefined`, zero `async.*`
46
+ events, and TWO interrupt requests both stuck `acknowledged:false` while worker 403288 ran ≥75s
47
+ post-cancel with fresh heartbeats. Fix: guard core extracted as `startRunInterruptGuard` (hosted in
48
+ `foreground-control.ts` with the producer — keeps the lazy-imports ratchet at 542); detached
49
+ `startInterruptGuard` is now a thin process-wide wrapper (behavior byte-identical: sweep +
50
+ stopParentGuard + exitCode 130, pinned by regression tests); `executeBackgroundRun` with no host
51
+ signal mounts a RUN-SCOPED guard — ack + event + `runController.abort()` ONLY, never
52
+ `terminateActiveChildPiProcesses()` (a global sweep would murder sibling runs' children in the same
53
+ host process; pinned by the SCOPED-KILL test with a real registered child). The team-run input now
54
+ receives the RESOLVED signal — inline runs previously had NO cancellation source at all. Guard
55
+ stops in a `finally`; ownership deduped by host-signal presence (no double guard).
56
+ 2. **heartbeat-watcher false-dead class.** `crew.task.heartbeat_dead` fired 3× for a live worker
57
+ (heartbeat silent >5min during long tool work, `task.heartbeat.pid` undefined on every task, so the
58
+ PID-liveness downgrade gate had nothing to read), and `handleRetry` re-queued tasks WITHOUT
59
+ stripping liveness fields (attempt-2 inherited attempt-1's heartbeat timestamps). Fix: (a) watcher
60
+ attempt-clamp — heartbeat/`agentProgress` evidence older than the current attempt's `startedAt` is
61
+ inadmissible; no usable evidence ⇒ elapsed from `startedAt` (a task younger than `deadMs` can never
62
+ be dead); (b) `handleRetry` strips `heartbeat` + `agentProgress` on re-queue (checkpoint untouched);
63
+ (c) child-executor pins the attempt's worker pid into `task.heartbeat` (per-attempt reset, guarded
64
+ spread) so the PID-gate actually functions.
65
+
66
+ Also: an already-ACKED interrupt no longer warns "Ignoring unknown foreground control request" every
67
+ poll tick until guard stop (verifier-flagged cosmetic, carried over from the pre-extraction guard).
68
+
69
+ Tests: 5 new `interrupt-guard.test.ts` (inline wiring, post-stop non-consumption, SCOPED-KILL,
70
+ detached regression, acked-silence), `async-runner-inline-interrupt.test.ts` (e2e via the real inline
71
+ seam: mid-run request → ack ≤3s + event + terminal run + no leaked interval), 4 watcher attempt-clamp
72
+ pins (incl. genuinely-stale-still-dead regression + no-startedAt legacy), 2 heartbeat-pid pins, +1
73
+ lifecycle retry-strip. Leader gates: full unit+integration, typecheck, biome, format:check,
74
+ lazy-imports 542, slop 41 — all green.
75
+
3
76
  ## [0.11.51] — SEC-2026-11: security audit remediation (30 confirmed findings + 4 leads, 13 waves) (2026-10-30)
4
77
 
5
78
  **Fixed (security).** Full remediation of the 2026-10-29 independent security audit (skill security-audit, full
@@ -96,13 +169,6 @@ halves) and the residuals deferred by w1b/w7a/w7b:
96
169
  42 new regression tests. Owner-observed deployment checklists for the leads' impact confirmation remain in the
97
170
  audit's NEEDS-VALIDATION.md.
98
171
 
99
- ## [Unreleased] — test hardening
100
-
101
- **Fixed (test-only).** `waitForPreGateEvents` (classifier-pre-gate) now THROWS on timeout with counts + path
102
- instead of returning silently — the silent return turned a legit wait-timeout into a far-away
103
- `TypeError: Cannot read properties of undefined (reading 'taskId')` on `preGateEvents(...)[0]!` (the [pg-10]
104
- CI flake: 2 Node-22 shards, run 40797688830, 2026-10-29; rerun green). A timeout now fails AT the wait site
105
- with diagnostics (found vs expected `task.verifier_pre_gate` events, eventsPath, budget). Bug-030 RCA backlog item.
106
172
 
107
173
  ## [0.11.50] — LOW-1: PI_CREW_BROKER_STDIN fd3 handshake DEFAULT-ON ('0' opts out) (2026-10-28)
108
174