@ouro.bot/cli 0.1.0-alpha.265 → 0.1.0-alpha.266
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.json +6 -0
- package/dist/heart/daemon/daemon.js +40 -0
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.266",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(daemon): vitest guard for production pidfile (~/.ouro-cli/daemon.pids). The pidfile path is hardcoded with no DI seam, so when a test creates a real OuroDaemon instance and calls start(), the daemon's killOrphanProcesses() reads the REAL pidfile, ps-verifies the PIDs, and SIGTERMs the production daemon's PIDs. Verified live: alpha.265 daemon (PID 64988) was killed 93s after startup by `npx vitest run` invoking daemon.start() in 6 different test files. SIGTERM forensics added in alpha.265 captured the death (parentPid=1, parentCommand=/sbin/launchd) but the killer hint was misleading — the real culprit was the production-pidfile leak. Fix: killOrphanProcesses() and writePidfile() now short-circuit under vitest with warn-level nerves events. Tests that need to verify these functions' behavior continue to use the extracted pure helpers (parseOrphanPidsFromPs, filterPidfilePidsToActualOrphans). New unit tests verify the pidfile is unchanged after no-op calls. Same defense-in-depth pattern as the alpha.265 socket-client hardening — production-side state being touched from tests is now physically impossible from a vitest worker."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.265",
|
|
6
12
|
"changes": [
|
|
@@ -64,6 +64,26 @@ const outlook_types_1 = require("../outlook/outlook-types");
|
|
|
64
64
|
const outlook_read_1 = require("../outlook/outlook-read");
|
|
65
65
|
const outlook_view_1 = require("../outlook/outlook-view");
|
|
66
66
|
const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
|
|
67
|
+
/**
|
|
68
|
+
* Defense-in-depth: detect if we're running under vitest. The pidfile lives
|
|
69
|
+
* at a hardcoded path under the user's real ~/.ouro-cli/ — there's no DI
|
|
70
|
+
* seam to redirect it. So when a test creates a real OuroDaemon and calls
|
|
71
|
+
* start(), the daemon's killOrphanProcesses() reads the REAL pidfile,
|
|
72
|
+
* ps-verifies the PIDs, and SIGTERMs the production daemon. We saw this
|
|
73
|
+
* cause an outage on 2026-04-08 (alpha.265 daemon killed 93s after startup
|
|
74
|
+
* by a vitest test that called daemon.start()).
|
|
75
|
+
*
|
|
76
|
+
* Both killOrphanProcesses() and writePidfile() short-circuit under vitest
|
|
77
|
+
* to make the production pidfile sacred. Tests that need to verify these
|
|
78
|
+
* functions' behavior should use the extracted pure helpers
|
|
79
|
+
* (parseOrphanPidsFromPs, filterPidfilePidsToActualOrphans).
|
|
80
|
+
*/
|
|
81
|
+
function isVitestProcess() {
|
|
82
|
+
/* v8 ignore next -- defensive: process and process.argv always exist in node @preserve */
|
|
83
|
+
if (typeof process === "undefined" || !Array.isArray(process.argv))
|
|
84
|
+
return false;
|
|
85
|
+
return process.argv.some((arg) => typeof arg === "string" && arg.includes("vitest"));
|
|
86
|
+
}
|
|
67
87
|
/**
|
|
68
88
|
* Scan `ps -eo pid,ppid,command` output for daemon-owned entry points whose
|
|
69
89
|
* parent has died (PPID reparented to init/PID 1). Returns the list of PIDs
|
|
@@ -170,6 +190,16 @@ function runPsCheck(pids) {
|
|
|
170
190
|
*/
|
|
171
191
|
/* v8 ignore start -- process lifecycle: uses kill/ps, tested via deployment @preserve */
|
|
172
192
|
function killOrphanProcesses() {
|
|
193
|
+
if (isVitestProcess()) {
|
|
194
|
+
(0, runtime_1.emitNervesEvent)({
|
|
195
|
+
level: "warn",
|
|
196
|
+
component: "daemon",
|
|
197
|
+
event: "daemon.orphan_cleanup_test_blocked",
|
|
198
|
+
message: "blocked killOrphanProcesses from touching real pidfile under vitest",
|
|
199
|
+
meta: { pidfilePath: PIDFILE_PATH },
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
173
203
|
try {
|
|
174
204
|
let pidsToKill = [];
|
|
175
205
|
// Primary: read pidfile from previous daemon
|
|
@@ -223,6 +253,16 @@ function killOrphanProcesses() {
|
|
|
223
253
|
* Called after all agents and senses are spawned.
|
|
224
254
|
*/
|
|
225
255
|
function writePidfile(extraPids = []) {
|
|
256
|
+
if (isVitestProcess()) {
|
|
257
|
+
(0, runtime_1.emitNervesEvent)({
|
|
258
|
+
level: "warn",
|
|
259
|
+
component: "daemon",
|
|
260
|
+
event: "daemon.write_pidfile_test_blocked",
|
|
261
|
+
message: "blocked writePidfile from clobbering real pidfile under vitest",
|
|
262
|
+
meta: { pidfilePath: PIDFILE_PATH, attemptedPids: extraPids.length },
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
226
266
|
try {
|
|
227
267
|
const pids = [process.pid, ...extraPids].filter(Boolean);
|
|
228
268
|
fs.mkdirSync(path.dirname(PIDFILE_PATH), { recursive: true });
|