// 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"); });