@huseynsnmz/pi-board 0.15.3 → 0.16.0
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 +13 -4
- package/CONTRIBUTING.md +7 -0
- package/README.md +53 -419
- package/SECURITY.md +19 -0
- package/docs/how-it-works.md +31 -0
- package/docs/reference.md +276 -0
- package/package.json +5 -11
- package/src/index.ts +1 -1
- package/src/runtime/service.mjs +12 -0
- package/src/ui/dashboard.ts +14 -8
- package/IMPLEMENTATION_PLAN.md +0 -920
- package/PRD.md +0 -484
- package/PROGRESS.md +0 -142
- package/VERIFY.md +0 -115
- package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
- package/docs/EXPLORATION.md +0 -187
- package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
- package/docs/RELEASE.md +0 -148
- package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
- package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
- package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
- package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
- package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
- package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
- package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
- package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
- package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
- package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
- package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
- package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
- package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
- package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
- package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
- package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
- package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
- package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
- package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
- package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
- package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
- package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
- package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
- package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
- package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
- package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
- package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
- package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
- package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
- package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
- package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
- package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
- package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
- package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
- package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
- package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
- package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
- package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
- package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
- package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
- package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
- package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
- package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
- package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
- package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
- package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
- package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
- package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
- package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
- package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
- package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
- package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
- package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
- package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
- package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
- package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
- package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
- package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
- package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
- package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
- package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
- package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
- package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
- package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
- package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
- package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
- package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
- package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
- package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
- package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
- package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
- package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
- package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
- package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
- package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
- package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
- package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
- package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
- package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
- package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
- package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
- package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
- package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
- package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
- package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
- package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
- package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
- package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
- package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
- package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
- package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
- package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
- package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
- package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
- package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
- package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
- package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
- package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
- package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
- package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
- package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
- package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
- package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
- package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
- package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
- package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
- package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
- package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
- package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
- package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
- package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
- package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
- package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
- package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
- package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
- package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
- package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
- package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
- package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
- package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
- package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
- package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
- package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
- package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
- package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
- package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
- package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
- package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
- package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
- package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
- package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
- package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
- package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
- package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
- package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
- package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
- package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
- package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
- package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
- package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
- package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
- package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
- package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
- package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
- package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
- package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
- package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
- package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
- package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
- package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
- package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
- package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
- package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
- package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
- package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
- package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
- package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
- package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
- package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
- package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
- package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
- package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
- package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
- package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
- package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
- package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
- package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
- package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
- package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
- package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
- package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
- package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
- package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
- package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
- package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
- package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
- package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
- package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
- package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
- package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
- package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
- package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
- package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
- package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
- package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
- package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
- package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
- package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
- package/scripts/budget-audit.mjs +0 -135
- package/scripts/release.mjs +0 -446
- package/scripts/release_helper.mjs +0 -309
- package/scripts/run-perf-gate.mjs +0 -40
- package/scripts/run-tests.mjs +0 -375
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
# Issue #241 — detached state-coordinator never exits on its own
|
|
2
|
-
|
|
3
|
-
Drafted: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-24T17:48:00+08:00
|
|
4
|
-
Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-24T17:59:00+08:00 (spec-cross-review round 1, degraded — findings R1-G1..G3, R1-D1..D8)
|
|
5
|
-
Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-24T18:04:00+08:00 (spec-cross-review round 2, degraded — converged; R2-D1..D3)
|
|
6
|
-
Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-24T18:35:00+08:00 (task-2 review I1: the root-missing step-down must not write a diagnostic — `appendLine` recreates the root skeleton; stderr attribution only. A3/A4 updated, §3.2/§6/§7 corrected)
|
|
7
|
-
Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-24T19:05:00+08:00 (task-3 verification ruling: A7 is a CI acceptance — the dev Windows box has 62 pre-existing environmental failures; local gate = 0 survivors + no new failure. U1 reworded to measure residue, not exit codes; §7 baseline row added)
|
|
8
|
-
Revised: deepseek/deepseek-flash (selected) · deepseek/deepseek-flash (physical) @ 2026-10-24T19:20:00+08:00 (final-review MUST-FIX I1: writes recreate a deleted root, so existence-only detection is insufficient — add the boot-time dev/ino root-identity guard, A3c integration test, correct §7/G1; wrapper trailing-separator false-green gets a code+unit fix in the same wave)
|
|
9
|
-
|
|
10
|
-
Status: step-4 root-cause report / spec draft (github-issue-driven). Cross-review:
|
|
11
|
-
`research/spec-review-round-1.md` (round 1 not converged → this revision; round 2
|
|
12
|
-
reviews this version). Research: `research/root-cause.md`. Issue comments: #241
|
|
13
|
-
(6382598306, 6382598816, 6382611575).
|
|
14
|
-
|
|
15
|
-
## 1. Problem statement
|
|
16
|
-
|
|
17
|
-
A detached `runner/state-coordinator.mjs` outlives every short-lived caller that
|
|
18
|
-
lazily started it (tests, probe child processes, detached runner children,
|
|
19
|
-
one-shot CLI/repro scripts) because it only ever exits on SIGTERM/SIGINT, a
|
|
20
|
-
server error, or three consecutive heartbeat failures. There is no idle timeout
|
|
21
|
-
and no "my root is gone" check; `server.listen()` keeps the event loop alive
|
|
22
|
-
forever. Live audit on the reporting box: 26 orphans, all roots under
|
|
23
|
-
`%TEMP%\agentview-*`, all parents dead, ~3.75 GB RSS, 68 `agentview-pty-*`
|
|
24
|
-
directories.
|
|
25
|
-
|
|
26
|
-
On win32 the throwaway root is also **undeletable while the coordinator lives**,
|
|
27
|
-
because the coordinator's own **cwd is the root** — verified: `rmSync(root)`
|
|
28
|
-
fails with `EPERM` (experiment in research §3). Therefore the issue's option 1
|
|
29
|
-
(`statSync(root)` ⇒ ENOENT ⇒ exit) cannot fire as written: the root is still
|
|
30
|
-
there. On Linux deleting a process's cwd is allowed, which is why CI stays green.
|
|
31
|
-
|
|
32
|
-
Root cause chain (four links):
|
|
33
|
-
|
|
34
|
-
1. `launchCoordinator` spawns `detached, stdio:ignore, cwd: root` — outliving
|
|
35
|
-
the caller is by design (a real board's coordinator must survive the
|
|
36
|
-
dashboard Pi), so the spawn shape itself is not the bug.
|
|
37
|
-
2. The coordinator has no root-vanished and no idle exit.
|
|
38
|
-
3. Short-lived callers lazily `ensureCoordinator` on throwaway roots and never
|
|
39
|
-
learn the spawned pid (`ensureCoordinator` returns it, but only
|
|
40
|
-
`coordinator-client.test.mjs` tracks it; probes and runner children do not).
|
|
41
|
-
4. On win32 the coordinator's `cwd: root` keeps the root present, so even the
|
|
42
|
-
"root is gone" signal the reporter proposed is shadowed by the process's own
|
|
43
|
-
handle — the test's `rmSync` fails, the directory and the process both stay.
|
|
44
|
-
|
|
45
|
-
## 2. Goals / non-goals
|
|
46
|
-
|
|
47
|
-
Goals:
|
|
48
|
-
|
|
49
|
-
- G1 A coordinator whose root was deleted exits on its own within a bounded,
|
|
50
|
-
small window, releasing its lease. Covers test/probe roots and one-shot CLI
|
|
51
|
-
roots, not just this repo's suite. A concurrent write that recreates the
|
|
52
|
-
directory is detected by the boot-time dev/ino identity guard and steps down on
|
|
53
|
-
the next tick (residual: a skeleton directory, never a surviving process).
|
|
54
|
-
- G2 Throwaway roots become deletable on win32 while a coordinator serves them
|
|
55
|
-
(the cwd handle is released), so test/probe cleanup stops leaving directories.
|
|
56
|
-
- G3 The test suite fails loudly if it leaks a coordinator: after `node --test`
|
|
57
|
-
finished, any coordinator spawned from this checkout on a temp root is killed
|
|
58
|
-
and the run exits nonzero.
|
|
59
|
-
- G4 Production behavior is unchanged while the board root exists: no extra
|
|
60
|
-
startup cost, no restart, no protocol bump, no data migration.
|
|
61
|
-
|
|
62
|
-
Non-goals:
|
|
63
|
-
|
|
64
|
-
- Idle timeout ("root still exists but nobody talks to me"). No acceptance
|
|
65
|
-
criterion in the issue requires it; it needs a conservative knob and could
|
|
66
|
-
thrash a live board. Follow-up candidate.
|
|
67
|
-
- Changing the detached spawn design (the coordinator must outlive the
|
|
68
|
-
dashboard Pi).
|
|
69
|
-
- Cleaning the 330 empty `agentview-cclient-*` directories (fake-server roots
|
|
70
|
-
that are simply never removed in `coordinator-client.test.mjs`; no live
|
|
71
|
-
process, separate hygiene item).
|
|
72
|
-
- Auto-cleaning orphans that predate this change (they have no self-exit code).
|
|
73
|
-
The wrapper only snapshots-and-warns about pre-existing ones.
|
|
74
|
-
|
|
75
|
-
## 3. Design
|
|
76
|
-
|
|
77
|
-
### 3.1 Release the root handle (launchCoordinator)
|
|
78
|
-
|
|
79
|
-
`src/core/launch.mjs` (add `import { dirname, resolve } from "node:path";` — the
|
|
80
|
-
file currently imports only `spawn`, `atomicWriteJson`, `resolveNode`, `paths.mjs`,
|
|
81
|
-
`writePid`, see `launch.mjs:8-12`):
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
export function coordinatorSpawnPlan(root) {
|
|
85
|
-
const absoluteRoot = resolve(root);
|
|
86
|
-
return { root: absoluteRoot, cwd: dirname(absoluteRoot) };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function launchCoordinator(root, opts) {
|
|
90
|
-
const node = opts.node ?? resolveNode();
|
|
91
|
-
const plan = coordinatorSpawnPlan(root);
|
|
92
|
-
const child = spawnDetached(node, [opts.runnerScript, plan.root], plan.cwd);
|
|
93
|
-
return { pid: child.pid ?? null };
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
- The coordinator derives every path from the absolute root argument and never
|
|
98
|
-
touches `process.cwd()` (verified line by line: `runner/state-coordinator.mjs`
|
|
99
|
-
uses `root = process.argv[2]` at `:91` plus `src/core/paths.mjs`; no `chdir`).
|
|
100
|
-
cwd is purely a process-level side effect, so there is no behavior change.
|
|
101
|
-
- Passing `resolve(root)` keeps relative-root callers working and makes the argv
|
|
102
|
-
canonical. Pipe-name isolation is unaffected: `coordinatorEndpointPathFor`
|
|
103
|
-
already hashes `path.win32.resolve(root)` (`src/core/paths.mjs:120-139`,
|
|
104
|
-
issue #124), so every spelling maps to the same endpoint.
|
|
105
|
-
- `dirname(root)` exists whenever the root's parent exists; for `C:\`/`/` the
|
|
106
|
-
parent is the root itself. The parent of a board root (`~/.pi/agent`) and the
|
|
107
|
-
parent of a temp root (`os.tmpdir()`) are stable, so the process still has a
|
|
108
|
-
valid cwd and no longer pins a throwaway root.
|
|
109
|
-
|
|
110
|
-
### 3.2 Root-vanished self-exit (state-coordinator)
|
|
111
|
-
|
|
112
|
-
`runner/state-coordinator.mjs`, inside the existing supervised heartbeat
|
|
113
|
-
interval (started in the `server.listen` callback, `:237-272`, default 1000 ms,
|
|
114
|
-
`AGENT_BOARD_TEST_COORDINATOR_HEARTBEAT_MS` compressible per
|
|
115
|
-
`src/core/test-knobs.mjs` and read at `main()` start, `:123`):
|
|
116
|
-
|
|
117
|
-
```js
|
|
118
|
-
heartbeatTimer = setInterval(() => {
|
|
119
|
-
if (rootVanished()) {
|
|
120
|
-
// No diagnostic: appendLine ensureDirs its parent and would recreate
|
|
121
|
-
// <root>/views/_coordinator, leaving the skeleton dir U1 measures.
|
|
122
|
-
process.stderr.write(`state-coordinator: board root missing (${root}); stepping down\n`);
|
|
123
|
-
shutdown(0);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
// ... existing touch/failure logic unchanged
|
|
127
|
-
}, heartbeatMs);
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
`rootVanished()` is a module-scope helper using the already-imported `statSync`
|
|
131
|
-
(`:39`):
|
|
132
|
-
|
|
133
|
-
```js
|
|
134
|
-
function rootVanished() {
|
|
135
|
-
try { statSync(root); return false; }
|
|
136
|
-
catch (err) { return err?.code === "ENOENT"; }
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
**Root-replacement guard (final-review I1).** An existence-only check is not
|
|
141
|
-
enough: a write that lands after the delete (`appendCommand` / `writeState` →
|
|
142
|
-
`ensureDir`) *succeeds by recreating* the directory, so the next tick sees
|
|
143
|
-
"present" and the coordinator serves a dead board — or, in the boot-window race
|
|
144
|
-
(guard passes, then the root is deleted before lease acquisition `ensureDir`),
|
|
145
|
-
it acquires a lease on the recreated root and serves forever. Capture the
|
|
146
|
-
directory identity once at boot and step down when it changes:
|
|
147
|
-
|
|
148
|
-
```js
|
|
149
|
-
const rootIdentity = captureRootIdentity(); // {dev, ino} | null (null when the FS reports ino 0)
|
|
150
|
-
function rootVanishedOrReplaced() {
|
|
151
|
-
try {
|
|
152
|
-
const st = statSync(root);
|
|
153
|
-
if (!rootIdentity) return null;
|
|
154
|
-
return st.dev !== rootIdentity.dev || st.ino !== rootIdentity.ino ? "replaced" : null;
|
|
155
|
-
} catch (err) {
|
|
156
|
-
return err?.code === "ENOENT" ? "missing" : null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
The boot guard captures the identity before lease acquisition and compares the
|
|
162
|
-
same way. Step-down stderr: `state-coordinator: board root missing (<root>);
|
|
163
|
-
stepping down` or `state-coordinator: board root replaced (<root>); stepping
|
|
164
|
-
down`. When `ino` is unavailable/0 the check degrades to existence-only (never a
|
|
165
|
-
false step-down). Residual accepted: a write in the delete window may leave a
|
|
166
|
-
skeleton directory (data, not a process); the guard bounds its lifetime to one
|
|
167
|
-
tick.
|
|
168
|
-
|
|
169
|
-
- `ENOENT` ⇒ step down. Any other error (EACCES/EBUSY) or success ⇒ keep
|
|
170
|
-
serving — a transient access failure must not kill a healthy coordinator.
|
|
171
|
-
- **No diagnostic is written on the root-missing path.** Every append helper
|
|
172
|
-
`ensureDir`s its parent (`src/core/atomic.mjs:107-109`), so writing
|
|
173
|
-
`<root>/views/_coordinator/diagnostics.jsonl` would *succeed* by recreating the
|
|
174
|
-
deleted directory chain — reintroducing the skeleton-dir residue U1 measures.
|
|
175
|
-
The step-down is logged to stderr instead
|
|
176
|
-
(`state-coordinator: board root missing (<root>); stepping down`): detached
|
|
177
|
-
callers ignore stdio, while a test can spawn the runner with a stderr pipe and
|
|
178
|
-
attribute the exit deterministically. The boot-time guard (`rootVanished()`
|
|
179
|
-
before lease acquisition — lease acquisition also `ensureDir`s and would
|
|
180
|
-
recreate the root, so "fails lease acquisition" is not a viable boot path)
|
|
181
|
-
logs the same stderr line and writes nothing either.
|
|
182
|
-
- `shutdown(0)` already clears the interval, closes the server, best-effort
|
|
183
|
-
unlinks the POSIX socket, releases the lease, and exits — all cleanup is
|
|
184
|
-
try/catch guarded (`:278-293`). `shutdown` is idempotent via its `shuttingDown`
|
|
185
|
-
flag, and `lease.release()` no-ops when `owner.json` is gone (token-fenced).
|
|
186
|
-
- A command in flight at the instant the root disappears fails its journal/write
|
|
187
|
-
and the client observes the documented ambiguous outcome
|
|
188
|
-
(`connection_reset`/`timeout`); it must not retry with a fresh commandId
|
|
189
|
-
(`coordinator-client.mjs:11-25`). This is an accepted edge, see §7.
|
|
190
|
-
|
|
191
|
-
Bounds: detection latency ≤ one heartbeat tick (1 s production) after the root
|
|
192
|
-
disappears. No new timer, no new startup cost. The test knob also shortens the
|
|
193
|
-
lease touch interval, but the stale-lease threshold stays
|
|
194
|
-
`max(60s, 3 × heartbeatMs)` = 60 s (existing #224 behavior in
|
|
195
|
-
`src/core/locks.mjs`), so A3/A4 never depend on the knob changing stale
|
|
196
|
-
semantics.
|
|
197
|
-
|
|
198
|
-
### 3.3 Suite gate (new `scripts/run-tests.mjs`)
|
|
199
|
-
|
|
200
|
-
`package.json` (`:56-57`):
|
|
201
|
-
|
|
202
|
-
```json
|
|
203
|
-
"test": "node scripts/run-tests.mjs",
|
|
204
|
-
"test:coverage": "c8 node scripts/run-tests.mjs",
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
The wrapper (Node, no shell glob, no new dependency):
|
|
208
|
-
|
|
209
|
-
1. Enumerate test files: `readdirSync("test")` filtered to `*.test.mjs`, sorted,
|
|
210
|
-
passed to `node --test <files>`; child stdio inherited.
|
|
211
|
-
2. **Before** the run, enumerate coordinator processes and keep their pids as a
|
|
212
|
-
*snapshot* (pre-existing orphans must not fail this run; print a one-line
|
|
213
|
-
warning with the count instead).
|
|
214
|
-
3. Spawn `process.execPath --test <files...>`, wait for exit. `exitCode === null`
|
|
215
|
-
(killed by a signal) is treated as failure and the signal is printed — it is
|
|
216
|
-
never a success passthrough.
|
|
217
|
-
4. **After** the run, poll every 100 ms up to 10 s for every coordinator above
|
|
218
|
-
the snapshot to be gone (the 1 s self-exit window plus load slack).
|
|
219
|
-
5. Any survivor: print pid + root per process, `SIGTERM` (on win32 that is
|
|
220
|
-
TerminateProcess), wait 2 s, `SIGKILL` leftovers, and exit 1. Otherwise exit
|
|
221
|
-
with the test run's own exit code (test failures keep priority; residue is
|
|
222
|
-
also reported if both happen).
|
|
223
|
-
|
|
224
|
-
**Import safety (required):** the module must be side-effect free on import —
|
|
225
|
-
`main()` runs only when the file is the entry point, e.g.
|
|
226
|
-
`if (import.meta.url === pathToFileURL(resolve(process.argv[1])).href) main();`
|
|
227
|
-
otherwise the wrapper's own unit tests (A5/A6) would recursively run the suite.
|
|
228
|
-
|
|
229
|
-
Process enumeration, split for testability:
|
|
230
|
-
|
|
231
|
-
```js
|
|
232
|
-
export function parseProcessList(raw, platform) // pure
|
|
233
|
-
export function selectOrphanCoordinators({ processes, repoRoot, tmpRoot, platform }) // pure
|
|
234
|
-
export async function runSuite({ spawn, listProcesses, kill, sleep, now, ... }) // injectable shell
|
|
235
|
-
export function main() // real shell, guarded entry
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
- win32 enumeration: `pwsh`/`powershell` `-NoProfile -NonInteractive -Command`
|
|
239
|
-
with `[Console]::OutputEncoding = [Text.Encoding]::UTF8; $OutputEncoding = ...;`
|
|
240
|
-
prefix and `Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
|
|
241
|
-
Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress`. The JSON may
|
|
242
|
-
be a single object when only one process matches — `parseProcessList` must
|
|
243
|
-
accept both object and array.
|
|
244
|
-
- POSIX enumeration: `ps -e -ww -o pid=,args=` (`-ww` = unlimited width; without
|
|
245
|
-
it macOS/Linux may truncate the command line before the root argument and the
|
|
246
|
-
process silently escapes selection).
|
|
247
|
-
- Selection predicate: command line contains this checkout's
|
|
248
|
-
`runner/state-coordinator.mjs` absolute path (case-insensitive on win32,
|
|
249
|
-
separator-normalized) **and** its root argument resolves under `os.tmpdir()`.
|
|
250
|
-
Production roots (`~/.pi/agent/agent-board`, `$AGENT_BOARD_ROOT`) never match,
|
|
251
|
-
so a live board coordinator is never touched; another checkout's test
|
|
252
|
-
coordinator never matches because the script path differs.
|
|
253
|
-
- Root extraction from the command line: the token after
|
|
254
|
-
`state-coordinator.mjs`, quotes stripped (spawned argv is not quoted, CIM may
|
|
255
|
-
quote paths with spaces).
|
|
256
|
-
- Enumeration unavailable (command missing/permission error): print a warning
|
|
257
|
-
and skip the residue check (fail-open). CI (`ps`, ubuntu) and the reporting
|
|
258
|
-
Windows box (`pwsh`) both support it; this avoids turning an enumeration
|
|
259
|
-
environment issue into a red suite.
|
|
260
|
-
|
|
261
|
-
`.c8rc.json` (`:4-9`) gains `"scripts/run-tests.mjs"` in `exclude` (CLI shell +
|
|
262
|
-
process enumeration; the pure parts are unit-tested but the wrapper itself is a
|
|
263
|
-
dev runner). Coverage collection is unaffected: c8 exports `NODE_V8_COVERAGE` to
|
|
264
|
-
the whole child tree.
|
|
265
|
-
|
|
266
|
-
### 3.4 Ordering constraint
|
|
267
|
-
|
|
268
|
-
The wrapper gate (3.3) can only land green **after** 3.1 + 3.2 make the existing
|
|
269
|
-
suite stop leaking. Implementation order: 3.1 → 3.2 → integration tests → wire
|
|
270
|
-
package.json + wrapper → run the full suite. If the gate reports survivors that
|
|
271
|
-
come from a path not fixed by 3.1/3.2, that is a real finding to investigate
|
|
272
|
-
(possibly an additional lazy-spawn path), not something to silence.
|
|
273
|
-
|
|
274
|
-
## 4. Component contracts
|
|
275
|
-
|
|
276
|
-
| Symbol | Module | Contract |
|
|
277
|
-
|---|---|---|
|
|
278
|
-
| `coordinatorSpawnPlan(root)` | `src/core/launch.mjs` | `{ root: string (absolute), cwd: string }`; `cwd === dirname(resolve(root))`; idempotent for absolute roots. |
|
|
279
|
-
| `parseProcessList(raw, platform)` | `scripts/run-tests.mjs` | `Array<{pid:number, commandLine:string}>`; `win32` accepts CIM JSON array **or** single object, `linux`/`darwin` parse `ps` lines; unparsable lines skipped, never throws. |
|
|
280
|
-
| `selectOrphanCoordinators({processes, repoRoot, tmpRoot, platform})` | `scripts/run-tests.mjs` | `Array<{pid:number, root:string}>`; only this checkout's `runner/state-coordinator.mjs`; root under `tmpRoot`; deterministic order by pid. |
|
|
281
|
-
| `runSuite(deps)` | `scripts/run-tests.mjs` | Injectable `spawn/listProcesses/kill/sleep`; returns `{exitCode, survivors}`; no `process.exit` inside, no `main()` side effect on import. |
|
|
282
|
-
| root-vanished check | `runner/state-coordinator.mjs` | `ENOENT` on `statSync(root)` ⇒ step down; other errors/OK ⇒ keep serving. |
|
|
283
|
-
|
|
284
|
-
## 5. Acceptance matrix
|
|
285
|
-
|
|
286
|
-
Verification commands assume repo root. `WT` = worktree absolute path (step 5).
|
|
287
|
-
|
|
288
|
-
| ID | Functional point | Verification | Concrete check | Pass criterion |
|
|
289
|
-
|---|---|---|---|---|
|
|
290
|
-
| A1 | spawn plan: absolute argv + parent-dir cwd | Automated (unit) — `test/coordinator-lifecycle.test.mjs` | `coordinatorSpawnPlan` on absolute, trailing-slash, dot-segment roots | `root` absolute, `cwd === dirname(resolve(root))`, no throw |
|
|
291
|
-
| A2 | `launchCoordinator` actually spawns with that plan | Automated (integration, cross-platform) — same file | spawn a temp fake runner via `launchCoordinator(root, {runnerScript: fake})`; fake writes `{cwd: process.cwd(), arg: process.argv[2]}` under root; poll for the file | `arg === root` (absolute); both sides normalized with `path.resolve` and compared case-insensitively on win32 ⇒ `cwd === dirname(root)`; fails if cwd reverts to `root` |
|
|
292
|
-
| A3 | root-vanished self-exit releases the process; root becomes and stays deletable | Automated (integration) — same file | set `AGENT_BOARD_TEST_COORDINATOR_HEARTBEAT_MS=100` before spawn (restore in `finally`); spawn the real runner directly with the `coordinatorSpawnPlan` argv/cwd and `stdio: ["ignore", "pipe", "pipe"]`; wait until the endpoint answers; assert lease `owner.json` exists at `<root>/views/_coordinator/state-coordinator.lock/owner.json` (`P.viewLockPath`, `paths.mjs:171`) and pid alive; `rmSync(root, {maxRetries: 5, retryDelay: 50})`; wait for exit | rmSync does not throw; root gone and stays absent after exit; exit code 0; stderr matches `board root missing`; "exists before gone" asserted first (win32 rmSync success is the cwd evidence, stderr the self-exit attribution) |
|
|
293
|
-
| A3c | root recreated in the delete window does not defeat step-down | Automated (integration) — same file | start the real coordinator (direct spawn, stderr piped, 100 ms knob); wait ready; `rmSync(root)`; immediately send one valid `state_command` that is rejected but journaled (e.g. `patch_fields` on an unknown view) so the reply path recreates the root skeleton; await exit | exit code 0 within 2 s; stderr matches `/board root (missing\|replaced)/`; the directory may remain as a skeleton (data residue accepted) but no process survives |
|
|
294
|
-
| A4 | root present ⇒ no step-down (no false positive) | Automated (integration) — same file | same knob; start via the same direct spawn (stderr piped), wait ≥ 3 ticks (≈ 500 ms), then `rawPing` the endpoint and assert pid alive | still alive and answering ping; stderr contains no `board root missing` line (the root-missing path is observable only on stderr — it writes no diagnostic by design) |
|
|
295
|
-
| A5 | wrapper selection purity | Automated (unit) — `test/run-tests-wrapper.test.mjs` | win32 CIM array **and** single-object JSON samples + POSIX `ps` sample incl. a long (>200 char) command line; this-checkout vs other-checkout script path; temp vs production root; slash/case variants | exact pid sets, no throws, long line not truncated |
|
|
296
|
-
| A6 | wrapper orchestration + import safety | Automated (unit, injected fakes) — same file | survivors present → kill called for each, exit 1; clean → no kill, test exit code passthrough; snapshot survivors warn-only; `exitCode === null` → failure; `import` of the module performs no I/O and starts nothing | behavior as specified; importing twice is safe |
|
|
297
|
-
| A7 | suite is wired through the wrapper | Automated (build/suite) — CI `npm test` (ubuntu-latest) + local Windows baseline diff | in CI: `npm test` uses the wrapper and exits 0, printing `run-tests: no leaked coordinators`; `package.json` points at it. On the dev Windows box the full suite has a known pre-existing failure baseline (62 failures, separated by environment: AF_UNIX/EACCES `.sock`, AttachConsole, separator expectations — all reproduce with plain `node --test` on `main`); there the requirement is "failure set ⊆ baseline and 0 survivors", not exit 0 | CI: exit 0, no survivors; Windows: no new failures vs `main`, 0 survivors |
|
|
298
|
-
| A8 | coverage path still works with the wrapper | Automated (build) — `npm run test:coverage` (part of `npm run verify`) | c8 + wrapper + `.c8rc.json` exclude | coverage collected, thresholds lines 85 / funcs 80 / branches 70 hold |
|
|
299
|
-
| U1 | Windows box stops accumulating | User measurement — after merge, local Windows, before/after `npm test` twice (one-time manual cleanup of pre-existing orphans first) | count coordinators (`Get-CimInstance ... state-coordinator`) and `%TEMP%\agentview-pty-*` dirs before/after each run; also run `npm run typecheck`/the wrapper subset. The Windows-local suite is NOT expected to be green (pre-existing baseline above); U1 is about residue, not exit codes | 0 new coordinators, 0 new `agentview-pty-*` (or `agentview-del-flow-*`) dirs per run |
|
|
300
|
-
|
|
301
|
-
Automated-first: A1/A5/A6 are `unit`, A2/A3/A4 are `integration` (a real spawn
|
|
302
|
-
is the point; A2 is deliberately the cross-platform one so the cwd fix has a
|
|
303
|
-
CI-observable regression lock, since Linux cannot detect it via `rmSync`), A7/A8
|
|
304
|
-
are the repo's own commands. U1 is the only user measurement: it observes
|
|
305
|
-
process/OS state across runs on the reporter's box and is the direct
|
|
306
|
-
reproduction of the report.
|
|
307
|
-
|
|
308
|
-
## 6. Testability split (design constraint for implementation)
|
|
309
|
-
|
|
310
|
-
- `coordinatorSpawnPlan` is a pure function in `launch.mjs`; `launchCoordinator`
|
|
311
|
-
is its thin spawn shell. A1 tests the pure part; A2 proves the shell's actual
|
|
312
|
-
cwd/argv end to end on every platform (the fake runner writes its own
|
|
313
|
-
`process.cwd()` and `argv[2]`); A3 proves the end-to-end consequence.
|
|
314
|
-
- The root-vanished decision is intentionally *not* extracted into a new module:
|
|
315
|
-
it is a one-line predicate whose correctness is the timing/step-down sequence,
|
|
316
|
-
so A3 (exit on delete) and A4 (no exit while present) are integration tests
|
|
317
|
-
over the real runner. A pure predicate unit test would be tautological.
|
|
318
|
-
Because the step-down must not write inside the root (see §3.2), its
|
|
319
|
-
attribution is the **stderr line**: A3/A4 spawn the runner directly with
|
|
320
|
-
`stdio: [..., "pipe", "pipe"]` instead of going through the detached
|
|
321
|
-
`launchCoordinator` (whose stdio is ignored); A1/A2 already lock the
|
|
322
|
-
`launchCoordinator` spawn plan, so no production-path coverage is lost.
|
|
323
|
-
- `scripts/run-tests.mjs` keeps two pure functions (`parseProcessList`,
|
|
324
|
-
`selectOrphanCoordinators`) and one injectable orchestrator (`runSuite`) apart
|
|
325
|
-
from `main()`'s real `spawn`/`execFileSync`/`process.kill`/timer shell, and is
|
|
326
|
-
import-side-effect free by contract. A5 covers the pure functions; A6 covers
|
|
327
|
-
`runSuite` with fakes (no real process is killed in unit tests) plus the
|
|
328
|
-
import-safety contract.
|
|
329
|
-
- No test may assert "clean" without first asserting the precondition (lease
|
|
330
|
-
file exists / pid alive before the delete; a survivor exists before the kill
|
|
331
|
-
path; the fake runner's file appears before reading it).
|
|
332
|
-
- A2's fake runner lives under the temp root, is tiny, and never binds a socket;
|
|
333
|
-
the test asserts the written file appears (bounded poll), then the values.
|
|
334
|
-
|
|
335
|
-
## 7. Risks / edge cases
|
|
336
|
-
|
|
337
|
-
| Risk | Handling |
|
|
338
|
-
|---|---|
|
|
339
|
-
| Root-missing step-down | writes no diagnostic and no state: `appendLine` recreates the parent chain, so a write would resurrect the deleted root skeleton; the step-down logs one stderr line and `shutdown(0)` stays unblocked. |
|
|
340
|
-
| `statSync` EACCES/EBUSY (transient) | treat as "root present", keep serving; only ENOENT steps down. |
|
|
341
|
-
| Root removed while a command is being journaled | the write may **succeed by recreating the root** (`ensureDir`), so existence-only detection is insufficient; the boot-time dev/ino identity guard detects the replacement on the next tick and steps down, leaving at most a skeleton directory. The client still sees the ambiguous `connection_reset`/`timeout` and must not retry with a fresh commandId (existing contract). |
|
|
342
|
-
| Wrapper kills another checkout's test coordinator | impossible: selection requires this checkout's absolute `runner/state-coordinator.mjs` path. |
|
|
343
|
-
| Wrapper runs while a real board coordinator serves a temp root (user set `AGENT_BOARD_ROOT` to a temp dir) | temp predicate would match; acceptable known limit — such a setup is itself a throwaway. Documented, not guarded. |
|
|
344
|
-
| Enumeration unavailable | warn + fail-open (skip residue check); A6 covers the failed-enumeration branch. |
|
|
345
|
-
| POSIX command line truncation | `ps -e -ww`; A5 covers a long line. |
|
|
346
|
-
| win32 CIM single-object JSON / non-UTF8 output | object-or-array parse; forced UTF-8 in the command prefix; A5 covers both shapes. |
|
|
347
|
-
| `pwsh` launch cost on every test run (~0.5–1 s) | acceptable for a dev/suite gate; POSIX path uses `ps`. |
|
|
348
|
-
| Windows-local full suite has pre-existing failures (AF_UNIX/EACCES `.sock`, AttachConsole, separator expectations) | A7 is a CI acceptance; locally the gate must show 0 survivors and no NEW failure vs `main`. Never loosen the gate (fail-open or skip) to make the local suite green. |
|
|
349
|
-
| Gate exposes a test that never deletes its root | after 3.1 the root is removable, so fix that test's cleanup in the same PR; no exemption list. |
|
|
350
|
-
| Existing orphans predating the fix | not auto-cleaned; wrapper warns with their count on every run; U1 includes one manual cleanup command. |
|
|
351
|
-
| npm test on Node 20 | wrapper uses only `readdirSync`/`spawn`/`process.kill`; no `--test-global-setup` (Node 22+ only), no new deps. |
|
|
352
|
-
| Coverage thresholds after adding the wrapper | `.c8rc.json` excludes the wrapper; A8 proves thresholds still hold. |
|
|
353
|
-
|
|
354
|
-
## 8. Deployment / execution path
|
|
355
|
-
|
|
356
|
-
- **No protocol version bump.** Every newly spawned coordinator runs the new
|
|
357
|
-
`runner/state-coordinator.mjs` from the installed package — the spawn path is
|
|
358
|
-
`opts.runnerScript` (`coordinator-client.mjs:197`), a file-path constant, not a
|
|
359
|
-
protocol-negotiated one. Test/probe roots are freshly created per run, so they
|
|
360
|
-
never collide with a pre-existing old-build instance. On a production root an
|
|
361
|
-
already-running old-build coordinator keeps serving until it is next replaced
|
|
362
|
-
or the process restarts; that is correct, because its root still exists and
|
|
363
|
-
the new behavior is specifically "step down when the root is gone". (A
|
|
364
|
-
protocol bump would force-reelect every user's coordinator on upgrade for no
|
|
365
|
-
gain — explicitly out of scope.)
|
|
366
|
-
- No data migration. Lease/journal formats untouched.
|
|
367
|
-
- Already-leaked processes from older builds stay until manually killed; U1
|
|
368
|
-
includes the one-time cleanup (`Get-CimInstance ... state-coordinator` filtered
|
|
369
|
-
to `%TEMP%\agentview-*`, plus the temp dirs). The new suite gate prevents new
|
|
370
|
-
ones.
|
|
371
|
-
- `npm run verify` (typecheck + perf gate + tests + coverage + pack:dry) stays
|
|
372
|
-
the completion gate; `npm test` now also covers the residue assertion.
|
|
373
|
-
|
|
374
|
-
## 9. Open questions for the design gate
|
|
375
|
-
|
|
376
|
-
1. Approve 3.1 + 3.2 + 3.3 (+ tests), i.e. the issue's preferred "1 + 3" with
|
|
377
|
-
the cwd correction, with idle timeout kept out of scope?
|
|
378
|
-
2. Is the 1 s root check / <=1 s detection latency acceptable in production, or
|
|
379
|
-
should the cadence be a separate (slower) knob?
|
|
380
|
-
3. Is the fail-open behavior when process enumeration is unavailable acceptable
|
|
381
|
-
(warn + skip), or should it fail the run?
|