Files
stack/packages/discord/tests/journal.test.mjs
T
jason.woltjeandClaude Fable 5.1 786e379c49 feat(discord): connector pilot for the Sage seat, reviewed candidate (#1509)
Zero-dependency Discord connector under packages/discord: binding
validation, REST and gateway clients, pi engine adapter, journal with
append-only inbox, outbox, admissions and notices, and a run.lock
ownership record {pid, start, boot} whose identity is checked three ways
and whose cleanup is gated by STOP. CLI check|run|stop|unlock via
scripts/discord.sh; offline suite scripts/test-discord.sh (28 checks,
87 node tests).

Reviewed by rev-code-02 on #1509 over nine rounds; approved exact tree
4e0feb6758c0a7e4a71483912a8e0d3e3ec95aef at comment 26170. Corrections
(1) to (12) recorded in BUILD-LOG. No listener started, no token read,
no Discord write; the live pilot follows this commit per the brief.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-13 01:15:37 -05:00

359 lines
20 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { writeFileSync, existsSync, mkdirSync, rmSync, readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { spawn, spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import {
ensureJournal, writePid, readPid, clearPid, stopTarget, ownerAlive, processStart, lockPath, ownerPath,
unlock, appendNotice, noticeOn, stopRequested, stopPath, bootId, ownerState, validStart, validBoot, pidAlive,
} from "../src/journal.mjs";
import { DiscordError } from "../src/errors.mjs";
import { makeRoot } from "./helpers.mjs";
function journal() {
const dir = join(makeRoot(), "journal");
ensureJournal(dir);
return dir;
}
function publish(dir, rec) {
mkdirSync(lockPath(dir), { recursive: true });
writeFileSync(ownerPath(dir), JSON.stringify(rec) + "\n", { mode: 0o600 });
}
// Spawns n claim workers over dir, releases them together, and resolves with
// their verdicts once all have answered. `finish` releases any winner.
function race(dir, n, tag) {
const worker = fileURLToPath(new URL("../fixtures/claim-worker.mjs", import.meta.url));
const go = join(dir, `go-${tag}`);
const done = join(dir, `done-${tag}`);
const closes = [];
const verdicts = [];
let settle;
const answered = new Promise((resolve) => { settle = resolve; });
for (let i = 0; i < n; i += 1) {
const child = spawn(process.execPath, [worker, dir, go, done], { stdio: ["ignore", "pipe", "pipe"] });
const entry = { pid: child.pid, out: "", err: "", code: null };
child.stdout.on("data", (d) => { entry.out += d; if (entry.out.endsWith("\n")) { verdicts.push(entry); if (verdicts.length === n) settle(); } });
child.stderr.on("data", (d) => { entry.err += d; });
closes.push(new Promise((resolve) => child.on("close", (code) => { entry.code = code; resolve(entry); })));
}
writeFileSync(go, "");
return answered.then(() => ({
winners: verdicts.filter((r) => r.out.trim() === "claimed"),
losers: verdicts.filter((r) => r.out.trim() === "refused"),
stopped: verdicts.filter((r) => r.out.trim() === "stopped"),
verdicts,
async finish() {
writeFileSync(done, "");
const results = await Promise.all(closes);
assert.equal(results.every((r) => r.code === 0), true, JSON.stringify(results));
},
}));
}
test("lock: the claim is exclusive; a second start against a live owner refuses", () => {
const dir = journal();
writePid(dir, process.pid);
const rec = readPid(dir);
assert.equal(rec.pid, process.pid);
assert.equal(rec.start, processStart(process.pid));
assert.equal(rec.boot, bootId());
assert.ok(rec.start !== null && rec.boot !== null, "this host has /proc; start marker and boot id are recorded");
assert.equal(ownerState(rec), "live");
assert.equal(existsSync(join(lockPath(dir), "owner.json.tmp")), false, "the record is published by rename");
assert.throws(() => writePid(dir, process.pid), (err) => err instanceof DiscordError && /another connector is running/.test(err.message), "a live matching owner refuses even a repeat claim");
assert.equal(stopTarget(dir), process.pid);
assert.throws(() => unlock(dir), /refusing to unlock: the connector is running/);
assert.equal(stopRequested(dir), true, "unlock wrote STOP before inspecting");
rmSync(stopPath(dir));
clearPid(dir, process.pid + 1);
assert.equal(existsSync(lockPath(dir)), true, "a different pid cannot clear the lock");
clearPid(dir, process.pid);
assert.equal(existsSync(lockPath(dir)), false);
});
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() };
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));
assert.deepEqual(readPid(dir), dead, "run did not touch the stale lock");
assert.deepEqual(unlock(dir), dead);
assert.equal(existsSync(lockPath(dir)), false);
assert.equal(unlock(dir), false, "nothing to unlock");
assert.equal(stopRequested(dir), true, "STOP stays after unlock");
assert.throws(() => writePid(dir, process.pid), /STOP is present/);
assert.equal(existsSync(lockPath(dir)), false, "a claim that meets STOP releases itself");
rmSync(stopPath(dir));
// Reused pid: our own live pid but a start marker that does not match.
const otherStart = String(BigInt(processStart(process.pid)) + 1n);
publish(dir, { pid: process.pid, start: otherStart, boot: bootId() });
assert.equal(ownerState(readPid(dir)), "mismatch");
assert.equal(ownerAlive(readPid(dir)), false);
assert.equal(stopTarget(dir), null, "stop must not signal a process whose identity does not match");
assert.throws(() => writePid(dir, process.pid), /which is gone or is a different process/);
assert.equal(readPid(dir).start, otherStart, "still untouched");
unlock(dir);
rmSync(stopPath(dir));
// Same pid and start ticks but a different boot id: a process from another
// boot. Never signaled, refuses run, unlock clears it.
publish(dir, { pid: process.pid, start: processStart(process.pid), boot: "00000000-0000-0000-0000-000000000000" });
assert.equal(ownerState(readPid(dir)), "mismatch");
assert.equal(stopTarget(dir), null, "a different boot is never a signal target");
assert.throws(() => writePid(dir, process.pid), /which is gone or is a different process/);
assert.deepEqual(readPid(dir).boot, "00000000-0000-0000-0000-000000000000", "still untouched");
unlock(dir);
rmSync(stopPath(dir));
// A record without a start marker or boot id is never a signal target;
// with a live pid it is unknown (refuses everything), with a dead pid it is dead.
publish(dir, { pid: process.pid, boot: bootId() });
assert.equal(ownerState(readPid(dir)), "unknown");
assert.equal(stopTarget(dir), null);
assert.throws(() => writePid(dir, process.pid), /cannot be verified/);
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" });
assert.equal(ownerState(readPid(dir)), "dead");
unlock(dir);
rmSync(stopPath(dir));
writePid(dir, process.pid);
assert.equal(readPid(dir).start, processStart(process.pid));
clearPid(dir, process.pid);
});
test("lock: an incomplete claim (directory without owner record) is busy and refuses run; unlock clears it", () => {
const dir = journal();
mkdirSync(lockPath(dir));
assert.throws(() => writePid(dir, process.pid), (err) => err instanceof DiscordError && /without an owner record/.test(err.message));
assert.equal(existsSync(lockPath(dir)), true, "the in-progress claim was left alone");
assert.equal(readPid(dir), null);
assert.equal(stopTarget(dir), null);
assert.equal(unlock(dir), null);
assert.equal(existsSync(lockPath(dir)), false);
rmSync(stopPath(dir));
writePid(dir, process.pid);
clearPid(dir, process.pid);
});
test("lock: an owner record that exists but cannot be read is invalid: never signaled, never removed, never claimed over", () => {
const dir = journal();
for (const content of ["12345\n", "{\"pid\":\"x\"}\n", "null\n", ""]) {
mkdirSync(lockPath(dir), { recursive: true });
writeFileSync(ownerPath(dir), content, { mode: 0o600 });
assert.equal(ownerState(readPid(dir)), "invalid", JSON.stringify(content));
assert.equal(stopTarget(dir), null);
assert.throws(() => writePid(dir, process.pid), /cannot be read/);
assert.throws(() => unlock(dir), /cannot be read; nothing removed/);
assert.equal(readFileSync(ownerPath(dir), "utf8"), content, "byte-identical");
clearPid(dir, process.pid);
assert.equal(existsSync(ownerPath(dir)), true, "clearPid never acts on an unreadable record");
rmSync(stopPath(dir));
rmSync(lockPath(dir), { recursive: true });
}
writePid(dir, process.pid);
clearPid(dir, process.pid);
});
// Spawn a live child that publishes an owner record it never clears, and
// prove that while it lives nothing signals it, removes its lock, or claims
// over it; once it exits, unlock clears the lock and one claim succeeds.
async function liveOwnerRefusesEverything(startArg, bootArg, label) {
const dir = journal();
const worker = fileURLToPath(new URL("../fixtures/legacy-owner-worker.mjs", import.meta.url));
const done = join(dir, "done");
const child = spawn(process.execPath, [worker, dir, done, startArg, bootArg], { stdio: ["ignore", "pipe", "inherit"] });
const exited = new Promise((resolve) => child.on("close", resolve));
await new Promise((resolve) => child.stdout.on("data", (d) => { if (String(d).includes("legacy-published")) resolve(); }));
const before = readFileSync(ownerPath(dir), "utf8");
const rec = readPid(dir);
assert.equal(rec.pid, child.pid, label);
assert.equal(ownerState(rec), "unknown", label);
assert.equal(stopTarget(dir), null, `${label}: no signal target`);
assert.throws(() => unlock(dir), /pid \d+ is alive and its identity cannot be verified; nothing removed/, label);
assert.equal(readFileSync(ownerPath(dir), "utf8"), before, `${label}: byte-identical lock`);
assert.equal(stopRequested(dir), true, label);
rmSync(stopPath(dir));
assert.throws(() => writePid(dir, process.pid), /cannot be verified/, `${label}: no second owner while the process lives`);
assert.equal(readFileSync(ownerPath(dir), "utf8"), before, label);
assert.equal(pidAlive(child.pid), true, `${label}: the original process is still alive`);
writeFileSync(done, "");
assert.equal(await exited, 0, label);
assert.equal(ownerState(readPid(dir)), "dead", label);
assert.deepEqual(unlock(dir), rec, label);
assert.equal(existsSync(lockPath(dir)), false, label);
rmSync(stopPath(dir));
writePid(dir, process.pid);
assert.equal(stopTarget(dir), process.pid, `${label}: one owner after recovery`);
clearPid(dir, process.pid);
return rec;
}
test("lock: legacy upgrade; a live connector holding a {pid, start} record is unknown, unlock refuses and nothing changes; after it exits, unlock clears it", async () => {
const rec = await liveOwnerRefusesEverything("real", "-", "legacy");
assert.equal(rec.boot, null, "the round-five record carries no boot id");
assert.notEqual(rec.start, null);
});
test("lock: a live pid whose record carries a malformed or noncanonical start or boot string is unknown, not a mismatch; nothing signals, removes, or claims over it", async () => {
const cases = [
["", "real", "empty start"],
["not-a-tick", "real", "nondecimal start"],
["12abc", "real", "mixed start"],
["real", "", "empty boot"],
["real", "not-a-uuid", "malformed boot"],
["real", "97DF3044-E52D-45A3-810D-C3F2F54634F4", "uppercase boot"],
["000", "real", "leading-zero start"],
["0", "real", "zero start"],
["99999999999999999999", "real", "start above 2^64-1"],
];
for (const [startArg, bootArg, label] of cases) {
const rec = await liveOwnerRefusesEverything(startArg, bootArg, label);
assert.ok(rec.start === null || rec.boot === null, `${label}: the malformed value reads as absent`);
}
});
test("lock: identity syntax; only canonical unsigned decimal start ticks and lowercase boot uuids are identities", () => {
for (const v of ["1", "1234567890", "18446744073709551615"]) assert.equal(validStart(v), true, v);
for (const v of ["", " 1", "1 ", "-1", "1.5", "abc", "1e3", 1, null, undefined, "0", "00", "01", "000", "18446744073709551616", "99999999999999999999", "9".repeat(21)]) assert.equal(validStart(v), false, String(v));
assert.equal(validBoot("97df3044-e52d-45a3-810d-c3f2f54634f4"), true);
for (const v of ["", "97df3044", "97DF3044-E52D-45A3-810D-C3F2F54634F4", "97df3044-e52d-45a3-810d-c3f2f54634f4\n", "g7df3044-e52d-45a3-810d-c3f2f54634f4", null, 5]) assert.equal(validBoot(v), false, String(v));
assert.equal(validStart(processStart(process.pid)), true, "this process's real start is valid");
assert.equal(validBoot(bootId()), true, "this host's real boot id is valid");
});
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));
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/);
assert.equal(existsSync(lockPath(dir)), false);
});
test("lock: a live pid whose identity cannot be read right now is unknown: never signaled, never removed, never claimed over", () => {
const dir = journal();
writePid(dir, process.pid);
const before = JSON.stringify(readPid(dir));
const unreadable = () => ({ start: null, boot: null });
assert.equal(ownerState(readPid(dir), { identity: unreadable }), "unknown");
assert.equal(stopTarget(dir, { identity: unreadable }), null, "no signal target");
assert.throws(() => unlock(dir, { identity: unreadable }), /identity cannot be verified; nothing removed/);
assert.equal(JSON.stringify(readPid(dir)), before, "the lock is unchanged");
assert.equal(stopRequested(dir), true);
rmSync(stopPath(dir));
assert.throws(() => writePid(dir, process.pid, { identity: unreadable }), /start time or the boot id/, "a claimant without identity cannot claim");
const halfBlind = (pid) => ({ start: processStart(pid), boot: null });
assert.throws(() => writePid(dir, process.pid + 1, { identity: halfBlind }), /start time or the boot id/);
// The claimant's own identity is readable (fabricated; the refusal happens before anything is written).
const claimantOk = (pid) => (pid === process.pid ? { start: null, boot: null } : { start: "1", boot: bootId() });
assert.throws(() => writePid(dir, process.pid + 1, { identity: claimantOk }), /alive but whose identity cannot be verified/, "a healthy claimant still refuses over an unknown owner");
assert.equal(JSON.stringify(readPid(dir)), before, "still unchanged");
// Identity readable again: one owner, and it is the original.
assert.equal(stopTarget(dir), process.pid);
assert.throws(() => unlock(dir), /connector is running/);
rmSync(stopPath(dir));
clearPid(dir, process.pid);
assert.equal(existsSync(lockPath(dir)), false);
});
test("lock: four processes racing for the same binding; exactly one claims it and the others refuse", async () => {
const dir = journal();
const r = await race(dir, 4, "a");
assert.equal(r.winners.length, 1, JSON.stringify(r.verdicts));
assert.equal(r.losers.length, 3, JSON.stringify(r.verdicts));
const rec = readPid(dir);
assert.equal(rec.pid, r.winners[0].pid, "the published owner is the winner");
assert.equal(stopTarget(dir), r.winners[0].pid, "the live winner is the only stop target");
assert.throws(() => writePid(dir, process.pid), /another connector is running/, "a fifth start refuses while the winner holds the lock");
await r.finish();
assert.equal(existsSync(lockPath(dir)), false, "the winner released the lock on exit");
});
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() };
publish(dir, stale);
// Several starts race over the stale lock: none may reclaim it.
const r1 = await race(dir, 4, "stale");
assert.equal(r1.winners.length, 0, JSON.stringify(r1.verdicts));
assert.equal(r1.losers.length, 4);
assert.deepEqual(readPid(dir), stale, "the stale lock is exactly as it was");
await r1.finish();
// Operator cleanup, once. A second unlock finds nothing.
assert.deepEqual(unlock(dir), stale);
assert.equal(unlock(dir), false);
// While STOP stands, a race over the cleared binding produces no owner.
const r15 = await race(dir, 3, "gated");
assert.equal(r15.winners.length, 0, JSON.stringify(r15.verdicts));
assert.ok(r15.stopped.length >= 1, "at least the claim that published met STOP and released itself");
assert.equal(r15.stopped.length + r15.losers.length, 3, "the rest refused on the transient lock; none holds");
assert.equal(existsSync(lockPath(dir)), false, "no residue");
await r15.finish();
rmSync(stopPath(dir));
// Now the same contenders race for the cleared binding: one owner.
const r2 = await race(dir, 4, "fresh");
assert.equal(r2.winners.length, 1, JSON.stringify(r2.verdicts));
assert.equal(r2.losers.length, 3);
const live = readPid(dir);
assert.equal(live.pid, r2.winners[0].pid);
assert.equal(stopTarget(dir), live.pid);
assert.throws(() => unlock(dir), /connector is running/, "unlock never removes a live owner");
await r2.finish();
assert.equal(existsSync(lockPath(dir)), false);
});
test("lock: four-party schedule; claims landing inside an unlock's gap never survive, one unlock leaves no owner and no residue", () => {
// Reviewer's schedule (#1509 comment 26132): unlock inspects stale S; C
// claims in the gap after inspection; unlock acts; D claims in the gap
// too. Required: no live owner is displaced, no residue, at most one owner.
const dir = journal();
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() };
publish(dir, stale);
const claims = [];
const claim = (tag) => {
const r = spawnSync(process.execPath, [worker, dir, go, join(dir, `done-${tag}`)], { encoding: "utf8", timeout: 10_000 });
claims.push({ tag, out: r.stdout.trim(), err: r.stderr.trim(), status: r.status });
return r.stdout.trim();
};
const beforeRemove = () => {
// C: the stale lock is still there, so C refuses on the record.
assert.equal(claim("C"), "refused");
// Simulate C's record having landed anyway (as if S vanished first), then D.
rmSync(lockPath(dir), { recursive: true, force: true });
assert.equal(claim("D"), "stopped", "D published, met STOP, released itself");
assert.equal(existsSync(lockPath(dir)), false, "D left nothing");
// A record that lands right before removal is also not an owner: STOP
// was written before it could have published.
publish(dir, { pid: process.pid, start: processStart(process.pid), boot: bootId() });
};
const cleared = unlock(dir, { beforeRemove });
assert.deepEqual(cleared, stale, "unlock reports the record it inspected");
assert.equal(existsSync(lockPath(dir)), false, "no lock at the canonical path");
assert.equal(readdirSync(dir).filter((f) => f.startsWith("run.lock")).length, 0, "no residue");
assert.equal(stopTarget(dir), null, "no live owner");
assert.equal(claims.every((c) => c.status === 0), true, JSON.stringify(claims));
assert.equal(stopRequested(dir), true, "STOP stands until the operator removes it");
rmSync(stopPath(dir));
writePid(dir, process.pid);
assert.equal(stopTarget(dir), process.pid, "after STOP is removed, one clean claim");
clearPid(dir, process.pid);
});
test("notices: a kind is recorded per UTC day and found again", () => {
const dir = journal();
assert.equal(noticeOn(dir, "ceiling", "2026-09-13"), false);
appendNotice(dir, { kind: "ceiling", date: "2026-09-13", at: "2026-09-13T10:00:00.000Z" });
assert.equal(noticeOn(dir, "ceiling", "2026-09-13"), true);
assert.equal(noticeOn(dir, "ceiling", "2026-09-14"), false);
assert.equal(noticeOn(dir, "other", "2026-09-13"), false);
assert.throws(() => appendNotice(dir, { kind: "ceiling" }), DiscordError);
});