Dewey's round 3 candidate, manifest
agents/dewey/work/queue-40/candidate-manifest-r3.sha256 (d0aa0ded,
27 files, checked OK in the canonical tree).
- WebUI inbox, tasks, agents and trail views, read-only over /api/bus.
The README says the bus proof ends at the Console process.
- CHAT-03 seal: the engine command is fixed, the engine environment is
explicit, SEAL_FLAGS has --no-approve, escalating is cleared on throw.
- Terminal input typed after Ctrl-T or Ctrl-O is held. Only the run whose
own parse set held drains it (T1), and #run catches errors per action.
- DEFERRED keeps N2 and moves F2 to done, citing T1.
Reviews: Filbert approve (comment 27011, rev 260), Darkwing approve
(27013, rev 264). Landing gate on 8cad7722 plus the candidate: webui 22,
conversation 161, control-board 124, every scripts/test-*.sh green,
test-task 98/0. Mutant Mr survives; its flows test is the first
follow-up row.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
89 lines
4.1 KiB
JavaScript
89 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
||
// A controller in its own process, for the crash fixtures (#1507: W5, W6,
|
||
// W12–W15, W20, H18, H21–H23, K-series). A test can't kill the controller it
|
||
// runs in, and the claim's owner check sees the test's own pid as live, so
|
||
// these fixtures run the controller here.
|
||
//
|
||
// argv[2] is JSON: { fx, launcher: "pgroup" | "scope", dieAt, holdAt,
|
||
// timeouts, host, fakeEnv, verifier }. Lines on stdout are JSON:
|
||
// { ready, ... } after start, { held: name } at a held barrier,
|
||
// { dying: name } just before a SIGKILL at `dieAt`. Lines on stdin:
|
||
// "go" releases a held barrier; "close" closes and exits; "kill-engine"
|
||
// closes with the engine killed; "launch <eligibility>" launches after a
|
||
// recover; "evidence" and "proof" print the controller's evidence and its
|
||
// verified cohort proof.
|
||
|
||
import { createInterface } from "node:readline";
|
||
import { join } from "node:path";
|
||
import { Controller, TEST_ENGINE } from "../src/controller.mjs";
|
||
import { AUTHORITY, PgroupLauncher, ScopeLauncher, systemdUnits } from "../src/cohort.mjs";
|
||
import { FixtureVerifier } from "../src/records.mjs";
|
||
import { defaultHost } from "../src/claim.mjs";
|
||
|
||
const cfg = JSON.parse(process.argv[2]);
|
||
const out = (v) => process.stdout.write(JSON.stringify(v) + "\n");
|
||
const dieAt = new Map(Object.entries(cfg.dieAt ?? {}));
|
||
const holdAt = new Set(cfg.holdAt ?? []);
|
||
let release = null;
|
||
|
||
// The test's stdin pipe is line-oriented control, not engine input.
|
||
const rl = createInterface({ input: process.stdin });
|
||
let ctrl = null;
|
||
rl.on("line", async (line) => {
|
||
if (line === "go" && release) {
|
||
const r = release;
|
||
release = null;
|
||
r();
|
||
} else if (line === "close" || line === "kill-engine") {
|
||
await ctrl?.close({ killEngine: line === "kill-engine" });
|
||
out({ closed: true });
|
||
process.exit(0);
|
||
} else if (line === "evidence") out({ evidence: ctrl?.evidence ?? null, binding: ctrl?.binding ?? null });
|
||
else if (line === "proof") out({ proof: ctrl?.stoppedProof?.proof ?? null });
|
||
else if (line.startsWith("launch ")) {
|
||
// The launcher's call after `recover` (Q15), never a client command.
|
||
try {
|
||
const r = await ctrl.launch(line.slice(7));
|
||
out({ launched: true, binding: r.binding, incarnation: ctrl.incarnation });
|
||
} catch (err) {
|
||
out({ launched: false, error: String(err.code ?? "error"), message: String(err.message) });
|
||
}
|
||
}
|
||
});
|
||
|
||
// `dieAt` maps a barrier name to the occurrence to die at (1 = first).
|
||
const seen = new Map();
|
||
async function barrier(name, detail) {
|
||
const n = (seen.get(name) ?? 0) + 1;
|
||
seen.set(name, n);
|
||
if (cfg.trace) out({ barrier: name, n });
|
||
if (dieAt.get(name) === n) {
|
||
out({ dying: name, n, detail });
|
||
process.kill(process.pid, "SIGKILL");
|
||
await new Promise(() => {});
|
||
}
|
||
if (holdAt.has(`${name}#${n}`) || (n === 1 && holdAt.has(name))) {
|
||
out({ held: name, n, detail });
|
||
await new Promise((r) => (release = r));
|
||
}
|
||
}
|
||
|
||
const host = cfg.host ? { machineId: () => cfg.host.machineId ?? defaultHost.machineId(), bootId: () => cfg.host.bootId ?? defaultHost.bootId() } : undefined;
|
||
const fx = cfg.fx;
|
||
const fakePi = join(import.meta.dirname, "fake-pi.mjs");
|
||
try {
|
||
ctrl = new Controller({
|
||
fixtureRoot: fx.base, claimRoot: fx.claimRoot, socketDir: cfg.socketDir ?? fx.socketDir, sessionFile: fx.sessionFile, seat: fx.seat,
|
||
launcher: cfg.launcher === "scope" ? new ScopeLauncher() : new PgroupLauncher(),
|
||
engine: { cwd: fx.proj }, [TEST_ENGINE]: { command: process.execPath, preArgs: [fakePi], env: { ...process.env, ...(cfg.fakeEnv ?? {}) } },
|
||
verifier: cfg.verifier === false ? null : new FixtureVerifier({ authorities: [AUTHORITY] }),
|
||
units: cfg.units === "absent" ? { lookup: async () => ({ state: "absent" }) } : systemdUnits,
|
||
host, barrier, timeouts: cfg.timeouts,
|
||
});
|
||
const started = await ctrl.start();
|
||
out({ ready: true, started, socketPath: ctrl.socketPath, incarnation: ctrl.incarnation, pid: process.pid, binding: ctrl.binding, claim: ctrl.claim ? { claimId: ctrl.claim.claimId, record: ctrl.claim.record } : null });
|
||
} catch (err) {
|
||
out({ error: String(err.code ?? "error"), message: String(err.message) });
|
||
process.exit(3);
|
||
}
|