Files
stack/packages/bus/src/process.mjs
T
jason.woltje 4f45cadc09 bus, cli, harness: the host starts the landing run once a merge decision is approved (row 156, #1651)
Until now a landing ran only when the chief of staff or merge-gate
followed row 149's manual steps. The host now does it.

Broker. landHolders(business) lists the approved merge decisions each
instance holds, sharing the #held generator with landHeld.
landRelease(record) releases a run's claim with by='land-relaunch'.
Delivered and unread messages keep the run, and a run that hasn't had
its first message.receive answers {released:false, landing:true}
(#received, round 2), so a landing turn in progress is never cut. A
run that never reaches its first receive, a hung landing turn or a
broker restart, keeps the release closed, and the decision falls back
to row 149's path.
usage.begin accepts a decision id that landHolders lists for the run.
The bus process answers both ops from their own launchOp lines and
refuses any op it doesn't name.

Launcher. start() reads landHolders once for a non-thread instance and
takes land_work and the session's landing list from that one answer.
landPass() runs after threadPass() on each tick.

Runner. One turn per landing entry before the message loop, with a
usage bracket on the decision id.

Docs. A "Landing runs" section in the cli README and docs/TOOLS.md.
roles/merge-gate.md, cto.md and chief-of-staff.md say the host starts
the run, and row 149's steps are the fallback.

Owner Dewey, brief docs/plans/2026-10-18_landing-run-launch.md, design
DESIGN.md in the packet (sage-0808). Round 2 answers Filbert's F1-F3
(29033, rulings sage-0942). Reviewer on candidate manifest
72a5c1b6...b700: Filbert approve 29039. Seat run 4: 290/290 singles,
36/36 mutants killed. Sage gate on a618528b2 (candidate 524bfcc80): GATE_PASSED 25/25. Package tests 2258 plus queue 156, script selftests 422, 23:30:07Z to 23:38:04Z. Run 1 stopped at pkg-webui on board-held.test.mjs:163, the P19 366 timeout, 220/1. Run 2 resumed at pkg-webui on the same tree: webui 221/0 and every scripts/test-*.sh green.

Nothing live.
2026-10-18 18:38:57 -05:00

123 lines
4.9 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',
'mayLaunch',
'landHolders',
'landRelease',
]);
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);
// Row 125 (#1619, REQ-LIMIT-4): the limit checks' answer before a launch (async).
if (m.op === 'mayLaunch') return runtime.mayLaunch(m);
// Row 156 (#1651): the launcher's landing pass: who holds a merge decision, and the stop of a
// role run started before its decision was resolved.
if (m.op === 'landHolders') return runtime.landHolders(m.business);
if (m.op === 'landRelease') return runtime.landRelease(m.record);
if (m.op === 'credentialStatus') return runtime.credentialStatus(m.business, m.role);
// Every LAUNCH_OPS name has its line above. A missing one used to answer as credentialStatus,
// whose [] for an unknown role read as "no holders" for landHolders (row 156 mutant P02).
throw new BusError('launch-op-refused');
}
// 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: await 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));
}