Row 137 round 4, Darkwing's candidate (agents/darkwing/work/queue-137/r4/, build.patch 0cabe175, manifest 548ee477, 31 paths, base63c82f1c4). Filbert approved round 4 (comment 28809) and Dewey approved it (28816) after round 3 changes (28801, 28805). Darkwing's round 4 ran 9/9 mutant kills, bus 317, seat 150, land-work 86. Round 5 is a rebase only (agents/sage/work/queue-137-r5/, build.patch d6cbaaa8, manifest f73598fe). Row 150 changed docs/TOOLS.md, broker.mjs and cli/README.md under it. The round 4 patch applied unchanged onto435d1c616, and with index and @@ lines removed the two patches are byte-identical. Filbert checked the interdiff (comment 28818). A landing moves refs/heads/main in <dataRoot>/working/<business>/ <project>.git, never the project root or canonical refactor. The seed now fetches the root as refs/heads/root and fast-forwards main only when it is an ancestor, so a landed main survives the next push. Only a cross-role merge decision lands. Its squash names the real channel: "via the CLI" for the human, and "as its arbiter, via the broker" for an arbiter's decision.resolve. Every other class refuses not-authorized before the human check. This carries row 139's two items. REQ-LAND-1: a gated merge decision never lands. REQ-DEC-2: the host land op accepts an arbiter-resolved merge. land-work's read.mjs stays human-only, and land-work.sh still accepts only a human CLI resolution. Sage's landing gate ran on435d1c616plus the candidate: 25 steps, all rc 0 (15 packages, 10 script suites), 20:51:00Z to 20:55:34Z, network sage-r137_default removed. Cherry-picked onto5d98fb194, which added only a docs/SESSIONS.md line. Co-Authored-By: Claude Opus 5.5 <[email protected]>
111 lines
4.2 KiB
JavaScript
111 lines
4.2 KiB
JavaScript
// Started with fork() by the trusted host; boot refs/capabilities travel over IPC,
|
|
// never command arguments, stdout or service-token-bearing environment variables.
|
|
import { startBroker } from './runtime.mjs';
|
|
import { BusError } from './broker.mjs';
|
|
const LAUNCH_OPS = new Set([
|
|
'identity',
|
|
'authorizeLaunch',
|
|
'refuse',
|
|
'endLaunch',
|
|
'credentialStatus',
|
|
'checkPush',
|
|
'recordPush',
|
|
'reviewFetch',
|
|
'checkLand',
|
|
'recordLand',
|
|
'landRefused',
|
|
'landHeld',
|
|
'threadsState',
|
|
'threadIdle',
|
|
]);
|
|
function launchOp(m) {
|
|
if (m.op === 'identity') return runtime.identity(m.cap);
|
|
if (m.op === 'authorizeLaunch') return runtime.authorizeLaunch(m.cap, m.instance);
|
|
if (m.op === 'refuse') return runtime.refuse(m.cap, m.code, m.subject);
|
|
if (m.op === 'endLaunch') return runtime.endLaunch(m.record);
|
|
// Row 93 (#1587): the git socket's ops.
|
|
if (m.op === 'checkPush') return runtime.checkPush(m.cap, m.task_ref);
|
|
if (m.op === 'recordPush') return runtime.recordPush(m.cap, m.push);
|
|
if (m.op === 'reviewFetch') return runtime.reviewFetch(m.cap, m.args);
|
|
// Row 137 (#1631): the git socket's land op, and the launcher's question whether to bind it.
|
|
if (m.op === 'checkLand') return runtime.checkLand(m.cap, m.args);
|
|
if (m.op === 'recordLand') return runtime.recordLand(m.ticket, m.land);
|
|
if (m.op === 'landRefused') return runtime.landRefused(m.ticket, m.args);
|
|
if (m.op === 'landHeld') return runtime.landHeld(m.record);
|
|
// Row 121 (#1615): the launcher's view of the business's threads, and its idle release.
|
|
if (m.op === 'threadsState') return runtime.threadsState(m.business);
|
|
if (m.op === 'threadIdle') return runtime.threadIdle(m.record);
|
|
return runtime.credentialStatus(m.business, m.role);
|
|
}
|
|
// The host's request id comes back on the reply, so a late reply can't
|
|
// answer a later request (host.mjs). A message without one gets none.
|
|
const tag = (message, reply) => (Number.isSafeInteger(message?.id) ? { ...reply, id: message.id } : reply);
|
|
let runtime,
|
|
booted = false,
|
|
closing = false;
|
|
async function close(code) {
|
|
if (closing) return;
|
|
closing = true;
|
|
try {
|
|
await runtime?.close();
|
|
} catch {
|
|
code = 2;
|
|
}
|
|
process.exitCode = code;
|
|
if (process.connected) process.disconnect();
|
|
}
|
|
if (!process.send) {
|
|
process.stderr.write('trusted-host-required\n');
|
|
process.exitCode = 2;
|
|
} else {
|
|
const timer = setTimeout(() => close(2), 10000);
|
|
process.on('message', async (message) => {
|
|
try {
|
|
if (message?.op === 'bindLaunch' && runtime) {
|
|
try {
|
|
process.send(tag(message, { ok: true, launch: runtime.bindLaunch(message.record) }));
|
|
} catch (e) {
|
|
process.send(tag(message, { ok: false, error: e instanceof BusError ? e.code : 'bind-refused' }));
|
|
}
|
|
return;
|
|
}
|
|
// S6 launcher ops, one reply each, like bindLaunch.
|
|
if (LAUNCH_OPS.has(message?.op) && runtime) {
|
|
try {
|
|
process.send(tag(message, { ok: true, result: launchOp(message) }));
|
|
} catch (e) {
|
|
process.send(tag(message, { ok: false, error: e instanceof BusError ? e.code : 'launch-op-refused' }));
|
|
}
|
|
return;
|
|
}
|
|
if (message?.op === 'close') {
|
|
clearTimeout(timer);
|
|
await close(0);
|
|
return;
|
|
}
|
|
if (message?.op !== 'boot' || booted) throw new BusError('invalid-host-request');
|
|
booted = true;
|
|
clearTimeout(timer);
|
|
// `trackers` is S3's plain-data boot config; the adapter is loaded only when a business has one.
|
|
const { trackers, ...config } = message.config ?? {};
|
|
if (trackers) {
|
|
const { tasksAdapter } = await import('../../tasks/src/adapter.mjs');
|
|
config.tasks = tasksAdapter({ trackers });
|
|
}
|
|
runtime = await startBroker(config);
|
|
if (closing) {
|
|
await runtime.close();
|
|
return;
|
|
}
|
|
process.send({ ok: true, path: runtime.path, launches: runtime.launches, readers: runtime.readers });
|
|
} catch (e) {
|
|
process.send?.(tag(message, { ok: false, error: e instanceof BusError ? e.code : 'startup-refused' }), () =>
|
|
close(2),
|
|
);
|
|
}
|
|
});
|
|
process.on('disconnect', () => close(2));
|
|
process.on('SIGTERM', () => close(0));
|
|
process.on('SIGINT', () => close(0));
|
|
}
|