Files
stack/scripts/foundation/cli.test.mjs
T
jason.woltje 8ebddd6f93 feat(foundation): offline synthetic scope/permission inspector (FI-FILBERT-8 APPROVED r6)
Rocko-authored, Filbert-reviewed inspector (r6 manifest
a4a44930...) with full review/build/verdict evidence under
docs/plans/reviews. 43/0 selftests, oracle zero-disagreement,
foundation checker PASS. Owner A9 acceptance recorded separately.
2026-09-07 14:06:35 -05:00

318 lines
14 KiB
JavaScript

// Isolated CLI tests: usage, I/O failures, bounds, output escaping, privacy and
// non-effect inventory (charter §7, §8, §10.3). Every run uses a disposable synthetic
// sandbox (home, cwd, fixtures, data-root) with canary files; nothing here touches
// the operator's real HOME, ~/.mosaic or any credential.
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import {
mkdtempSync, mkdirSync, writeFileSync, symlinkSync, readdirSync, lstatSync, readFileSync, rmSync, chmodSync,
} from "node:fs";
import { join, dirname } from "node:path";
import { tmpdir } from "node:os";
import { createHash } from "node:crypto";
import { fileURLToPath } from "node:url";
import { renderText, renderJson, escapeText, run } from "../foundation-inspect.mjs";
import { exitFor } from "./resolve.mjs";
const HERE = dirname(fileURLToPath(import.meta.url));
const CLI = join(HERE, "..", "foundation-inspect.mjs");
const DEMO = join(HERE, "fixtures", "demo");
const CANARY = "CANARY-DO-NOT-PRINT";
let root;
let env;
let cwd;
function sha256(buf) {
return createHash("sha256").update(buf).digest("hex");
}
/** path/type/size/mode/uid/gid/ino/mtime/content-hash inventory of a tree, sorted by path (charter §8 ownership included). */
function inventory(dir, out = []) {
for (const name of readdirSync(dir).sort()) {
const p = join(dir, name);
const st = lstatSync(p);
const type = st.isDirectory() ? "dir" : st.isSymbolicLink() ? "link" : st.isFIFO() ? "fifo" : st.isFile() ? "file" : "other";
const content = st.isFile() ? sha256(readFileSync(p)) : null;
out.push([p.slice(root.length), type, st.size, st.mode, st.uid, st.gid, st.ino, st.mtimeMs, content].join("|"));
if (st.isDirectory()) inventory(p, out);
}
return out;
}
function inspect(args, opts = {}) {
const r = spawnSync(process.execPath, [CLI, ...args], {
cwd: opts.cwd || cwd, env: opts.env || env, encoding: "buffer", timeout: 30000,
});
assert.equal(r.error, undefined, "spawn failed");
return { status: r.status, stdout: r.stdout.toString("utf8"), stderr: r.stderr.toString("utf8"), raw: r.stdout };
}
function jsonRun(args, opts) {
const r = inspect(["--json", ...args], opts);
assert.equal(r.stderr, "", "stderr must be empty");
const parsed = JSON.parse(r.stdout);
assert.ok(!("exit" in parsed), "exit is process metadata, never a serialized field");
assert.equal(r.status, exitFor(parsed), "process exit code is derived from the closed result");
return { ...r, parsed };
}
before(() => {
root = mkdtempSync(join(tmpdir(), "foundation-cli-"));
for (const d of ["home", "home/.mosaic", "home/.config", "home/.config/mosaic", "cwd", "fixtures", "data-root", "data-root/runs"]) {
mkdirSync(join(root, d), { recursive: true });
}
writeFileSync(join(root, "home", ".mosaic", "credentials.json"), `{"token":"${CANARY}-1"}\n`);
writeFileSync(join(root, "home", ".config", "mosaic", "config.json"), `{"secret":"${CANARY}-2"}\n`);
writeFileSync(join(root, "cwd", "bundle.json"), `{"canary":"${CANARY}-3"}\n`);
writeFileSync(join(root, "data-root", "runs", "result.json"), `{"canary":"${CANARY}-4"}\n`);
writeFileSync(join(root, "cwd", "package.json"), `{"name":"${CANARY}-5"}\n`);
for (const name of readdirSync(DEMO)) {
if (name.endsWith(".json") && !name.includes(".expected.")) writeFileSync(join(root, "fixtures", name), readFileSync(join(DEMO, name)));
}
cwd = join(root, "cwd");
env = {
HOME: join(root, "home"),
TMPDIR: join(root, "data-root"),
PATH: "/nonexistent",
MOSAIC_CONFIG: join(root, "home", ".config", "mosaic", "config.json"),
XDG_CONFIG_HOME: join(root, "home", ".config"),
MOSAIC_DATA_ROOT: join(root, "data-root"),
LANG: "C",
};
});
after(() => {
if (root) rmSync(root, { recursive: true, force: true });
});
test("usage errors exit 2 usage-invalid without touching any file", () => {
const before = inventory(root);
for (const args of [[], ["--json"], ["a.json", "b.json"], ["--nope", "a.json"], ["--json", "--json", "a.json"], ["-x"]]) {
const r = jsonRun(args);
assert.equal(r.status, 2, JSON.stringify(args));
assert.equal(r.parsed.rule, "usage-invalid");
assert.equal(r.parsed.reason, "invalid-request");
assert.equal(r.parsed.result, "invalid");
assert.equal(r.parsed.diagnostic, null);
}
const t = inspect([]);
assert.equal(t.status, 2);
assert.match(t.stdout, /^SYNTHETIC PREVIEW/);
assert.deepEqual(inventory(root), before);
});
test("missing file: exit 4 input-open-failed with the input path echoed in the diagnostic only", () => {
const r = jsonRun(["does-not-exist.json"]);
assert.equal(r.status, 4);
assert.equal(r.parsed.rule, "input-open-failed");
assert.equal(r.parsed.reason, "io-failure");
assert.deepEqual(r.parsed.diagnostic, { byteOffset: null, inputPath: "does-not-exist.json" });
assert.equal(r.parsed.selection, null);
});
test("symlink to a valid bundle is refused (O_NOFOLLOW) with exit 4", () => {
symlinkSync(join(root, "fixtures", "demo-read-w1.json"), join(root, "cwd", "link.json"));
const r = jsonRun(["link.json"]);
assert.equal(r.status, 4);
assert.equal(r.parsed.rule, "input-open-failed");
assert.equal(r.parsed.diagnostic.inputPath, "link.json");
});
test("directory and FIFO are not regular files: exit 4 input-not-regular", () => {
const d = jsonRun([join(root, "fixtures")]);
assert.equal(d.status, 4);
assert.equal(d.parsed.rule, "input-not-regular");
const fifo = join(root, "cwd", "pipe.json");
const mk = spawnSync("mkfifo", [fifo], { env: { ...env, PATH: "/usr/bin:/bin" } });
if (mk.status !== 0) {
assert.fail("mkfifo unavailable; FIFO case cannot be exercised");
}
const f = jsonRun(["pipe.json"]);
assert.equal(f.status, 4, "FIFO must be refused after O_NONBLOCK open, not read");
assert.equal(f.parsed.rule, "input-not-regular");
});
test("unreadable regular file: exit 4 input-open-failed", (t) => {
if (typeof process.getuid === "function" && process.getuid() === 0) {
t.skip("root ignores mode bits");
return;
}
const p = join(root, "cwd", "unreadable.json");
writeFileSync(p, "{}\n");
chmodSync(p, 0o000);
const r = jsonRun(["unreadable.json"]);
assert.equal(r.status, 4);
assert.equal(r.parsed.rule, "input-open-failed");
chmodSync(p, 0o600);
});
test("oversize file: exit 2 input-too-large with a null diagnostic (no path echo)", () => {
const p = join(root, "cwd", "big.json");
const buf = Buffer.alloc(1024 * 1024 + 1, 0x20);
buf[0] = 0x7b; buf[buf.length - 1] = 0x7d;
writeFileSync(p, buf);
const r = jsonRun(["big.json"]);
assert.equal(r.status, 2);
assert.equal(r.parsed.rule, "input-too-large");
assert.equal(r.parsed.reason, "invalid-request");
assert.equal(r.parsed.diagnostic, null);
rmSync(p);
// exactly 1 MiB is read and parsed (here: an object, so a shape refusal, not a bound)
const exact = Buffer.alloc(1024 * 1024, 0x20);
exact[0] = 0x7b; exact[exact.length - 1] = 0x7d;
writeFileSync(p, exact);
const e = jsonRun(["big.json"]);
assert.equal(e.status, 2);
assert.equal(e.parsed.rule, "shape-missing-field");
rmSync(p);
});
test("parse failure carries byteOffset and never the path", () => {
writeFileSync(join(root, "cwd", "dup.json"), '{"a": 1, "a": 2}');
const r = jsonRun(["dup.json"]);
assert.equal(r.status, 2);
assert.equal(r.parsed.rule, "input-parse-failed");
assert.deepEqual(r.parsed.diagnostic, { byteOffset: 9, inputPath: null });
});
test("text output is derived from the same result as JSON output", () => {
for (const name of ["demo-read-w1", "demo-change-w1", "adm-registration-revoked"]) {
const j = jsonRun([join(root, "fixtures", `${name}.json`)]);
const t = inspect([join(root, "fixtures", `${name}.json`)]);
assert.equal(t.stderr, "");
assert.equal(t.status, j.status);
assert.equal(t.stdout, renderText(j.parsed), name);
assert.equal(j.stdout, renderJson(j.parsed), name);
assert.equal(j.stdout, renderJson(JSON.parse(j.stdout)), `${name}: JSON output is a pure round-trip`);
}
});
test("owner demo: permitted preview exits 0 and prints the disclaimer first", () => {
const t = inspect([join(root, "fixtures", "demo-read-w1.json")]);
assert.equal(t.status, 0);
const lines = t.stdout.split("\n");
assert.equal(lines[0], "SYNTHETIC PREVIEW — NO LIVE EFFECTS");
assert.equal(lines[1], "preview: no live registrations or permission grants");
assert.ok(lines.includes("result: allowed"));
assert.ok(!lines.some((l) => l.startsWith("exit")), "no exit line in text output");
});
test("risky code points in echoed fields are escaped in both renderings", () => {
const bundle = JSON.parse(readFileSync(join(root, "fixtures", "demo-read-w1.json"), "utf8"));
bundle.operation = { name: "file.read", target: { root: "workspace", path: "src/a\u2028b" } };
writeFileSync(join(root, "cwd", "sep.json"), `${JSON.stringify(bundle)}\n`);
const j = jsonRun(["sep.json"]);
assert.equal(j.status, 0, "U+2028 is Zl, accepted by the pinned checker");
assert.ok(!j.stdout.includes("\u2028"), "JSON output escapes U+2028");
assert.ok(j.stdout.includes("\\u2028"));
const t = inspect(["sep.json"]);
assert.ok(!t.stdout.includes("\u2028"));
assert.ok(t.stdout.includes("operation: file.read target workspace:src/a\\u2028b"));
// ESC in a selection id is a shape refusal; the id must still not reach the terminal raw
bundle.operation = { name: "work.read", target: null };
bundle.selection.agentId = "agent-a\u001b[31m";
writeFileSync(join(root, "cwd", "esc.json"), `${JSON.stringify(bundle)}\n`);
const e = inspect(["esc.json"]);
assert.equal(e.status, 2);
assert.ok(!e.stdout.includes("\u001b"));
assert.ok(e.stdout.includes("selection: null"));
assert.equal(escapeText("a\u001b\u2028\\\u{1F600}b"), "a\\u001b\\u2028\\u005c\u{1F600}b");
});
test("canaries never reach stdout: bundle strings, HOME, config, cwd, data-root", () => {
const bundle = JSON.parse(readFileSync(join(root, "fixtures", "demo-read-w1.json"), "utf8"));
for (const r of bundle.records) if (r.kind === "project") r.payload.displayName = `${CANARY}-6`;
writeFileSync(join(root, "cwd", "canary.json"), `${JSON.stringify(bundle)}\n`);
const outputs = [];
for (const args of [["canary.json"], ["--json", "canary.json"], ["missing.json"], ["--json", "missing.json"], []]) {
const r = inspect(args);
outputs.push(r.stdout, r.stderr);
}
const joined = outputs.join("\n");
assert.ok(!joined.includes(CANARY), "canary content leaked to output");
assert.ok(!joined.includes(root), "sandbox root path leaked to output");
});
test("HOME/config independence: identical bytes across different synthetic environments", () => {
const args = ["--json", join(root, "fixtures", "demo-change-w1.json")];
const a = inspect(args);
const altHome = join(root, "home2");
mkdirSync(altHome, { recursive: true });
const b = inspect(args, { env: { HOME: altHome, PATH: "/nonexistent", MOSAIC_CONFIG: "/nonexistent/config.json", NODE_OPTIONS: "" }, cwd: root });
const c = inspect(args);
assert.equal(a.stdout, b.stdout);
assert.equal(a.stdout, c.stdout);
assert.equal(a.status, 3);
assert.equal(JSON.parse(a.stdout).result, "unresolved");
});
test("non-effect: before/after inventory of the sandbox is unchanged by every kind of run", () => {
rmSync(join(root, "cwd", "pipe.json"), { force: true });
const before = inventory(root);
const runs = [
["demo-read-w1.json"], ["--json", "demo-change-w1.json"], ["adm-registration-revoked.json"],
["prop-message-is-not-authority.json"], ["demo-file-change-src.json"],
];
for (const args of runs) {
const r = inspect(args.map((a) => (a.endsWith(".json") ? join(root, "fixtures", a) : a)));
assert.equal(r.stderr, "");
assert.ok([0, 3].includes(r.status), args.join(" "));
}
inspect(["../home/.mosaic/credentials.json"]);
inspect([join(root, "home", ".config", "mosaic", "config.json")]);
inspect(["missing.json"]);
inspect([]);
const after = inventory(root);
assert.deepEqual(after, before);
assert.deepEqual(readdirSync(join(root, "data-root")), ["runs"]);
assert.deepEqual(readdirSync(join(root, "data-root", "runs")), ["result.json"]);
});
test("in-process run() agrees with the spawned CLI", () => {
const p = join(root, "fixtures", "demo-read-w1.json");
const inproc = run(["--json", p]);
const spawned = jsonRun([p]);
assert.deepEqual(inproc.result, spawned.parsed);
assert.equal(inproc.json, true);
assert.equal(run([]).result.rule, "usage-invalid");
assert.equal(run(["--json"]).json, true);
});
test("profile negative control: one final LF in a typed field is refused pre-admission through the CLI and never echoed", () => {
const bundle = JSON.parse(readFileSync(join(root, "fixtures", "demo-read-w1.json"), "utf8"));
bundle.selection.agentId = `${bundle.selection.agentId}\n`;
const p = join(root, "cwd", "profile-lf.json");
writeFileSync(p, `${JSON.stringify(bundle)}\n`);
const r = jsonRun(["profile-lf.json"]);
assert.equal(r.status, 2);
assert.equal(r.parsed.result, "invalid");
assert.equal(r.parsed.reason, "invalid-request");
assert.equal(r.parsed.rule, "profile-pattern-mismatch");
assert.equal(r.parsed.selection, null);
assert.equal(r.parsed.operation, null);
assert.equal(r.parsed.proposal, null);
assert.equal(r.parsed.diagnostic, null);
assert.ok(!r.stdout.includes("agent-a"), "typed value never echoed");
const t = inspect(["profile-lf.json"]);
assert.equal(t.status, 2);
assert.equal(t.stderr, "");
assert.ok(t.stdout.includes("\nrule: profile-pattern-mismatch\n"));
assert.ok(t.stdout.includes("\nselection: null\n"));
assert.ok(!t.stdout.includes("agent-a"));
// Two final LFs fail the schema pattern itself (both implementations), not the profile.
bundle.selection.agentId = `${bundle.selection.agentId}\n`;
writeFileSync(p, `${JSON.stringify(bundle)}\n`);
const two = jsonRun(["profile-lf.json"]);
assert.equal(two.status, 2);
assert.equal(two.parsed.rule, "shape-pattern-mismatch");
// Positive control: free-form text with escaped newlines is not blanket-rejected.
const ok = JSON.parse(readFileSync(join(root, "fixtures", "demo-read-w1.json"), "utf8"));
ok.records.find((x) => x.kind === "mission" && x.id === "m-w1").payload.objective = "line one\nline two\n";
writeFileSync(join(root, "cwd", "text-lf.json"), `${JSON.stringify(ok)}\n`);
const allowed = jsonRun(["text-lf.json"]);
assert.equal(allowed.status, 0);
assert.equal(allowed.parsed.result, "allowed");
});