import assert from "node:assert/strict"; import { execFile } from "node:child_process"; import { chmod, lstat, mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; import { createServer, connect } from "node:net"; import test from "node:test"; import { canonicalIntegrationBase, CHECK_IDS, buildSafeEnvironment, installExternalFetchGuard, installNetworkGuard, negativeRequestEvidence, cleanupOwnedRun, createOwnedRun, deriveOverall, executeChecks, exportArchiveEvidencePath, readAndValidateOwnership, runCommand, runIntegration, scalarSecretBytes, scanSecrets, validateReport, validateRunRoot, } from "./p1-acceptance.mjs"; const execFileAsync = promisify(execFile); const roots = []; async function fakeRepository() { const root = await mkdtemp(join(tmpdir(), "p1 acceptance repository with spaces-")); roots.push(root); await mkdir(join(root, ".artifacts", "p1-integration"), { recursive: true }); return await realpath(root); } test.afterEach(async () => { await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); test("run roots are only canonical direct integration children", async () => { const repositoryRoot = await fakeRepository(); const base = canonicalIntegrationBase(repositoryRoot); const id = `p1-${"a".repeat(32)}`; assert.equal(validateRunRoot(repositoryRoot, join(base, id), id), join(base, id)); for (const candidate of [ base, join(repositoryRoot, ".artifacts", "manual-acceptance", id), join(base, id, "nested"), join(base, "foreign"), join(dirname(base), id), ]) assert.throws(() => validateRunRoot(repositoryRoot, candidate, id)); assert.throws(() => validateRunRoot(repositoryRoot, join(base, `p1-${"A".repeat(32)}`), `p1-${"A".repeat(32)}`)); }); test("cleanup refuses every unowned or ambiguous root", async () => { const repositoryRoot = await fakeRepository(); const base = canonicalIntegrationBase(repositoryRoot); const cases = [ ["missing ownership", async (run) => rm(join(run.root, "ownership.json"))], ["malformed ownership", async (run) => writeFile(join(run.root, "ownership.json"), "{")], ["mismatched root", async (run) => { const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8")); value.root = join(base, `p1-${"b".repeat(32)}`); await writeFile(join(run.root, "ownership.json"), JSON.stringify(value)); }], ["mismatched pid", async (run) => { const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8")); value.pid += 1; await writeFile(join(run.root, "ownership.json"), JSON.stringify(value)); }], ["wrong resource list", async (run) => { const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8")); value.resources.push(join(repositoryRoot, "foreign")); await writeFile(join(run.root, "ownership.json"), JSON.stringify(value)); }], ]; for (const [, mutate] of cases) { const run = await createOwnedRun({ repositoryRoot }); await mutate(run); await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce })); assert.equal((await lstat(run.root)).isDirectory(), true); } const wrongNonce = await createOwnedRun({ repositoryRoot }); await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: wrongNonce.root, expectedNonce: "0".repeat(64) })); const symlinkRun = await createOwnedRun({ repositoryRoot }); const target = `${symlinkRun.root}-target`; await rm(symlinkRun.root, { recursive: true }); await mkdir(target); await symlink(target, symlinkRun.root); await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: symlinkRun.root, expectedNonce: symlinkRun.nonce })); for (const bad of [base, join(repositoryRoot, ".artifacts", "manual-acceptance"), join(base, "foreign")]) { await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: bad, expectedNonce: "0".repeat(64) })); } }); test("cleanup atomically removes one owned root and preserves siblings", async () => { const repositoryRoot = await fakeRepository(); const run = await createOwnedRun({ repositoryRoot }); const sibling = join(canonicalIntegrationBase(repositoryRoot), `p1-${"c".repeat(32)}`); await mkdir(sibling); await writeFile(join(sibling, "sentinel"), "foreign"); await readAndValidateOwnership({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce }); await cleanupOwnedRun({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce }); await assert.rejects(lstat(run.root)); assert.equal(await readFile(join(sibling, "sentinel"), "utf8"), "foreign"); }); function resultFor(id) { return { id, status: "PASS", startedAt: "2026-08-09T00:00:00.000Z", finishedAt: "2026-08-09T00:00:01.000Z", commands: ["git"], artifacts: [{ path: `logs/${id}.json`, sha256: "a".repeat(64) }], }; } function validReport(checks = CHECK_IDS.map(resultFor)) { return { schemaVersion: 1, runId: `p1-${"d".repeat(32)}`, startedAt: "2026-08-09T00:00:00.000Z", finishedAt: "2026-08-09T00:00:02.000Z", command: "p1-acceptance integration --keep", overall: deriveOverall(checks), checks, }; } test("report validation enforces uniqueness, derivation, safe evidence, hashes, times, and commands", () => { assert.doesNotThrow(() => validateReport(validReport())); const mutations = [ (r) => r.checks.push(structuredClone(r.checks[0])), (r) => { r.checks[0].attempt = 1; }, (r) => { r.checks[0].artifacts[0].path = "../secret"; }, (r) => { r.checks[0].artifacts[0].sha256 = "bad"; }, (r) => { r.checks[0].startedAt = "today"; }, (r) => { r.checks[0].commands = ["git status"]; }, (r) => { r.overall = "PASS"; r.checks[0].status = "FAIL"; }, (r) => { r.nested = { retries: 2 }; }, ]; for (const mutate of mutations) { const report = validReport(); mutate(report); assert.throws(() => validateReport(report)); } }); function exactScenarios(run = async () => ({ commands: [], artifacts: [] })) { return CHECK_IDS.map((id) => ({ id, run: () => run(id) })); } test("injected failure executes once, retains a complete ordered diagnostic report, and returns nonzero", async () => { const repositoryRoot = await fakeRepository(); const calls = []; const failAt = CHECK_IDS[3]; const result = await runIntegration({ repositoryRoot, keep: false, failAt, checks: exactScenarios(async (id) => { calls.push(id); return { commands: [], artifacts: [] }; }), }); assert.equal(result.exitCode, 1); assert.deepEqual(calls, CHECK_IDS.slice(0, 4)); assert.equal((await lstat(result.runRoot)).isDirectory(), true); const report = JSON.parse(await readFile(join(result.runRoot, "report.json"), "utf8")); assert.deepEqual(report.checks.map(({ id }) => id), CHECK_IDS); assert.equal(report.checks.filter((check) => check.status === "FAIL").length, CHECK_IDS.length - 3); assert.equal(report.checks[3].error, "Acceptance scenario failed safely."); assert.equal(report.checks[4].error, "Not executed after earlier failure."); }); test("failed scenario retains partial request and response evidence with observed commands", async () => { const partial = { commands: ["git"], artifacts: [ { path: "requests/partial.json", sha256: "a".repeat(64) }, { path: "responses/partial.json", sha256: "b".repeat(64) }, ], }; const checks = exactScenarios(async (id) => { if (id === CHECK_IDS[4]) { const error = new Error("HTTP scenario failed after response persistence"); error.acceptancePartial = partial; throw error; } return {}; }); const results = await executeChecks({ checks }); assert.deepEqual(results[4].commands, partial.commands); assert.deepEqual(results[4].artifacts, partial.artifacts); assert.equal(results[4].error, "Acceptance scenario failed safely."); }); test("executeChecks never repeats or executes after first failure but emits the exact check set", async () => { const calls = new Map(); const result = await executeChecks({ checks: exactScenarios(async (id) => { calls.set(id, (calls.get(id) ?? 0) + 1); return {}; }), failAt: CHECK_IDS[1], }); assert.deepEqual(result.map(({ id }) => id), CHECK_IDS); assert.deepEqual(Object.fromEntries(calls), Object.fromEntries(CHECK_IDS.slice(0, 2).map((id) => [id, 1]))); assert.equal(result[1].status, "FAIL"); assert(result.slice(2).every(({ status, error }) => status === "FAIL" && error === "Not executed after earlier failure.")); assert.throws(() => validateReport(validReport(CHECK_IDS.slice(0, -1).map(resultFor)))); await assert.rejects(executeChecks({ checks: exactScenarios().reverse() })); }); test("owned setup failure still writes one safe result for every exact check", async () => { const repositoryRoot = await fakeRepository(); const result = await runIntegration({ repositoryRoot, keep: false, setup: async () => { throw new Error("fixture setup raw failure"); }, }); assert.equal(result.exitCode, 1); const report = JSON.parse(await readFile(join(result.runRoot, "report.json"), "utf8")); assert.deepEqual(report.checks.map(({ id }) => id), CHECK_IDS); assert.equal(report.checks[0].error, "Acceptance setup failed safely."); assert(report.checks.slice(1).every(({ error }) => error === "Not executed after earlier failure.")); }); test("scalar fixture secret files contain no harness-invalid whitespace", () => { const bytes = scalarSecretBytes("CANARY-secret-value-123456"); assert.equal(bytes.toString("utf8"), "CANARY-secret-value-123456"); assert.equal([...bytes].some((byte) => /\s/.test(String.fromCharCode(byte))), false); assert.throws(() => scalarSecretBytes("bad secret")); }); test("secret scanner excludes only the direct fixture-secrets subtree", async () => { const repositoryRoot = await fakeRepository(); const run = await createOwnedRun({ repositoryRoot }); const canary = "CANARY-secret-value-123456"; await mkdir(join(run.root, "fixture-secrets")); await writeFile(join(run.root, "fixture-secrets", "allowed"), canary); const paths = [ "logs/a.log", "responses/a.json", "rendered/a.yaml", "exports/raw/a.zip", "exports/extracted/a.md", "requests/a.json", "report-preview.md", "nested/fixture-secrets/not-excluded", ]; for (const path of paths) { await mkdir(dirname(join(run.root, path)), { recursive: true }); await writeFile(join(run.root, path), `prefix ${canary} suffix`); } const findings = await scanSecrets({ runRoot: run.root, forbiddenValues: [canary], expectedGitRepositories: [] }); assert.deepEqual(new Set(findings.map((finding) => finding.path)), new Set(paths)); }); test("secret scanner examines reachable Git blobs, not just loose file bytes", async () => { const repositoryRoot = await fakeRepository(); const run = await createOwnedRun({ repositoryRoot }); const canary = "GIT-CANARY-secret-value-987654"; const gitRoot = join(run.root, "author"); await mkdir(gitRoot); await execFileAsync("git", ["init", "--initial-branch=main"], { cwd: gitRoot }); await execFileAsync("git", ["config", "user.name", "Scanner Test"], { cwd: gitRoot }); await execFileAsync("git", ["config", "user.email", "scanner@example.invalid"], { cwd: gitRoot }); await writeFile(join(gitRoot, "secret.txt"), canary); await execFileAsync("git", ["add", "secret.txt"], { cwd: gitRoot }); await execFileAsync("git", ["commit", "-m", "secret blob"], { cwd: gitRoot }); await execFileAsync("git", ["rm", "secret.txt"], { cwd: gitRoot }); await execFileAsync("git", ["commit", "-m", "remove worktree copy"], { cwd: gitRoot }); const findings = await scanSecrets({ runRoot: run.root, forbiddenValues: [canary], expectedGitRepositories: ["author"] }); assert.equal(findings.some((finding) => finding.path.startsWith("git-object:")), true); }); test("successful lifecycle honors keep and cleanup", async () => { const repositoryRoot = await fakeRepository(); const checks = exactScenarios(); const kept = await runIntegration({ repositoryRoot, keep: true, checks }); assert.equal(kept.exitCode, 0); assert.equal((await lstat(kept.runRoot)).isDirectory(), true); const cleaned = await runIntegration({ repositoryRoot, keep: false, checks }); assert.equal(cleaned.exitCode, 0); await assert.rejects(lstat(cleaned.runRoot)); }); test("command helper accepts only executable plus separate argv", async () => { await assert.rejects(runCommand("git status")); await assert.rejects(runCommand({ executable: "/bin/echo", argv: "hello" })); await assert.rejects(runCommand({ executable: "/bin/echo", argv: [], shell: true })); await assert.rejects(runCommand({ executable: "git status; rm -rf /", argv: [] })); await assert.rejects(runCommand({ executable: "/tmp/git", argv: ["--version"] }), /command executable is not allowlisted/); await assert.rejects(runCommand({ executable: "tht", argv: ["config", "check"] }), /command executable is not allowlisted/); const repositoryRoot = await fakeRepository(); const executable = join(repositoryRoot, "executable with spaces"); await writeFile(executable, "#!/bin/sh\nprintf '%s' \"$1\"\n", { mode: 0o700 }); await chmod(executable, 0o700); await assert.rejects(runCommand({ executable, argv: ["literal;not-a-shell"] }), /command executable is not allowlisted/); const result = await runCommand({ executable: "git", argv: ["--version"] }); assert.match(result.stdout, /^git version /); assert.equal(result.code, 0); }); test("safe environment rejects ambient THT and keeps only strict process allowlist plus fixture values", () => { const safe = buildSafeEnvironment({ ambient: { PATH: "/safe/bin", HOME: "/home/test", LANG: "C", THT_SECRETS_FILE: "/real/secrets", AWS_SECRET_ACCESS_KEY: "real" }, fixture: { THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets" }, }); assert.deepEqual(safe, { PATH: "/safe/bin", HOME: "/home/test", LANG: "C", THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets", }); }); test("secret scan fails closed when Git enumeration fails", async () => { const repositoryRoot = await fakeRepository(); const run = await createOwnedRun({ repositoryRoot }); await mkdir(join(run.root, "remote.git")); await assert.rejects(scanSecrets({ runRoot: run.root, forbiddenValues: ["CANARY-value-123"] }), /Git secret scan failed closed/); }); test("negative request evidence persists only case label and expected input field", () => { const value = negativeRequestEvidence("credential-field", "evidence.source.password"); assert.deepEqual(value, { case: "credential-field", expectedInputField: "evidence.source.password" }); assert.equal(JSON.stringify(value).includes("body"), false); }); test("external fetch guard permits only the owned loopback API and records external attempts", async () => { const called = []; const guard = installExternalFetchGuard("http://127.0.0.1:12345", async (url) => { called.push(String(url)); return { ok: true }; }); await guard.fetch("http://127.0.0.1:12345/workspaces"); await assert.rejects(guard.fetch("https://evidence.example.test/guide.md"), /external fetch prohibited/); await assert.rejects(guard.fetch("http://127.0.0.1:9999/health"), /external fetch prohibited/); assert.deepEqual(called, ["http://127.0.0.1:12345/workspaces"]); assert.equal(guard.externalAttempts.length, 2); }); test("export archive evidence path matches the persisted binary request id", () => { assert.equal(exportArchiveEvidencePath("export-p1-filesystem"), "exports/raw/export-p1-filesystem.zip"); }); test("announce callback observes PASS and manual pending before non-keep cleanup", async () => { const repositoryRoot = await fakeRepository(); let observed; const result = await runIntegration({ repositoryRoot, keep: false, checks: exactScenarios(), announce: async ({ report, runRoot }) => { observed = { overall: report.overall, manual: "PENDING", rootExists: (await lstat(runRoot)).isDirectory() }; }, }); assert.deepEqual(observed, { overall: "PASS", manual: "PENDING", rootExists: true }); assert.equal(result.retained, false); }); test("public wrapper replaces ambient environment before invoking the runner", async () => { const wrapper = await readFile(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "scripts", "p1-acceptance.sh"), "utf8"); assert.match(wrapper, /safe_env=\(env -i/); assert.match(wrapper, /P1_ACCEPTANCE_FAIL_AT/); assert.doesNotMatch(wrapper, /export THT_BIN/); }); test("network guard is installed globally, rejects non-loopback sockets, and permits one owned listener", async () => { const server = createServer((socket) => socket.end("ok")); await new Promise((resolvePromise, reject) => server.listen(0, "127.0.0.1", (error) => error ? reject(error) : resolvePromise())); const address = server.address(); assert(address && typeof address === "object"); const guard = installNetworkGuard(); try { guard.addOwnedOrigin(`http://127.0.0.1:${address.port}`); const contents = await new Promise((resolvePromise, reject) => { const socket = connect({ host: "127.0.0.1", port: address.port }); let value = ""; socket.setEncoding("utf8"); socket.on("data", (chunk) => { value += chunk; }); socket.on("end", () => resolvePromise(value)); socket.on("error", reject); }); assert.equal(contents, "ok"); assert.throws(() => connect({ host: "example.com", port: 80 }), /external network connection prohibited/); await assert.rejects(globalThis.fetch("https://example.com/"), /external network connection prohibited/); assert.equal(guard.externalAttempts.length, 2); } finally { guard.restore(); await new Promise((resolvePromise) => server.close(resolvePromise)); } }); test("report validation rejects duplicate artifact paths across checks", () => { const report = validReport(); report.checks[1].artifacts[0].path = report.checks[0].artifacts[0].path; assert.throws(() => validateReport(report), /report artifact path is duplicated/); }); test("virtual report leakage yields a minimal sanitized exact-15 FAIL report", async () => { const repositoryRoot = await fakeRepository(); const canary = "VIRTUAL-CANARY-12345678"; const checks = exactScenarios(async (id) => ({ commands: [], artifacts: id === CHECK_IDS[0] ? [{ path: `logs/${canary}.json`, sha256: "a".repeat(64) }] : [], })); const result = await runIntegration({ repositoryRoot, checks, setup: async (_run, _repositoryRoot, _env, ctx) => { ctx.forbiddenValues = [canary]; return ctx; }, }); assert.equal(result.exitCode, 1); const bytes = await readFile(join(result.runRoot, "report.json")); assert.equal(bytes.includes(Buffer.from(canary)), false); const report = JSON.parse(bytes); assert.deepEqual(report.checks.map(({ id }) => id), CHECK_IDS); assert(report.checks.every(({ status, commands, artifacts }) => status === "FAIL" && commands.length === 0 && artifacts.length === 0)); }); test("partial setup preserves forbidden values and never writes secret-bearing report bytes", async () => { const repositoryRoot = await fakeRepository(); const canary = "PARTIAL-SETUP-CANARY-12345678"; const result = await runIntegration({ repositoryRoot, setup: async (run, _repositoryRoot, _env, ctx) => { ctx.forbiddenValues = [canary]; await mkdir(join(run.root, "logs"), { recursive: true }); await writeFile(join(run.root, "logs", "partial-setup.log"), canary); throw new Error(`unsafe ${canary}`); }, }); assert.equal(result.exitCode, 1); const bytes = await readFile(join(result.runRoot, "report.json")); assert.equal(bytes.includes(Buffer.from(canary)), false); const report = JSON.parse(bytes); assert.equal(report.checks.length, 15); assert(report.checks.every(({ status }) => status === "FAIL")); }); test("secret scan fails closed when either expected Git repository is missing", async () => { for (const missing of ["remote.git", "author"]) { const repositoryRoot = await fakeRepository(); const run = await createOwnedRun({ repositoryRoot }); const present = missing === "remote.git" ? "author" : "remote.git"; await mkdir(join(run.root, present)); await execFileAsync("git", present === "remote.git" ? ["init", "--bare", join(run.root, present)] : ["init", join(run.root, present)]); await assert.rejects(scanSecrets({ runRoot: run.root, forbiddenValues: ["CANARY-value-123"] }), new RegExp(`missing expected Git repository: ${missing.replace(".", "\\.")}`)); } });