Files
stack/packages/bus/tests/channel-proof.test.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

76 lines
5.3 KiB
JavaScript

// Row 164 freezes reviewed proof bytes and the literal entry-path rule.
// Row 196 intentionally re-pins human.mjs for the distinct user nonce and shared human
// walk, and webui bus.mjs for initial-spawn nonce plus peer refusal status. Entry paths
// and the ban on channel plug-ins importing a proof caller are retained.
// Row 254 (#1759) re-pins human-cli.mjs for the 130 s library.sync client timeout and webui
// bus.mjs for six library-* STATUS entries; no proof or transport change.
// Row 236 (#1742) re-pins human-cli.mjs for the same 130 s wait on the grants verbs; no proof or
// transport change.
// Row 252 (#1753, Webby N5) re-pins human.mjs, whose readProcess also keeps the five
// MOSAIC_BUS_CONSOLE_* keys for the Console wrapper's proof, and webui bus.mjs for consoleCall,
// which shares humanCall's spawn as transport(). The human proof and its spawn are unchanged.
// Row 225 (C2-10b) re-pins webui bus.mjs for one STATUS entry, library-revert-not-agent: 409; no proof
// or transport change.
// E2E6 fix 6 re-pins human-cli.mjs, which exits 5 when its connect failed before the request was
// written, and cli transport.mjs, which reads that exit as bus-not-running. The proof is unchanged.
import test from "node:test";
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { provedRuntime } from "../../cli/tests/channel-fixture.mjs";
import { Client } from "../src/client.mjs";
const baselines = {
"packages/bus/src/human.mjs": "bb13c146717cdc46d75d157177e400edac4d3e10f14fe511027a25fd44cec885",
"packages/bus/src/human-cli.mjs": "410892787cc70440b0ba4c45d6f24861d12ee5e70cba678cfeb1ed86b796c149",
// Row 239 (#1745): EXIT outcome-unknown-revoked 1; no proof or transport change.
// E2E6 fix 6: shim exit 5 with outcome-unknown becomes bus-not-running (exit 3, start and recover next).
"packages/cli/src/transport.mjs": "101870f7ffb47a9cbd54ebd8c1901734cc9f442d6770cb2a0d059afd4d14b160",
// Row 165 (#1662): seven STATUS entries for the role instance routes (organization-instance-exists,
// organization-unknown-instance, organization-credentials-out-of-scope, organization-launch-not-configured,
// organization-no-change, organization-project-mismatch, organization-unresolvable-instance), no proof or
// transport change. Row 155 (#1649) added eight for the assistant routes.
// Row 171 (#1668): project-name-is-repo maps to 409; no proof or transport change.
// Row 194 (#1692, brief item 7, sage-1610 a): organization-tracker-required 400 and
// organization-tracker-bot-taken 409; no proof or transport change.
// Row 301 (#1808, mos-1449): the user transport moved from user-bus.mjs into bus.mjs; humanCall unchanged.
// Row 239 (#1745): outcome-unknown-revoked joins OPEN (duckworth-0165 C1), and STATUS gets
// revision-conflict-revoked 409 and outcome-unknown-revoked 503; no proof or transport change.
// #1839: artifact Console replies alone may use 2 MiB; the proof and other limits are unchanged.
// #1873 U1: humanCall, busCaller, userCall and userCaller take an optional signal and absolute
// deadline; userCall shares transport(). Nonces, argv and limits are unchanged.
// #1873 A3: STATUS maps credential-unbound, copy-refused, project-refused and project-agent-elsewhere
// to 409; no proof or transport change.
"packages/webui/src/bus.mjs": "d8f3e0eb5299fdb192e98a1238ca693aae7c36af9a21de72d5f5d39610589377",
};
const repoFile = path => new URL("../../../" + path, import.meta.url);
test("channel conversion preserves the four proof/transport byte baselines and modules cannot import a proof caller", () => {
for (const [file, hash] of Object.entries(baselines))
assert.equal(createHash("sha256").update(readFileSync(repoFile(file))).digest("hex"), hash, file);
for (const name of ["cli", "webui"]) {
const source = readFileSync(repoFile(`packages/plugins/channel/${name}/index.mjs`), "utf8");
assert.doesNotMatch(source, /human-cli|humanTransport|humanCall|verifyHuman|readProcess|child_process|spawn/);
}
});
test("channel recovery still requires the same shim path and rejects an absent or forged proof", { timeout: 20000 }, async t => {
const s = await provedRuntime(t);
const direct = new Client({ path: s.socket });
await assert.rejects(direct.call("plugins"), e => ["invalid-envelope", "unauthenticated", "human-required"].includes(e.code));
const forged = new Client({ path: s.socket, human: { business: "demo", pid: process.pid, startTime: "0", nonce: "a".repeat(64) } });
await assert.rejects(forged.call("plugins"), e => e.code === "human-required");
const genuine = await s.shimCall(s.shim, { business: "demo", verb: "plugins" });
assert.equal(genuine.status, 0, genuine.stderr);
assert.deepEqual(JSON.parse(genuine.stdout).plugins.channel, []);
let other = readFileSync(s.shim, "utf8");
other = other.replace("'./client.mjs'", JSON.stringify(new URL("../src/client.mjs", import.meta.url).href));
other = other.replace("'./human.mjs'", JSON.stringify(new URL("../src/human.mjs", import.meta.url).href));
const differentPath = join(s.root, "other-human-cli.mjs");
writeFileSync(differentPath, other);
const refused = await s.shimCall(differentPath, { business: "demo", verb: "plugins" });
assert.notEqual(refused.status, 0);
assert.equal(refused.stderr.trim(), "human-required");
});