Files
stack/agents/darkwing/work/cohort-k1/scope-receipt.mjs
T
jason.woltjeandClaude Opus 5.5 7478ee2c02 docs(conversation): row 46 cohort K1/K3/K10 diagnosis, receipts and candidate packet (darkwing)
The ignoreTerm tool child was TERMed before Node installed its handler
when the claim store sits on a fast disk. Candidate fixes the fixture;
the patch itself is not committed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 08:11:48 -05:00

62 lines
3.2 KiB
JavaScript

// Row 46 receipt probe. Runs the fixture's ignoreTerm child inside a
// delegated systemd user scope, sends it SIGTERM, and records the scope's
// `systemctl --user show`, `cgroup.procs` before and after TERM, and the
// child's exit code and signal.
//
// node scope-receipt.mjs <mode> <delayMs> <runs>
//
// mode `race`: TERM goes `delayMs` after spawn, as the fixture does today.
// mode `ready`: TERM goes `delayMs` after the child reports its handler is
// installed, as the fixed fixture does.
// The outer process launches one scope per run; the inner process
// (`--inner`) is the scope's main process and prints one JSON line.
import { spawn, spawnSync } from "node:child_process";
import { readFileSync } from "node:fs";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// The fixture's child code for { ignoreTerm: true } with no pidLog
// (packages/conversation/tests/fake-pi.mjs, spawnChild), plus a readiness
// byte on stdout in `ready` mode.
const childCode = (ready) =>
"const note=()=>{};process.on('SIGTERM',()=>note('term'));" +
(ready ? "process.stdout.write('r');" : "") +
"setInterval(()=>{},1e9)";
const procs = (cg) => readFileSync(`/sys/fs/cgroup${cg}/cgroup.procs`, "utf8").split("\n").filter(Boolean).map(Number);
async function inner(mode, delayMs, unit) {
const cg = readFileSync("/proc/self/cgroup", "utf8").trim().split("::")[1];
const ready = mode === "ready";
const t0 = performance.now();
const child = spawn(process.execPath, ["-e", childCode(ready)], { stdio: ["ignore", ready ? "pipe" : "ignore", "ignore"] });
const exited = new Promise((r) => child.on("exit", (code, signal) => r({ code, signal, atMs: Math.round(performance.now() - t0) })));
let readyMs = null;
if (ready) {
await new Promise((r) => child.stdout.once("data", r));
readyMs = Math.round(performance.now() - t0);
}
await sleep(delayMs);
const show = spawnSync("systemctl", ["--user", "show", "-p", "Id,LoadState,ActiveState,SubState,InvocationID,ControlGroup,Delegate", `${unit}.scope`], { encoding: "utf8" }).stdout.trim().split("\n");
const before = procs(cg);
const termMs = Math.round(performance.now() - t0);
child.kill("SIGTERM");
const exit = await Promise.race([exited, sleep(500).then(() => null)]);
const after = procs(cg);
if (!exit) child.kill("SIGKILL");
console.log(JSON.stringify({ mode, delayMs, readyMs, termMs, child: child.pid, self: process.pid, show, before, after, childInBefore: before.includes(child.pid), childInAfter: after.includes(child.pid), exit: exit ?? "alive 500 ms after TERM" }));
}
async function outer(mode, delayMs, runs) {
for (let i = 0; i < runs; i++) {
const unit = `dw-r46-${mode}-${delayMs}-${process.pid}-${i}`;
const r = spawnSync("systemd-run", ["--user", "--scope", "--quiet", "-p", "Delegate=yes", `--unit=${unit}`, "--", process.execPath, import.meta.filename, "--inner", mode, String(delayMs), unit], { encoding: "utf8" });
process.stdout.write(r.stdout || `{"unit":"${unit}","status":${r.status},"stderr":${JSON.stringify(r.stderr)}}\n`);
}
}
const a = process.argv.slice(2);
if (a[0] === "--inner") await inner(a[1], Number(a[2]), a[3]);
else await outer(a[0], Number(a[1]), Number(a[2] ?? 5));