Files
stack/packages/bus/src/human-cli.mjs
jason.woltje 23e504f331 fix(cli,bus,organization): E2E6 lane f6c, refusal lines name the next step (#1873)
1. Stale bus host lock refusals from bus status, start, stop and restart
   name mosaic bus recover ORG.
2. org definitions: unknown org, unknown or malformed name and bad id give
   one refusal or usage line, no stack trace, no host path.
3. Pack, board and workflow refusals print their hint as template save does;
   tested with the real CliError transport shape.
4. console start and stop clear a console/start.lock whose named supervisor
   (pid and start time) is gone; a live or unnamed holder refuses.
5. Template save keeps free text verbatim and writes no placeholders; the
   preview lists each field that mentions an org-specific name.
6. A request the shim never sent (no socket, connect refused) exits 5 and
   the CLI reports bus-not-running with the start and recover line.
7. autonomy-precondition carries the check that failed, including on the
   hire paths.
8. Saving under an existing template name refuses template-exists with a
   next step; refusal table entry added, channel-proof pins moved.
2026-11-13 20:14:21 -06:00

46 lines
2.0 KiB
JavaScript

// Transport shim used by S4, not a second product CLI. Input and proof never carry service tokens.
import { randomBytes } from 'node:crypto';
import { spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { readFileSync } from 'node:fs';
import { Client, NOT_SENT_EXIT } from './client.mjs';
import { readProcess } from './human.mjs';
const file = fileURLToPath(import.meta.url);
if (!process.env.MOSAIC_BUS_CLI_NONCE) {
const child = spawnSync(process.execPath, [file, ...process.argv.slice(2)], {
stdio: 'inherit',
env: { ...process.env, MOSAIC_BUS_CLI_NONCE: randomBytes(32).toString('hex') },
});
process.exit(child.status ?? 2);
}
try {
const request = JSON.parse(readFileSync(0, 'utf8'));
if (
!request ||
typeof request !== 'object' ||
Object.keys(request).some((k) => !['business', 'verb', 'args'].includes(k))
)
throw Error();
const me = readProcess(process.pid);
const client = new Client({
path: process.argv[2],
// Row 254: library.sync fetches for up to 100 s and the server holds its socket for 120 s,
// so wait past that for the server's own answer. Row 236: the grants verbs call Gitea and get the
// same wait. Every other verb keeps the 5 s default.
timeout: request.verb === 'library.sync' || request.verb.startsWith('grants.') ? 130_000 : undefined,
human: {
business: request.business,
pid: me.pid,
startTime: me.startTime,
nonce: process.env.MOSAIC_BUS_CLI_NONCE,
},
});
const result = await client.call(request.verb, request.args ?? {});
process.stdout.write(JSON.stringify(result) + '\n');
} catch (e) {
process.stderr.write((/^[a-z-]{1,64}$/.test(e.code ?? '') ? e.code : 'invalid-request') + '\n');
// E2E6 fix 6: a request that never left (no socket, or the connect refused) keeps its code and
// exits NOT_SENT_EXIT, so `mosaic` can say the bus is not running. Every other failure exits 2.
process.exitCode = e?.notSent === true ? NOT_SENT_EXIT : 2;
}