feat(cli): S4 follow-up, refusal backoff and tracker boot (row 45, #1527)

Rocko's round 2 candidate, packet agents/rocko/work/s4-follow-up/
(build.patch c8cec070, candidate manifest 5b067a9d, 8/8 OK).

- Definite DM refusals wait the full 30-minute cap, counted from the
  journal's last refusal, so five refusals span about two hours before
  gave-up (lead decision 73). Unknown outcomes keep doubling.
- Broker close sends at host.mjs:144/150/184/188 pass a callback, which
  closes the EPIPE window both reviewers found in round 1.
- README documents manual recovery for an open decision.
- trackers-boot test, X9, X14, and Darkwing's round 1 notes 1-4.
- The append type check stays out; Rocko's reason holds (both reviewers
  agree).

Reviews: Darkwing approve (comment 26884), Filbert approve (26886).
Landing gate on b13fef4c plus the patch: every node suite and every
scripts/test-*.sh green, test-task 98/0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-09 09:27:34 -05:00
co-authored by Claude Opus 5.5
parent f4714aa03b
commit 9cdb6d82e3
8 changed files with 635 additions and 56 deletions
+16 -6
View File
@@ -17,6 +17,15 @@ function journal() {
return dir;
}
// A pid that is provably dead: a child spawned and reaped here, then
// confirmed gone with kill(pid, 0). A fixed number can belong to a live process.
function deadPid() {
for (;;) {
const { pid } = spawnSync(process.execPath, ["-e", ""], { stdio: "ignore" });
if (Number.isSafeInteger(pid) && !pidAlive(pid)) return pid;
}
}
function publish(dir, rec) {
mkdirSync(lockPath(dir), { recursive: true });
writeFileSync(ownerPath(dir), JSON.stringify(rec) + "\n", { mode: 0o600 });
@@ -76,7 +85,7 @@ test("lock: the claim is exclusive; a second start against a live owner refuses"
test("lock: a stale lock (dead owner, reused pid, or record without start) refuses run and is never signaled; only unlock clears it", () => {
const dir = journal();
const dead = { pid: 2 ** 22 - 7, start: "1", boot: bootId() };
const dead = { pid: deadPid(), start: "1", boot: bootId() };
publish(dir, dead);
assert.equal(stopTarget(dir), null);
assert.throws(() => writePid(dir, process.pid), (err) => err instanceof DiscordError && /which is gone/.test(err.message) && /unlock/.test(err.message));
@@ -116,7 +125,7 @@ test("lock: a stale lock (dead owner, reused pid, or record without start) refus
assert.throws(() => unlock(dir), /cannot be verified; nothing removed/);
rmSync(stopPath(dir));
rmSync(lockPath(dir), { recursive: true });
publish(dir, { pid: 2 ** 22 - 7, start: "1" });
publish(dir, { pid: deadPid(), start: "1" });
assert.equal(ownerState(readPid(dir)), "dead");
unlock(dir);
rmSync(stopPath(dir));
@@ -227,8 +236,9 @@ test("lock: identity syntax; only canonical unsigned decimal start ticks and low
test("lock: a process whose start marker or boot id cannot be read refuses to claim", () => {
const dir = journal();
assert.equal(processStart(2 ** 22 - 7), null);
assert.throws(() => writePid(dir, 2 ** 22 - 7), (err) => err instanceof DiscordError && /start time or the boot id/.test(err.message));
const gone = deadPid();
assert.equal(processStart(gone), null);
assert.throws(() => writePid(dir, gone), (err) => err instanceof DiscordError && /start time or the boot id/.test(err.message));
assert.equal(existsSync(lockPath(dir)), false, "nothing was left behind");
const noBoot = (pid) => ({ start: processStart(pid), boot: null });
assert.throws(() => writePid(dir, process.pid, { identity: noBoot }), /start time or the boot id/);
@@ -276,7 +286,7 @@ test("lock: four processes racing for the same binding; exactly one claims it an
test("lock: stale handoff; concurrent starts over a stale lock all refuse, nothing reclaims, one unlock then exactly one live owner", async () => {
const dir = journal();
const stale = { pid: 2 ** 22 - 7, start: "1", boot: bootId() };
const stale = { pid: deadPid(), start: "1", boot: bootId() };
publish(dir, stale);
// Several starts race over the stale lock: none may reclaim it.
const r1 = await race(dir, 4, "stale");
@@ -315,7 +325,7 @@ test("lock: four-party schedule; claims landing inside an unlock's gap never sur
const worker = fileURLToPath(new URL("../fixtures/claim-worker.mjs", import.meta.url));
const go = join(dir, "go");
writeFileSync(go, "");
const stale = { pid: 2 ** 22 - 7, start: "1", boot: bootId() };
const stale = { pid: deadPid(), start: "1", boot: bootId() };
publish(dir, stale);
const claims = [];
const claim = (tag) => {