Files
stack/packages/cli/src/hosts.mjs
T
jason.woltje b1fece6f0a row 235: machine grant row b, raise binding, executor, reconcile, revoke and sweep, creative roles refused at raise, recheck and host.run, N10 (#1741)
Lands Gizmoduck's round 1 candidate, ed4193316 on gizmoduck/row235-e (one commit on 62ddc96cc), approved
by Duckworth (duckworth-0089, REVIEW-r1 d9ecec514 on duckworth/reviews, #1741 comment 30022). 13 paths,
manifest dad377ca8fe4. No 235 path changed between 62ddc96cc and this commit's parent. Patch-id
c355d2fc457d, the same as 62ddc96cc..ed4193316. broker.mjs keeps row 213's import and decision.raise
creative refusal as landed.

Creative roles (mos-1163, mos-1164, sage-1908): #hostDefinition refuses host-creative-role at the raise
and at the step 1 recheck, and #hostAccess refuses it at host.run before reading the grant record, which
stays for hosts.revoke. Duckworth checked from the code that a creative role can't raise, approve or
spend a host grant, and ruled the upsert mutant equivalent.

Evidence: Gizmoduck's runs on my slot. B1-B37 on 5df4b52c5, and run 2 on 534f2254a (10:12:52-10:14:51Z):
grant-host 9/9, grant-executor 37/37 and grant-enroll 24/24, each run whole, with B16, B23, B27, B38 and
B39 killed. B1-B39: 38 killed, and B17 is equivalent (sage-1904).
Gate on my slot, 10:30:55-10:42:43Z on 29ce1e360 plus the 13 paths: bus 596/0 (its first whole run on
this head, duckworth-0089 N1), business 200/0, cli 666/0, and every scripts/test-*.sh green (auth 15,
conductor 17, config 31, discord 78, extension-package 18, foundation 44, land-work 86, queue 156 and 28,
release 14, task 98, all 0 failed).
2026-10-21 05:42:52 -05:00

53 lines
2.8 KiB
JavaScript

// `mosaic hosts` (row 234, #1740, r6 E4): the broker's hosts.status, hosts.check and hosts.remove. The
// human runs all three. With --tester the tester's session runs them. check logs in as mosaic-installer
// and runs `list` on the host, then marks the host ready. remove drops a host no grant uses. Nothing here
// reads a key: the answers carry fingerprints only. Row 235 (#1741, r6 section 6, X3) adds revoke, which
// takes an agent's grant on a host away at once, and sweep, which removes the lines a failure left.
import { CliError } from "./errors.mjs";
export const HOSTS_USAGE =
"mosaic hosts [status] [--json] [--tester] [--business ID] | check|remove|sweep HOST [--tester] [--business ID] | revoke AGENT HOST --reason R [--tester] [--business ID]";
function parse(args) {
const out = { positional: [], flags: new Set(), values: {} };
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "--json" || arg === "--tester") out.flags.add(arg);
else if (arg === "--business" || arg === "--reason") {
if (!args[i + 1] || Object.hasOwn(out.values, arg)) throw new CliError(HOSTS_USAGE, 4);
out.values[arg] = args[++i];
} else if (arg.startsWith("--")) throw new CliError(HOSTS_USAGE, 4);
else out.positional.push(arg);
}
return out;
}
const line = (h) =>
[h.host, h.state, `${h.user}@${h.address}:${h.port}`, h.sudo ? "sudo" : "no-sudo", h.checkedAt ?? "unchecked",
...(h.hostLineLeft.length ? [`line left: ${h.hostLineLeft.join(" ")}`] : []),
...(h.revoking?.length ? [`revoking: ${h.revoking.join(" ")}`] : [])].join("\t");
export async function hostsCommand(args, io, callFor) {
const parsed = parse(args);
const [verb = "status", first, second, ...extra] = parsed.positional;
const one = first !== undefined && second === undefined;
const shape = { status: first === undefined, check: one, remove: one, sweep: one, revoke: second !== undefined };
// N10: own keys only, so `constructor` or `toString` is a usage error, not a verb.
if (extra.length || !Object.hasOwn(shape, verb) || !shape[verb] || (parsed.flags.has("--json") && verb !== "status") ||
(verb === "revoke") !== Object.hasOwn(parsed.values, "--reason"))
throw new CliError(HOSTS_USAGE, 4);
try {
const call = callFor(parsed);
if (verb === "status") {
const rows = await call("hosts.status", {});
io.stdout.write(parsed.flags.has("--json") ? JSON.stringify(rows, null, 2) + "\n" : rows.map(line).join("\n") + (rows.length ? "\n" : ""));
return;
}
const args = verb === "revoke" ? { agent: first, host: second, reason: parsed.values["--reason"] } : { host: first };
io.stdout.write(JSON.stringify(await call(`hosts.${verb}`, args), null, 2) + "\n");
} catch (error) {
if (error instanceof CliError) throw error;
throw new CliError(error.message, 2);
}
}