Files
ThothII/backend/scripts/p1-acceptance.mjs
T

1304 lines
78 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { execFile } from "node:child_process";
import { createHash, randomBytes } from "node:crypto";
import {
closeSync, constants as fsConstants, existsSync, fsyncSync, lstatSync, openSync, realpathSync,
} from "node:fs";
import {
access, chmod, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, symlink, writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { Socket, isIP } from "node:net";
import {
basename, dirname, isAbsolute, join, relative, resolve, sep,
} from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
let commandEventSink;
let activeCommandCheckId;
const RUN_ID = /^p1-[0-9a-f]{32}$/;
const HEX40 = /^[0-9a-f]{40}$/;
const HEX64 = /^[0-9a-f]{64}$/;
const ISO_UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
const SAFE_RELATIVE = /^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))(?!.*\\)[A-Za-z0-9._/-]+$/;
const COMMAND = /^[A-Za-z0-9._+-]+$/;
export const CHECK_IDS = Object.freeze([
"preflight", "clean_state", "ownership", "local_git_bootstrap",
"http_validate_publish_pull_read_export", "same_revision_git_objects",
"content_only_revision", "snapshot_and_docs", "runtime_render_determinism",
"tht_config_check", "negative_schema_cases", "negative_context_case",
"no_p1_scope_artifacts", "secret_scan", "cleanup_confinement",
]);
const TOPOLOGY = [
"remote.git", "author", "installation/registry", "installation/data", "installation/runtime",
"fixture-secrets", "fixtures/descriptors", "fixtures/requests", "requests", "responses",
"exports/raw", "exports/extracted", "rendered", "logs",
];
const ZIP_FILES = ["manifest.json", "workspace.yaml", "contract.env.example", "README.md"];
const MAX_OUTPUT = 1024 * 1024;
const modulePath = fileURLToPath(import.meta.url);
const defaultRepositoryRoot = realpathSync(resolve(dirname(modulePath), "../.."));
function nowIso() { return new Date().toISOString(); }
function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
export function scalarSecretBytes(value) {
if (typeof value !== "string" || value.length === 0 || /\s|\0/.test(value)) throw new Error("scalar fixture secret is invalid");
return Buffer.from(value);
}
function canonicalRoot(repositoryRoot) { return realpathSync(repositoryRoot); }
export function canonicalIntegrationBase(repositoryRoot) {
return join(canonicalRoot(repositoryRoot), ".artifacts", "p1-integration");
}
export function validateRunRoot(repositoryRoot, runRoot, runId) {
if (!RUN_ID.test(runId)) throw new Error("invalid owned run id");
const base = canonicalIntegrationBase(repositoryRoot);
const lexical = resolve(runRoot);
if (dirname(lexical) !== base || basename(lexical) !== runId) throw new Error("run root is not a direct integration child");
return lexical;
}
function validateNoSymlinkAncestors(repositoryRoot, target) {
const repo = canonicalRoot(repositoryRoot);
const rel = relative(repo, target);
if (rel.startsWith("..") || isAbsolute(rel)) throw new Error("path leaves repository");
let cursor = repo;
for (const part of rel.split(sep).filter(Boolean)) {
cursor = join(cursor, part);
if (!existsSync(cursor)) break;
const entry = lstatSync(cursor);
if (entry.isSymbolicLink()) throw new Error("owned path ancestor is a symlink");
}
}
async function atomicWrite(path, bytes, mode = 0o600) {
await mkdir(dirname(path), { recursive: true });
const staging = join(dirname(path), `.${basename(path)}.${randomBytes(16).toString("hex")}.tmp`);
let handle;
try {
handle = await open(staging, "wx", mode);
await handle.writeFile(bytes);
await handle.sync();
await handle.close(); handle = undefined;
await rename(staging, path);
const directory = openSync(dirname(path), fsConstants.O_RDONLY);
try { fsyncSync(directory); } finally { closeSync(directory); }
} catch (error) {
if (handle) await handle.close().catch(() => {});
await rm(staging, { force: true });
throw error;
}
}
function exactOwnedResources(run) {
const contextual = join(run.root, "installation", "runtime", "contextual");
return [
run.root,
join(run.root, "remote.git"),
join(run.root, "author"),
join(run.root, "installation", "registry"),
join(run.root, "installation", "data"),
join(run.root, "installation", "runtime"),
contextual,
join(contextual, "remote.git"),
join(contextual, "author"),
join(contextual, "registry"),
join(contextual, "data"),
join(contextual, "runtime"),
];
}
function initialListeners(pid) {
return ["primary", "contextual"].map((name) => ({
name, kind: "fastify", host: "127.0.0.1", requestedPort: 0, pid, state: "not_started",
}));
}
function ownership(run, listeners = run.listeners) {
return {
schemaVersion: 1, runId: run.runId, runNonce: run.nonce, root: run.root,
repositoryRoot: run.repositoryRoot, startedAt: run.startedAt, pid: run.pid,
listeners,
resources: exactOwnedResources(run),
};
}
async function writeOwnership(run, listenerUpdate) {
if (listenerUpdate) {
run.listeners = run.listeners.map((listener) => listener.name === listenerUpdate.name ? listenerUpdate : listener);
}
await atomicWrite(join(run.root, "ownership.json"), `${JSON.stringify(ownership(run), null, 2)}\n`);
}
export async function createOwnedRun({ repositoryRoot, runId, nonce, now, pid } = {}) {
const repo = canonicalRoot(repositoryRoot);
const base = canonicalIntegrationBase(repo);
validateNoSymlinkAncestors(repo, base);
await mkdir(join(repo, ".artifacts"), { mode: 0o700 }).catch((error) => { if (error.code !== "EEXIST") throw error; });
await mkdir(base, { mode: 0o700 }).catch((error) => { if (error.code !== "EEXIST") throw error; });
validateNoSymlinkAncestors(repo, base);
const id = runId ?? `p1-${randomBytes(16).toString("hex")}`;
const root = validateRunRoot(repo, join(base, id), id);
const run = {
repositoryRoot: repo, root, runId: id, nonce: nonce ?? randomBytes(32).toString("hex"),
startedAt: now ?? nowIso(), pid: pid ?? process.pid,
listeners: initialListeners(pid ?? process.pid),
};
if (!HEX64.test(run.nonce) || !ISO_UTC.test(run.startedAt)) throw new Error("invalid ownership identity");
await mkdir(root, { mode: 0o700 });
await writeOwnership(run);
return run;
}
function strictOwnership(value, run, expectedNonce) {
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("ownership is malformed");
const validListeners = Array.isArray(value.listeners) && value.listeners.length === 2
&& value.listeners.every((listener, index) => {
const expectedName = ["primary", "contextual"][index];
const common = listener?.name === expectedName && listener.kind === "fastify" && listener.host === "127.0.0.1"
&& listener.requestedPort === 0 && listener.pid === process.pid && ["not_started", "listening", "closed"].includes(listener.state);
return common && (listener.state === "not_started"
? !("actualPort" in listener)
: Number.isInteger(listener.actualPort) && listener.actualPort >= 1 && listener.actualPort <= 65535);
});
if (value.schemaVersion !== 1 || value.runId !== run.runId || value.runNonce !== expectedNonce
|| value.root !== run.root || value.repositoryRoot !== run.repositoryRoot || value.pid !== process.pid
|| !ISO_UTC.test(value.startedAt ?? "") || !validListeners
|| JSON.stringify(value.resources) !== JSON.stringify(exactOwnedResources(run))) throw new Error("ownership identity mismatch");
return value;
}
export async function readAndValidateOwnership({ repositoryRoot, runRoot, expectedNonce }) {
const repo = canonicalRoot(repositoryRoot);
const id = basename(resolve(runRoot));
const lexical = validateRunRoot(repo, runRoot, id);
const rootEntry = await lstat(lexical);
if (!rootEntry.isDirectory() || rootEntry.isSymbolicLink()) throw new Error("owned run root is not a directory");
if (await realpath(lexical) !== lexical) throw new Error("owned run root is not canonical");
const ownershipPath = join(lexical, "ownership.json");
const ownershipEntry = await lstat(ownershipPath);
if (!ownershipEntry.isFile() || ownershipEntry.isSymbolicLink()) throw new Error("ownership file is unsafe");
let value;
try { value = JSON.parse(await readFile(ownershipPath, "utf8")); } catch { throw new Error("ownership is malformed"); }
return strictOwnership(value, {
repositoryRoot: repo, root: lexical, runId: id, nonce: expectedNonce,
startedAt: value.startedAt, pid: process.pid,
}, expectedNonce);
}
export async function cleanupOwnedRun({ repositoryRoot, runRoot, expectedNonce }) {
const value = await readAndValidateOwnership({ repositoryRoot, runRoot, expectedNonce });
const base = canonicalIntegrationBase(repositoryRoot);
const tombstone = join(base, `.deleting-${value.runId}-${expectedNonce.slice(0, 16)}`);
await rename(runRoot, tombstone);
await rm(tombstone, { recursive: true });
}
export async function finalizeOwnedRun({ run, success, keep }) {
if (!success || keep) return false;
await cleanupOwnedRun({ repositoryRoot: run.repositoryRoot, runRoot: run.root, expectedNonce: run.nonce });
return true;
}
export async function runCommand(options) {
if (!options || typeof options !== "object" || Array.isArray(options)) throw new Error("command requires an options object");
const allowed = new Set(["executable", "argv", "cwd", "env", "timeoutMs", "stdin", "maxOutputBytes"]);
for (const key of Object.keys(options)) if (!allowed.has(key)) throw new Error(`unsupported command option ${key}`);
const { executable, argv, cwd, env, timeoutMs = 30_000, stdin, maxOutputBytes = MAX_OUTPUT } = options;
if (typeof executable !== "string" || executable.length === 0 || /[;&|`$><\n\r]/.test(executable)) throw new Error("command executable is invalid");
const allowlistedExecutable = executable === "git" || (isAbsolute(executable) && basename(executable) === "tht");
if (!allowlistedExecutable) throw new Error("command executable is not allowlisted");
if (!Array.isArray(argv) || argv.some((value) => typeof value !== "string")) throw new Error("command argv must be a string array");
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000) throw new Error("command timeout is invalid");
return await new Promise((resolvePromise, reject) => {
const child = execFile(executable, argv, { cwd, env, timeout: timeoutMs, maxBuffer: maxOutputBytes, encoding: "utf8" }, (error, stdout, stderr) => {
const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
const result = { code, stdout: stdout ?? "", stderr: stderr ?? "" };
if (commandEventSink) commandEventSink.push({
executable: basename(executable),
argvLabels: argv.map((value) => isAbsolute(value) || value.includes(sep) ? "[path]" : /^[a-z]+:\/\//i.test(value) ? "[url]" : value.length > 80 ? "[value]" : value),
outcome: error ? "FAIL" : "PASS",
...(activeCommandCheckId ? { checkId: activeCommandCheckId } : {}),
});
if (error) Object.assign(error, { result });
error ? reject(error) : resolvePromise(result);
});
if (stdin !== undefined) { child.stdin.end(stdin); }
});
}
async function git(argv, options = {}) { return await runCommand({ executable: "git", argv, ...options }); }
async function tht(executable, argv, options = {}) { return await runCommand({ executable, argv, ...options }); }
function safeArtifactPath(path) {
if (typeof path !== "string" || !SAFE_RELATIVE.test(path) || path.startsWith(".") || path.includes("//")) throw new Error("unsafe artifact path");
return path;
}
async function fileArtifact(runRoot, path) {
safeArtifactPath(path);
return { path, sha256: sha256(await readFile(join(runRoot, path))) };
}
function forbiddenKey(value) {
if (!value || typeof value !== "object") return false;
if (Array.isArray(value)) return value.some(forbiddenKey);
for (const [key, nested] of Object.entries(value)) {
if (/^(attempt|attempts|retry|retries)$/i.test(key) || forbiddenKey(nested)) return true;
}
return false;
}
export function deriveOverall(checks) { return checks.length > 0 && checks.every(({ status }) => status === "PASS") ? "PASS" : "FAIL"; }
function hasExactCheckIds(checks) {
return checks.length === CHECK_IDS.length && checks.every(({ id }, index) => id === CHECK_IDS[index]);
}
export function validateReport(report) {
if (!report || report.schemaVersion !== 1 || !RUN_ID.test(report.runId ?? "") || !ISO_UTC.test(report.startedAt ?? "")
|| !ISO_UTC.test(report.finishedAt ?? "") || typeof report.command !== "string" || forbiddenKey(report)
|| !Array.isArray(report.checks) || !hasExactCheckIds(report.checks)) throw new Error("report is invalid");
const ids = new Set();
const artifactPaths = new Set();
for (const check of report.checks) {
if (!check || !/^[a-z0-9_]+$/.test(check.id ?? "") || ids.has(check.id) || !["PASS", "FAIL"].includes(check.status)
|| !ISO_UTC.test(check.startedAt ?? "") || !ISO_UTC.test(check.finishedAt ?? "")
|| !Array.isArray(check.commands) || check.commands.some((name) => !COMMAND.test(name))
|| !Array.isArray(check.artifacts) || check.artifacts.some(({ path, sha256 }) => {
try { safeArtifactPath(path); } catch { return true; }
return !HEX64.test(sha256 ?? "");
})) throw new Error("report check is invalid");
ids.add(check.id);
for (const artifact of check.artifacts) {
if (artifactPaths.has(artifact.path)) throw new Error("report artifact path is duplicated");
artifactPaths.add(artifact.path);
}
}
if (report.overall !== deriveOverall(report.checks)) throw new Error("report overall is not derived");
return report;
}
function renderReportMarkdown(report) {
validateReport(report);
const rows = report.checks.map((check) => `| ${check.id} | ${check.status} |`).join("\n");
return `# P1 automated integration\n\nRun: \`${report.runId}\`\n\n| Check | Status |\n|---|---|\n${rows}\n\nautomated integration: ${report.overall}\nmanual acceptance: PENDING\n`;
}
function containsAny(bytes, forbiddenValues) {
return forbiddenValues.some((value) => value && bytes.includes(Buffer.from(value)));
}
async function walkFiles(root, current = root, out = []) {
for (const entry of await readdir(current, { withFileTypes: true })) {
const path = join(current, entry.name);
const rel = relative(root, path).split(sep).join("/");
if (entry.isSymbolicLink()) continue;
if (entry.isDirectory()) {
if (rel === "fixture-secrets") continue;
await walkFiles(root, path, out);
} else if (entry.isFile()) out.push({ path, rel });
}
return out;
}
async function gitObjectFindings(runRoot, forbiddenValues, expectedGitRepositories) {
const findings = [];
for (const rel of expectedGitRepositories) {
const directory = join(runRoot, rel);
if (!existsSync(directory)) throw new Error(`Git secret scan failed closed: missing expected Git repository: ${rel}`);
const args = basename(directory) === "remote.git" ? ["--git-dir", directory] : ["-C", directory];
let objects;
try {
objects = (await git([...args, "rev-list", "--objects", "--all"])).stdout.trim().split("\n").filter(Boolean);
} catch { throw new Error(`Git secret scan failed closed during enumeration: ${basename(directory)}`); }
for (const line of objects) {
const oid = line.split(" ", 1)[0];
let type; let bytes;
try {
type = (await git([...args, "cat-file", "-t", oid])).stdout.trim();
if (!/^(blob|tree|commit|tag)$/.test(type)) throw new Error("invalid object type");
if (type !== "blob") continue;
bytes = Buffer.from((await git([...args, "cat-file", "blob", oid], { maxOutputBytes: 16 * 1024 * 1024 })).stdout);
} catch { throw new Error(`Git secret scan failed closed during object inspection: ${basename(directory)}:${oid}`); }
if (containsAny(bytes, forbiddenValues)) findings.push({ path: `git-object:${basename(directory)}:${oid}` });
}
}
return findings;
}
export async function scanSecrets({ runRoot, forbiddenValues, virtualFiles = [], expectedGitRepositories = ["remote.git", "author"] }) {
const values = forbiddenValues.filter((value) => typeof value === "string" && value.length >= 8);
const findings = [];
for (const file of await walkFiles(runRoot)) if (containsAny(await readFile(file.path), values)) findings.push({ path: file.rel });
for (const file of virtualFiles) if (containsAny(Buffer.from(file.bytes), values)) findings.push({ path: file.path });
findings.push(...await gitObjectFindings(runRoot, values, expectedGitRepositories));
return findings;
}
export function negativeRequestEvidence(caseLabel, expectedInputField) {
if (!/^[a-z0-9-]+$/.test(caseLabel) || !/^[a-z_]+(?:\.[a-z_]+)*$/.test(expectedInputField)) throw new Error("unsafe negative-case evidence");
return { case: caseLabel, expectedInputField };
}
function loopbackOrigin(value) {
const url = new URL(value);
if (url.protocol !== "http:" || url.hostname !== "127.0.0.1" || !url.port) throw new Error("owned API must be loopback HTTP");
return url.origin;
}
export function installExternalFetchGuard(ownedBaseUrl, fetchImplementation = globalThis.fetch) {
const ownedOrigin = loopbackOrigin(ownedBaseUrl);
const externalAttempts = [];
const guardedFetch = async (input, init) => {
const candidate = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
if (candidate.origin !== ownedOrigin) {
externalAttempts.push({ transport: "fetch", protocol: candidate.protocol, loopback: candidate.hostname === "127.0.0.1" });
throw new Error("external fetch prohibited");
}
return await fetchImplementation(input, init);
};
return { fetch: guardedFetch, externalAttempts };
}
function socketDestination(args) {
const first = Array.isArray(args[0]) ? args[0][0] : args[0];
if (typeof first === "object" && first !== null) {
if (first.path !== undefined) return { path: String(first.path) };
return { host: String(first.host ?? first.hostname ?? "localhost"), port: Number(first.port) };
}
if (typeof first === "number") return { host: typeof args[1] === "string" ? args[1] : "localhost", port: first };
return { path: String(first) };
}
export function installNetworkGuard(fetchImplementation = globalThis.fetch) {
if (typeof fetchImplementation !== "function") throw new Error("global fetch is unavailable");
const ownedOrigins = new Set();
const externalAttempts = [];
const originalFetch = globalThis.fetch;
const originalConnect = Socket.prototype.connect;
const isOwned = (host, port) => {
if (!Number.isInteger(port) || port < 1 || port > 65535) return false;
const normalized = host === "localhost" || host === "::1" ? "127.0.0.1" : host;
return isIP(normalized) !== 0 && normalized === "127.0.0.1" && ownedOrigins.has(`http://127.0.0.1:${port}`);
};
globalThis.fetch = async (input, init) => {
const candidate = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
if (!ownedOrigins.has(candidate.origin)) {
externalAttempts.push({ transport: "fetch", protocol: candidate.protocol, loopback: candidate.hostname === "127.0.0.1" });
throw new Error("external network connection prohibited");
}
return await fetchImplementation(input, init);
};
Socket.prototype.connect = function guardedSocketConnect(...args) {
const destination = socketDestination(args);
if (!("host" in destination) || !isOwned(destination.host, destination.port)) {
externalAttempts.push({ transport: "socket", loopback: destination.host === "127.0.0.1" });
throw new Error("external network connection prohibited");
}
return originalConnect.apply(this, args);
};
let restored = false;
return {
externalAttempts,
addOwnedOrigin(value) { ownedOrigins.add(loopbackOrigin(value)); },
hasOwnedOrigin(value) { return ownedOrigins.has(loopbackOrigin(value)); },
restore() {
if (restored) return;
restored = true;
globalThis.fetch = originalFetch;
Socket.prototype.connect = originalConnect;
},
};
}
function sanitizeForEvidence(value, forbiddenValues = []) {
if (typeof value === "string") {
let safe = value;
for (const forbidden of forbiddenValues) if (forbidden) safe = safe.split(forbidden).join("[REDACTED]");
return safe.length > 16_384 ? `${safe.slice(0, 16_384)}[TRUNCATED]` : safe;
}
if (Array.isArray(value)) return value.map((item) => sanitizeForEvidence(item, forbiddenValues));
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, sanitizeForEvidence(nested, forbiddenValues)]));
return value;
}
async function evidence(run, path, value, forbiddenValues = []) {
const safe = sanitizeForEvidence(value, forbiddenValues);
await atomicWrite(join(run.root, path), `${JSON.stringify(safe, null, 2)}\n`);
return await fileArtifact(run.root, path);
}
export async function executeChecks({ checks, failAt, recorder } = {}) {
if (!Array.isArray(checks) || !hasExactCheckIds(checks)) throw new Error("scenarios must match the exact ordered check set");
if (failAt !== undefined && !CHECK_IDS.includes(failAt)) throw new Error("failure hook must name an exact check");
const results = [];
let stopped = false;
for (const scenario of checks) {
const startedAt = nowIso();
let result;
if (stopped) {
result = { id: scenario.id, status: "FAIL", startedAt, finishedAt: nowIso(), commands: [], artifacts: [], error: "Not executed after earlier failure." };
} else {
try {
const output = await scenario.run();
if (scenario.id === failAt) {
const injected = new Error("injected acceptance failure");
injected.acceptancePartial = { commands: output.commands ?? [], artifacts: output.artifacts ?? [] };
throw injected;
}
result = { id: scenario.id, status: "PASS", startedAt, finishedAt: nowIso(), commands: output.commands ?? [], artifacts: output.artifacts ?? [] };
} catch (error) {
const partial = error?.acceptancePartial ?? {};
result = { id: scenario.id, status: "FAIL", startedAt, finishedAt: nowIso(), commands: partial.commands ?? [], artifacts: partial.artifacts ?? [], error: "Acceptance scenario failed safely." };
stopped = true;
}
}
results.push(result);
if (recorder) await recorder(result);
}
return results;
}
function baseWorkspace(id, evidenceSource) {
return {
workspace: { schema_version: 3, id, name: `P1 ${id}`, language: "en" },
dwh: { engine: "postgres", database: "postgres", schema: "public", supported_transports: ["postgres_direct"] },
semantic_index: {
vector_store: { engine: "qdrant", collection: id, dimensions: 1024, distance: "cosine" },
embedding: { provider: "ollama_internal", model: "qwen3-embedding:0.6b", dimensions: 1024 },
},
llm_policy: { allowed: ["zai/glm-5.2"] },
evidence: { source: evidenceSource, policy: { max_chunk_chars: 4000, retain_published_generations: 3 } },
};
}
function descriptors() {
return [
baseWorkspace("p1-filesystem", { type: "filesystem", uri: "workspace-content/p1-filesystem/evidence", patterns: ["**/*.md"], max_bytes: 10485760 }),
baseWorkspace("p1-http", { type: "http", uris: ["https://evidence.example.test/guide.md"], authentication: "signed_urls_file", connect_timeout_ms: 1250, read_timeout_ms: 30001, max_bytes: 12345, max_redirects: 2, allow_private_hosts: false, max_cache_bytes: 67890 }),
baseWorkspace("p1-s3", { type: "s3", uri: "s3://p1-evidence/published/", endpoint_url: "https://s3.example.test/", region: "eu-west-1", credentials: "static_files", trusted_endpoint: true, allow_private_endpoint: false, allow_insecure_endpoint: false, max_bytes: 12345, max_objects: 33, max_pages: 4, page_size: 5 }),
];
}
function namespace(id) { return id.toUpperCase().replaceAll("-", "_"); }
async function createTopology(run) {
for (const path of TOPOLOGY) await mkdir(join(run.root, path), { recursive: true, mode: path === "fixture-secrets" ? 0o700 : 0o755 });
}
async function setupSecrets(ctx) {
const secretDir = join(ctx.run.root, "fixture-secrets");
const values = {
dwh: `DWH-${randomBytes(16).toString("hex")}`,
signed: `SIGNED-${randomBytes(16).toString("hex")}`,
access: `ACCESS-${randomBytes(16).toString("hex")}`,
secret: `SECRET-${randomBytes(16).toString("hex")}`,
session: `SESSION-${randomBytes(16).toString("hex")}`,
rejected: `REJECTED-${randomBytes(16).toString("hex")}`,
};
ctx.forbiddenValues = Object.values(values);
ctx.secretValues = values;
const paths = {
dwh: join(secretDir, "dwh-password"), signed: join(secretDir, "evidence-signed-urls.json"),
access: join(secretDir, "evidence-access"), secret: join(secretDir, "evidence-secret"), session: join(secretDir, "evidence-session"),
};
await atomicWrite(paths.dwh, scalarSecretBytes(values.dwh));
await atomicWrite(paths.signed, JSON.stringify([`https://evidence.example.test/guide.md?token=${values.signed}`]));
await atomicWrite(paths.access, scalarSecretBytes(values.access));
await atomicWrite(paths.secret, scalarSecretBytes(values.secret));
await atomicWrite(paths.session, scalarSecretBytes(values.session));
const env = {};
for (const workspace of ctx.descriptors) {
const ns = namespace(workspace.workspace.id); const prefix = `THT_WS_${ns}`;
Object.assign(env, {
[`${prefix}_DWH_TRANSPORT`]: "postgres_direct", [`${prefix}_DWH_HOST`]: "dwh.invalid",
[`${prefix}_DWH_PORT`]: "5432", [`${prefix}_DWH_USER`]: "reader", [`${prefix}_DWH_PASSWORD_FILE`]: paths.dwh,
});
}
Object.assign(env, {
THT_WS_P1_HTTP_EVIDENCE_SIGNED_URLS_FILE: paths.signed,
THT_WS_P1_S3_EVIDENCE_ACCESS_KEY_FILE: paths.access,
THT_WS_P1_S3_EVIDENCE_SECRET_KEY_FILE: paths.secret,
THT_WS_P1_S3_EVIDENCE_SESSION_TOKEN_FILE: paths.session,
});
Object.assign(ctx.env, env);
env.THT_WORKSPACE_SECRET_ROOTS = secretDir;
await atomicWrite(join(ctx.run.root, "installation", "bindings.env"), `${Object.entries(env).map(([key, value]) => `${key}=${value}`).join("\n")}\n`);
await atomicWrite(join(ctx.run.root, "installation", "runtime", "base.yaml"), "{}\n");
}
async function initializeGit(ctx) {
await git(["init", "--bare", "--initial-branch=main", join(ctx.run.root, "remote.git")], { cwd: ctx.run.root });
await git(["clone", join(ctx.run.root, "remote.git"), join(ctx.run.root, "author")], { cwd: ctx.run.root });
await git(["config", "user.name", "P1 Fixture Curator"], { cwd: join(ctx.run.root, "author") });
await git(["config", "user.email", "p1-curator@example.invalid"], { cwd: join(ctx.run.root, "author") });
const evidenceRoot = join(ctx.run.root, "author", "workspace-content", "p1-filesystem", "evidence");
await mkdir(join(evidenceRoot, "domain"), { recursive: true });
await writeFile(join(evidenceRoot, "guide.md"), "# P1 curated Evidence\n");
await writeFile(join(evidenceRoot, "domain", "table.md"), "# Curated table\n");
await git(["add", "workspace-content"], { cwd: join(ctx.run.root, "author") });
await git(["commit", "-m", "Bootstrap curated P1 content"], { cwd: join(ctx.run.root, "author") });
await git(["push", "origin", "main"], { cwd: join(ctx.run.root, "author") });
ctx.bootstrapCommit = (await git(["rev-parse", "HEAD"], { cwd: join(ctx.run.root, "author") })).stdout.trim();
}
async function loadProductionBackend() {
const [{ loadConfig }, { buildApp }, { WorkspaceRegistry }, { ThtRunner }] = await Promise.all([
import("../dist/config.js"), import("../dist/app.js"), import("../dist/workspaces/registry.js"), import("../dist/tht/tht-runner.js"),
]);
return { loadConfig, buildApp, WorkspaceRegistry, ThtRunner };
}
async function startProductionBackend(ctx, { name, env, runtimeConfigPath }) {
const { loadConfig, buildApp, WorkspaceRegistry, ThtRunner } = await loadProductionBackend();
const config = loadConfig(env);
const registry = new WorkspaceRegistry(config.workspaceRegistry);
const thtRunner = new ThtRunner({
thtBin: config.thtBin, harnessDir: config.harnessDir, configPath: runtimeConfigPath,
dataRoot: config.dataRoot, runtimeSnapshotRoot: join(config.workspaceRegistry.root, "snapshots", "runtime"),
secretRoots: config.workspaceRegistry.secretRoots, secretsFile: config.secretsFile, secretFiles: config.secretFiles,
semanticRuntime: { internalQdrantUrl: config.internalQdrantUrl, internalEmbeddingUrl: config.internalEmbeddingUrl,
internalEmbeddingModel: config.internalEmbeddingModel, internalEmbeddingDimensions: config.internalEmbeddingDimensions },
});
const app = buildApp(config, { thtRunner, workspaceRegistry: registry });
let address;
try {
address = await app.listen({ host: "127.0.0.1", port: 0 });
} catch (error) {
await app.close().catch(() => {});
throw error;
}
const url = new URL(address);
const baseUrl = `http://127.0.0.1:${url.port}`;
ctx.networkGuard.addOwnedOrigin(baseUrl);
const service = { name, app, baseUrl, registry, thtRunner, config };
ctx.services.push(service);
await writeOwnership(ctx.run, {
name, kind: "fastify", host: "127.0.0.1", requestedPort: 0,
actualPort: Number(url.port), pid: process.pid, state: "listening",
});
return service;
}
async function startBackend(ctx) {
const service = await startProductionBackend(ctx, {
name: "primary", env: ctx.env,
runtimeConfigPath: join(ctx.run.root, "installation", "runtime", "base.yaml"),
});
ctx.registryConfig = service.config.workspaceRegistry;
ctx.registry = service.registry;
ctx.thtRunner = service.thtRunner;
ctx.app = service.app;
ctx.baseUrl = service.baseUrl;
}
export function exportArchiveEvidencePath(requestId) {
if (!/^export-[a-z0-9-]+$/.test(requestId)) throw new Error("invalid export request id");
return `exports/raw/${requestId}.zip`;
}
function trackArtifact(ctx, artifact) {
if (ctx.activeArtifacts && !ctx.activeArtifacts.some(({ path }) => path === artifact.path)) ctx.activeArtifacts.push(artifact);
return artifact;
}
async function request(ctx, id, method, path, body, binary = false, requestEvidence, baseUrl = ctx.baseUrl) {
const requestSummary = requestEvidence === undefined
? { method, path, ...(body === undefined ? {} : { body: sanitizeForEvidence(body, ctx.forbiddenValues) }) }
: { method, path, input: requestEvidence };
trackArtifact(ctx, await evidence(ctx.run, `requests/${id}.json`, requestSummary, ctx.forbiddenValues));
ctx.httpRequests.push({ method, path });
const response = await globalThis.fetch(`${baseUrl}${path}`, {
method, headers: body === undefined ? {} : { "content-type": "application/json" },
...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(15_000),
});
if (binary) {
const bytes = Buffer.from(await response.arrayBuffer());
await atomicWrite(join(ctx.run.root, exportArchiveEvidencePath(id)), bytes);
trackArtifact(ctx, await evidence(ctx.run, `responses/${id}.json`, { status: response.status, contentType: response.headers.get("content-type"), bytes: bytes.length }));
return { status: response.status, bytes };
}
const text = await response.text(); let parsed;
try { parsed = text ? JSON.parse(text) : null; } catch { parsed = { invalidJson: true }; }
const safe = sanitizeForEvidence(parsed, ctx.forbiddenValues);
trackArtifact(ctx, await evidence(ctx.run, `responses/${id}.json`, { status: response.status, body: safe }, ctx.forbiddenValues));
return { status: response.status, body: parsed };
}
async function extractZip(ctx, id, bytes) {
const yauzl = (await import("yauzl")).default;
const output = join(ctx.run.root, "exports", "extracted", id); await mkdir(output, { recursive: true });
const files = await new Promise((resolvePromise, reject) => {
yauzl.fromBuffer(bytes, { lazyEntries: true, strictFileNames: true, validateEntrySizes: true }, (error, zip) => {
if (error || !zip) return reject(error ?? new Error("zip open failed"));
const collected = new Map(); let total = 0;
zip.on("error", reject); zip.on("end", () => resolvePromise(collected));
zip.on("entry", (entry) => {
const type = (entry.externalFileAttributes >>> 16) & 0o170000;
if (!ZIP_FILES.includes(entry.fileName) || entry.fileName.includes("\\") || entry.fileName.includes("..") || entry.fileName.startsWith("/") || entry.fileName.endsWith("/") || type === 0o120000 || collected.has(entry.fileName) || entry.uncompressedSize > 2_000_000) return reject(new Error("unsafe export entry"));
zip.openReadStream(entry, (streamError, stream) => {
if (streamError || !stream) return reject(streamError ?? new Error("zip stream failed"));
const chunks = []; stream.on("data", (chunk) => { total += chunk.length; if (total > 8_000_000) reject(new Error("export too large")); else chunks.push(chunk); });
stream.on("end", () => { collected.set(entry.fileName, Buffer.concat(chunks)); zip.readEntry(); }); stream.on("error", reject);
});
});
zip.readEntry();
});
});
assert(files.size === ZIP_FILES.length, "export file allowlist mismatch");
const manifest = JSON.parse(files.get("manifest.json").toString("utf8"));
assert(manifest.schema_version === 1 && manifest.workspace_id === id, "export manifest identity mismatch");
for (const name of ZIP_FILES.slice(1)) assert(sha256(files.get(name)) === manifest.files[name], `export hash mismatch ${name}`);
for (const [name, contents] of files) await atomicWrite(join(output, name), contents, 0o600);
return manifest;
}
function assert(condition, message) { if (!condition) throw new Error(message); }
function assertGenericWorkspaceInvalid(response, label) {
assert(response.status === 400 && response.body?.code === "workspace_invalid", `${label} was not rejected through HTTP`);
assert(Object.keys(response.body).sort().join(",") === "code,message", `${label} response envelope was not exact`);
assert(response.body.message === "Workspace request or bundle is invalid.", `${label} response message was not generic`);
assert(!/fatal:|stderr|git command|rev-parse|ls-tree/i.test(JSON.stringify(response.body)), `${label} exposed Git stderr`);
}
async function snapshotDigest(path) {
const files = await walkFiles(path); const result = {};
for (const file of files) result[file.rel] = sha256(await readFile(file.path));
return result;
}
const SAFE_AMBIENT_ENV = Object.freeze(["PATH", "HOME", "LANG", "LC_ALL", "TMPDIR", "TZ", "NODE_EXTRA_CA_CERTS"]);
export function buildSafeEnvironment({ ambient = process.env, fixture = {} } = {}) {
const safe = {};
for (const key of SAFE_AMBIENT_ENV) if (typeof ambient[key] === "string") safe[key] = ambient[key];
for (const [key, value] of Object.entries(fixture)) {
if (typeof value !== "string") throw new Error(`fixture environment value must be a string: ${key}`);
safe[key] = value;
}
return safe;
}
async function setupContext(run, repositoryRoot, env, ctx = {}) {
const thtBin = realpathSync(env.THT_BIN ?? join(repositoryRoot, "harness", ".venv", "bin", "tht"));
const harnessDir = realpathSync(join(repositoryRoot, "harness"));
const gitTracePath = join(run.root, "logs", "production-git-trace.jsonl");
const fixtureEnv = {
HOST: "127.0.0.1", PORT: "0", AUTH_MODE: "none", THT_BIN: thtBin,
THT_HARNESS_DIR: harnessDir, THT_DATA_ROOT: join(run.root, "installation", "data"),
SETTINGS_FILE: join(run.root, "installation", "data", "settings.json"),
MAINTENANCE_STATE_FILE: join(run.root, "installation", "data", "maintenance.json"),
THT_WORKSPACE_REGISTRY_ROOT: join(run.root, "installation", "registry"),
THT_WORKSPACE_GIT_REMOTE: join(run.root, "remote.git"), THT_WORKSPACE_GIT_BRANCH: "main",
THT_WORKSPACE_GIT_AUTHOR_NAME: "P1 API Publisher", THT_WORKSPACE_GIT_AUTHOR_EMAIL: "p1-api@example.invalid",
THT_WORKSPACE_INSTALLATION_ID: "p1-acceptance", THT_WORKSPACE_SECRET_ROOTS: join(run.root, "fixture-secrets"),
THT_HOME: join(run.root, "installation", "runtime", "tht-home"),
GIT_TRACE2_EVENT: gitTracePath,
};
Object.assign(ctx, {
run, repositoryRoot, descriptors: descriptors(), forbiddenValues: ctx.forbiddenValues ?? [],
env: buildSafeEnvironment({ ambient: env, fixture: fixtureEnv }),
httpRequests: [], services: [], gitTracePath,
expectedGitRepositories: ["remote.git", "author"],
});
await createTopology(run);
await setupSecrets(ctx);
return ctx;
}
async function treeHash(path, excludedPrefixes = []) {
const digest = await snapshotDigest(path);
for (const key of Object.keys(digest)) if (excludedPrefixes.some((prefix) => key === prefix || key.startsWith(`${prefix}/`))) delete digest[key];
return sha256(JSON.stringify(digest));
}
async function checkoutSemanticState(path) {
const head = await git(["rev-parse", "HEAD"], { cwd: path });
const branch = await git(["symbolic-ref", "--short", "HEAD"], { cwd: path });
const statusResult = await git(["status", "--porcelain=v1"], { cwd: path });
const indexTree = await git(["write-tree"], { cwd: path });
const refs = await git(["show-ref"], { cwd: path });
return {
head: head.stdout.trim(), branch: branch.stdout.trim(), status: statusResult.stdout,
indexTree: indexTree.stdout.trim(), refs: sha256(refs.stdout),
worktree: await treeHash(path, [".git"]),
};
}
async function bareSemanticState(path, branch) {
const [head, tree, refs] = await Promise.all([
git(["--git-dir", path, "rev-parse", `refs/heads/${branch}`]),
git(["--git-dir", path, "rev-parse", `refs/heads/${branch}^{tree}`]),
git(["--git-dir", path, "show-ref"]),
]);
return { head: head.stdout.trim(), tree: tree.stdout.trim(), refs: sha256(refs.stdout) };
}
async function primarySemanticState(ctx) {
return {
remote: await bareSemanticState(join(ctx.run.root, "remote.git"), "main"),
author: await checkoutSemanticState(join(ctx.run.root, "author")),
checkout: await checkoutSemanticState(join(ctx.run.root, "installation", "registry", "repo")),
active: await treeHash(join(ctx.run.root, "installation", "registry", "state")),
snapshots: await treeHash(join(ctx.run.root, "installation", "registry", "snapshots")),
data: await treeHash(join(ctx.run.root, "installation", "data")),
runtime: await treeHash(join(ctx.run.root, "installation", "runtime"), ["contextual"]),
};
}
async function registryState(ctx) {
return await primarySemanticState(ctx);
}
async function contextualSemanticState(root) {
return {
remote: await bareSemanticState(join(root, "remote.git"), "invalid-context"),
author: await checkoutSemanticState(join(root, "author")),
checkout: await checkoutSemanticState(join(root, "registry", "repo")),
active: await treeHash(join(root, "registry", "state")),
snapshots: await treeHash(join(root, "registry", "snapshots")),
data: await treeHash(join(root, "data")),
runtime: await treeHash(join(root, "runtime")),
};
}
async function invariantArtifact(ctx, path, value) {
return trackArtifact(ctx, await evidence(ctx.run, path, value, ctx.forbiddenValues));
}
function assertByteIdentical(left, right, label) {
assert(JSON.stringify(left) === JSON.stringify(right), `${label} state changed`);
}
async function currentSnapshotManifest(ctx, commit) {
const path = join(ctx.run.root, "installation", "registry", "snapshots", commit, "snapshot.json");
const manifest = JSON.parse(await readFile(path, "utf8"));
assert(manifest.head === commit, "snapshot manifest head mismatch");
return { path, manifest };
}
function revisionFromManifest(manifest, id) {
const revision = manifest.revisions.find((candidate) => candidate.id === id);
assert(revision, `manifest revision absent ${id}`);
return revision;
}
function renderedRoot(parsed) { return parsed.evidence.sources[0].root; }
function assertRuntimeContract(ctx, id, parsed, revision) {
assert(parsed.runtime_identity.workspace_id === id, "runtime workspace identity mismatch");
assert(parsed.runtime_identity.workspace_revision === revision.commit, "runtime revision mismatch");
assert(parsed.runtime_identity.source_identity === `workspace://${id}`, "runtime source identity mismatch");
assert(parsed.vector.max_chunk_chars === 4000 && parsed.vector.retain_published_generations === 3, "runtime policy mismatch");
const source = parsed.evidence.sources[0];
if (id === "p1-filesystem") {
const exactRoot = join(dirname(revision.snapshotPath), "workspace-content", id, "evidence");
assert(source.type === "filesystem" && source.root === exactRoot, "filesystem root mismatch");
assert(JSON.stringify(source.patterns) === JSON.stringify(["**/*.md"]) && source.max_bytes === 10485760, "filesystem source contract mismatch");
assert(!existsSync(source.root), "filesystem Evidence root was materialized");
} else if (id === "p1-http") {
assert(source.type === "http", "HTTP source type mismatch");
assert(JSON.stringify(source.provenance_urls) === JSON.stringify(["https://evidence.example.test/guide.md"]), "HTTP provenance mismatch");
assert(source.signed_urls_file === join(ctx.run.root, "fixture-secrets", "evidence-signed-urls.json"), "HTTP binding mismatch");
assert(source.connect_timeout === 1.25 && source.read_timeout === 30.001 && source.max_bytes === 12345
&& source.max_redirects === 2 && source.allow_private_hosts === false && source.max_cache_bytes === 67890, "HTTP limits mismatch");
} else if (id === "p1-s3") {
assert(source.type === "s3" && source.bucket === "p1-evidence" && source.prefix === "published/", "S3 identity mismatch");
assert(source.endpoint_url === "https://s3.example.test/" && source.region === "eu-west-1", "S3 endpoint mismatch");
assert(source.access_key_file === join(ctx.run.root, "fixture-secrets", "evidence-access")
&& source.secret_key_file === join(ctx.run.root, "fixture-secrets", "evidence-secret")
&& source.session_token_file === join(ctx.run.root, "fixture-secrets", "evidence-session"), "S3 bindings mismatch");
assert(source.trusted_endpoint === true && source.allow_private_endpoint === false && source.allow_insecure_endpoint === false
&& source.max_bytes === 12345 && source.max_objects === 33 && source.max_pages === 4 && source.page_size === 5, "S3 limits mismatch");
}
}
async function traceSize(path) {
try { return (await stat(path)).size; } catch (error) { if (error.code === "ENOENT") return 0; throw error; }
}
function uniqueArtifacts(artifacts) {
const seen = new Set();
return artifacts.filter((artifact) => !seen.has(artifact.path) && seen.add(artifact.path));
}
async function commandsObservedForCheck(ctx, checkId, traceBefore) {
const commands = new Set((commandEventSink ?? []).filter((event) => event.checkId === checkId).map((event) => event.executable));
if (await traceSize(ctx.gitTracePath) > traceBefore) commands.add("git");
return [...commands].sort();
}
function wrapProductionCheck(ctx, scenario) {
return {
id: scenario.id,
run: async () => {
ctx.activeArtifacts = [];
activeCommandCheckId = scenario.id;
const traceBefore = await traceSize(ctx.gitTracePath);
try {
const output = await scenario.run();
return {
commands: await commandsObservedForCheck(ctx, scenario.id, traceBefore),
artifacts: uniqueArtifacts([...(output.artifacts ?? []), ...ctx.activeArtifacts]),
};
} catch (error) {
error.acceptancePartial = {
commands: await commandsObservedForCheck(ctx, scenario.id, traceBefore),
artifacts: uniqueArtifacts(ctx.activeArtifacts),
};
throw error;
} finally {
activeCommandCheckId = undefined;
ctx.activeArtifacts = undefined;
}
},
};
}
async function assertProductionGitTrace(ctx) {
const text = await readFile(ctx.gitTracePath, "utf8");
const events = text.split("\n").filter(Boolean).map((line) => JSON.parse(line));
const productionStarts = events.filter((event) => event.event === "start" && Array.isArray(event.argv)
&& event.argv.some((arg) => typeof arg === "string" && arg.startsWith("core.hooksPath=")));
const publication = productionStarts.some(({ argv }) => argv.includes("commit") && argv.some((arg) => /^Publish workspace /.test(arg)));
const pull = productionStarts.some(({ argv }) => argv.includes("fetch"));
assert(publication && pull, "production Git publication/pull operations absent from Trace2 evidence");
return { eventCount: events.length, productionStartCount: productionStarts.length, publication, pull };
}
async function assertNoP1ScopeEntrypoints(ctx) {
const workspacesRoot = join(ctx.repositoryRoot, "backend", "dist", "workspaces");
const modules = (await readdir(workspacesRoot)).filter((name) => name.endsWith(".js"));
const forbiddenModuleNames = modules.filter((name) => /evidence[-_.]?(?:adapter|acquisition|preprocess)|(?:acquisition|preprocess)[-_.]?evidence/i.test(name));
const forbiddenExports = [];
for (const name of modules) {
const source = await readFile(join(workspacesRoot, name), "utf8");
if (/export\s+(?:class|function|const)\s+(?:acquire|preprocess)Evidence|export\s+(?:class|function|const)\s+Evidence(?:Adapter|Acquisition|Preprocessor)/.test(source)) forbiddenExports.push(name);
}
const routeSurfaces = ctx.services.map(({ name, app }) => ({ name, routes: app.printRoutes({ commonPrefix: false }) }));
const forbiddenRoutes = routeSurfaces.filter(({ routes }) => /\/(?:evidence|acquisition|preprocess)(?:\W|$)/i.test(routes));
const packageJson = JSON.parse(await readFile(join(ctx.repositoryRoot, "backend", "package.json"), "utf8"));
const entrypointBytes = JSON.stringify({ main: packageJson.main, bin: packageJson.bin, exports: packageJson.exports, scripts: packageJson.scripts });
const forbiddenPackageEntrypoints = /(?:acquire|preprocess)Evidence|Evidence(?:Adapter|Acquisition|Preprocessor)/i.test(entrypointBytes);
assert(forbiddenModuleNames.length === 0 && forbiddenExports.length === 0 && forbiddenRoutes.length === 0 && !forbiddenPackageEntrypoints,
"prohibited P1 adapter/acquisition/preprocessing entrypoint surface present");
return { moduleFilesAudited: modules.sort(), routeAppsAudited: routeSurfaces.map(({ name }) => name), packageEntrypointsAudited: true };
}
function productionChecks(ctx) {
const log = async (id, value) => ({ commands: [], artifacts: [await evidence(ctx.run, `logs/${id}.json`, value, ctx.forbiddenValues)] });
const scenarios = [
{ id: "preflight", run: async () => {
const gitVersion = await git(["--version"]); await access(ctx.env.THT_BIN, fsConstants.X_OK);
return await log("preflight", { git: gitVersion.stdout.trim(), node: process.version, thtExecutable: true });
} },
{ id: "clean_state", run: async () => {
assert(RUN_ID.test(ctx.run.runId), "run identity invalid");
return await log("clean_state", { exclusiveRoot: true, reused: false });
} },
{ id: "ownership", run: async () => {
await readAndValidateOwnership({ repositoryRoot: ctx.repositoryRoot, runRoot: ctx.run.root, expectedNonce: ctx.run.nonce });
return await log("ownership", { valid: true, listener: "not_started" });
} },
{ id: "local_git_bootstrap", run: async () => {
await initializeGit(ctx);
const descriptorArtifacts = [];
for (const workspace of ctx.descriptors) {
const path = `fixtures/descriptors/${workspace.workspace.id}.json`;
await atomicWrite(join(ctx.run.root, path), `${JSON.stringify(workspace, null, 2)}\n`);
descriptorArtifacts.push(await fileArtifact(ctx.run.root, path));
}
assert(!existsSync(join(ctx.run.root, "author", "workspaces")), "fixture authored a descriptor");
return { artifacts: [await evidence(ctx.run, "logs/local_git_bootstrap.json", { bootstrapCommit: ctx.bootstrapCommit, descriptorEmpty: true }), ...descriptorArtifacts] };
} },
{ id: "http_validate_publish_pull_read_export", run: async () => {
await startBackend(ctx);
const status = await request(ctx, "registry-status", "GET", "/workspace-registry/status");
assert(status.status === 200 && status.body.head === ctx.bootstrapCommit, "empty registry status failed");
let base = status.body.head;
for (const workspace of ctx.descriptors) {
const id = workspace.workspace.id;
const validated = await request(ctx, `validate-${id}`, "POST", "/workspaces/validate", { workspace });
assert(validated.status === 200 && validated.body.workspace.workspace.id === id, `validation failed ${id}`);
const published = await request(ctx, `publish-${id}`, "POST", "/workspaces/publish", { action: "create", workspace, baseCommit: base });
assert(published.status === 200 && HEX40.test(published.body.revision.commit), `publication failed ${id}`);
base = published.body.revision.commit;
}
ctx.publicationHead = base;
const pulled = await request(ctx, "registry-pull", "POST", "/workspace-registry/pull");
assert(pulled.status === 200 && pulled.body.head === base, "pull failed");
const listed = await request(ctx, "workspace-list", "GET", "/workspaces");
assert(listed.status === 200 && listed.body.length === 3, "list failed");
ctx.reads = {}; ctx.exportManifests = {};
const artifacts = [];
for (const workspace of ctx.descriptors) {
const id = workspace.workspace.id; const read = await request(ctx, `read-${id}`, "GET", `/workspaces/${id}`);
assert(read.status === 200, `read failed ${id}`); ctx.reads[id] = read.body;
const exported = await request(ctx, `export-${id}`, "GET", `/workspaces/${id}/export`, undefined, true);
assert(exported.status === 200, `export failed ${id}`); ctx.exportManifests[id] = await extractZip(ctx, id, exported.bytes);
artifacts.push(await fileArtifact(ctx.run.root, exportArchiveEvidencePath(`export-${id}`)));
for (const name of ZIP_FILES) artifacts.push(await fileArtifact(ctx.run.root, `exports/extracted/${id}/${name}`));
}
artifacts.unshift(await evidence(ctx.run, "logs/http-flow.json", { workspaceIds: Object.keys(ctx.reads), head: base, realListener: true, fetch: true }));
return { commands: [], artifacts };
} },
{ id: "same_revision_git_objects", run: async () => {
const revision = ctx.reads["p1-filesystem"].revision;
ctx.oldRevision = revision; ctx.oldSnapshotDigest = await snapshotDigest(dirname(revision.snapshotPath));
const checkoutHead = (await git(["rev-parse", "HEAD"], { cwd: join(ctx.run.root, "installation", "registry", "repo") })).stdout.trim();
const manifestPath = join(dirname(revision.snapshotPath), "snapshot.json");
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
const lease = ctx.thtRunner.acquireWorkspaceRuntime(revision.snapshotPath); let rendered;
try { rendered = (await import("yaml")).parse(await readFile(lease.path, "utf8")); } finally { lease.release(); }
ctx.oldFilesystemRoot = renderedRoot(rendered);
const identities = [revision.commit, checkoutHead, manifest.head, rendered.runtime_identity.workspace_revision];
assert(new Set(identities).size === 1, "revision identities diverged");
const repo = join(ctx.run.root, "installation", "registry", "repo");
await git(["cat-file", "-e", `${revision.commit}:workspaces/p1-filesystem.yaml`], { cwd: repo });
await git(["cat-file", "-e", `${revision.commit}:workspace-content/p1-filesystem/evidence/guide.md`], { cwd: repo });
const type = (await git(["cat-file", "-t", `${revision.commit}:workspace-content/p1-filesystem/evidence`], { cwd: repo })).stdout.trim();
assert(type === "tree", "Evidence object is not a tree");
assert(!existsSync(join(dirname(revision.snapshotPath), "workspace-content")), "snapshot materialized workspace-content");
return { commands: ["git"], artifacts: [
await evidence(ctx.run, "logs/git-object-proof.json", { commit: revision.commit, checkoutHead, manifestHead: manifest.head, runtimeRevision: rendered.runtime_identity.workspace_revision, filesystemRoot: ctx.oldFilesystemRoot, evidenceType: type }),
await fileArtifact(ctx.run.root, relative(ctx.run.root, manifestPath)), await fileArtifact(ctx.run.root, relative(ctx.run.root, revision.snapshotPath)),
] };
} },
{ id: "content_only_revision", run: async () => {
const author = join(ctx.run.root, "author");
await git(["fetch", "origin", "main"], { cwd: author }); await git(["reset", "--hard", "origin/main"], { cwd: author });
const descriptorBefore = (await git(["rev-parse", "HEAD:workspaces/p1-filesystem.yaml"], { cwd: author })).stdout.trim();
await writeFile(join(author, "workspace-content", "p1-filesystem", "evidence", "guide.md"), "# P1 curated Evidence v2\n");
await git(["add", "workspace-content/p1-filesystem/evidence/guide.md"], { cwd: author }); await git(["commit", "-m", "Update curated Evidence only"], { cwd: author }); await git(["push", "origin", "main"], { cwd: author });
ctx.contentCommit = (await git(["rev-parse", "HEAD"], { cwd: author })).stdout.trim();
const pulled = await request(ctx, "content-only-pull", "POST", "/workspace-registry/pull");
assert(pulled.status === 200 && pulled.body.head === ctx.contentCommit, "content pull failed");
const currentRead = (await request(ctx, "read-filesystem-content", "GET", "/workspaces/p1-filesystem")).body;
const current = currentRead.revision;
const descriptorAfter = (await git(["rev-parse", "HEAD:workspaces/p1-filesystem.yaml"], { cwd: author })).stdout.trim();
assert(current.commit === ctx.contentCommit && current.blob === ctx.oldRevision.blob && descriptorAfter === descriptorBefore, "content revision identity failed");
assertByteIdentical(await snapshotDigest(dirname(ctx.oldRevision.snapshotPath)), ctx.oldSnapshotDigest, "old snapshot");
const lease = ctx.thtRunner.acquireWorkspaceRuntime(current.snapshotPath); let rendered;
try { rendered = (await import("yaml")).parse(await readFile(lease.path, "utf8")); } finally { lease.release(); }
const newRoot = renderedRoot(rendered);
const expectedOldRoot = join(dirname(ctx.oldRevision.snapshotPath), "workspace-content", "p1-filesystem", "evidence");
const expectedNewRoot = join(dirname(current.snapshotPath), "workspace-content", "p1-filesystem", "evidence");
assert(ctx.oldFilesystemRoot === expectedOldRoot, "old filesystem root was not old commit-addressed root");
assert(newRoot === expectedNewRoot && newRoot !== ctx.oldFilesystemRoot, "new filesystem root did not change exactly with commit");
ctx.currentRevision = current;
const currentSnapshot = await currentSnapshotManifest(ctx, current.commit); ctx.currentManifest = currentSnapshot.manifest;
return { commands: ["git"], artifacts: [
await evidence(ctx.run, "logs/content-only-revision.json", { oldCommit: ctx.oldRevision.commit, newCommit: current.commit, descriptorBlob: current.blob, oldFilesystemRoot: ctx.oldFilesystemRoot, newFilesystemRoot: newRoot, oldSnapshotImmutable: true }),
await fileArtifact(ctx.run.root, relative(ctx.run.root, currentSnapshot.path)), await fileArtifact(ctx.run.root, relative(ctx.run.root, current.snapshotPath)),
] };
} },
{ id: "snapshot_and_docs", run: async () => {
const artifacts = [];
for (const id of ctx.descriptors.map((item) => item.workspace.id)) {
const extracted = join(ctx.run.root, "exports", "extracted", id);
for (const name of ZIP_FILES) {
assert((await lstat(join(extracted, name))).isFile(), `missing extracted ${name}`);
artifacts.push(await fileArtifact(ctx.run.root, `exports/extracted/${id}/${name}`));
}
const revision = revisionFromManifest(ctx.currentManifest, id);
for (const suffix of [".yaml", ".env.example", ".md", "snapshot.json"]) {
const file = suffix === "snapshot.json" ? join(dirname(revision.snapshotPath), suffix) : join(dirname(revision.snapshotPath), `${id}${suffix}`);
assert(existsSync(file), `snapshot artifact absent ${file}`);
artifacts.push(await fileArtifact(ctx.run.root, relative(ctx.run.root, file)));
}
assert(!existsSync(join(dirname(revision.snapshotPath), "workspace-content")), "snapshot materialized source tree");
}
artifacts.unshift(await evidence(ctx.run, "logs/snapshot-and-docs.json", { exactBundleFiles: ZIP_FILES, generatedDocs: true, immutableSnapshots: true, derivedFromManifest: true }));
return { commands: [], artifacts };
} },
{ id: "runtime_render_determinism", run: async () => {
ctx.configChecks = []; const artifacts = []; const YAML = await import("yaml");
for (const id of ctx.descriptors.map((item) => item.workspace.id)) {
const revision = revisionFromManifest(ctx.currentManifest, id); const bytes = [];
for (let n = 1; n <= 2; n += 1) {
const lease = ctx.thtRunner.acquireWorkspaceRuntime(revision.snapshotPath);
try {
const contents = await readFile(lease.path); bytes.push(contents);
await atomicWrite(join(ctx.run.root, "rendered", `${id}-${n}.yaml`), contents);
const checked = await tht(ctx.env.THT_BIN, ["config", "check", "-c", lease.path], { cwd: ctx.env.THT_HARNESS_DIR, env: ctx.env, timeoutMs: 30_000 });
ctx.configChecks.push({ id, observation: n, code: checked.code });
} finally { lease.release(); }
const runtimeDir = join(ctx.run.root, "installation", "registry", "snapshots", "runtime");
if (existsSync(runtimeDir)) assert((await readdir(runtimeDir)).length === 0, "runtime lease leaked");
artifacts.push(await fileArtifact(ctx.run.root, `rendered/${id}-${n}.yaml`));
}
assert(bytes[0].equals(bytes[1]), `render nondeterministic ${id}`);
assertRuntimeContract(ctx, id, YAML.parse(bytes[0].toString("utf8")), revision);
}
artifacts.unshift(await evidence(ctx.run, "logs/runtime-render.json", { deterministic: true, released: true, fullSourcePolicyAssertions: true, rootsUnmaterialized: true, workspaces: ctx.descriptors.map((item) => item.workspace.id) }));
return { commands: ["tht"], artifacts };
} },
{ id: "tht_config_check", run: async () => {
assert(ctx.configChecks.length === 6 && ctx.configChecks.every(({ code }) => code === 0), "tht config checks incomplete");
return { commands: ["tht"], artifacts: [await evidence(ctx.run, "logs/tht-config-check.json", ctx.configChecks)] };
} },
{ id: "negative_schema_cases", run: async () => {
const base = structuredClone(ctx.descriptors[0]);
const cases = [
["absolute", (w) => { w.evidence.source.uri = "/tmp/evidence"; }, "evidence.source.uri"],
["traversal", (w) => { w.evidence.source.uri = "workspace-content/p1-filesystem/../evidence"; }, "evidence.source.uri"],
["backslash", (w) => { w.evidence.source.uri = "workspace-content\\p1-filesystem\\evidence"; }, "evidence.source.uri"],
["cross-workspace", (w) => { w.evidence.source.uri = "workspace-content/other/evidence"; }, "evidence.source.uri"],
["unsupported-source", (w) => { w.evidence.source.type = "ftp"; w.evidence.source.uri = "ftp://example.test/file"; }, "evidence.source.type"],
["unsupported-protocol", (w) => { w.evidence.source = { type: "http", uris: ["ftp://evidence.example.test/file"], authentication: "none" }; }, "evidence.source.uris"],
["credential-field", (w) => { w.evidence.source.password = ctx.secretValues.rejected; }, "evidence.source.password"],
["http-userinfo-query", (w) => { w.evidence.source = { type: "http", uris: [`https://user:${ctx.secretValues.rejected}@evidence.example.test/guide?x=${ctx.secretValues.rejected}`], authentication: "none" }; }, "evidence.source.uris"],
["malformed-policy", (w) => { w.evidence.policy.max_chunk_chars = 0; }, "evidence.policy.max_chunk_chars"],
["malformed-limit", (w) => { w.evidence.source.max_bytes = 0; }, "evidence.source.max_bytes"],
];
const outcomes = [];
for (const [id, mutate, field] of cases) {
const before = await registryState(ctx); const workspace = structuredClone(base); mutate(workspace);
const safeInput = negativeRequestEvidence(id, field);
trackArtifact(ctx, await evidence(ctx.run, `fixtures/requests/negative-${id}.json`, safeInput));
const response = await request(ctx, `negative-${id}`, "POST", "/workspaces/validate", { workspace }, false, safeInput);
assertGenericWorkspaceInvalid(response, `negative ${id}`);
assert(JSON.stringify(response.body).includes(ctx.secretValues.rejected) === false, `negative leaked ${id}`);
assertByteIdentical(await registryState(ctx), before, `negative ${id}`);
outcomes.push({ case: id, status: response.status, code: response.body.code, expectedInputField: field, genericSafeEnvelope: true, stateByteIdentical: true });
}
return { commands: ["git"], artifacts: [await evidence(ctx.run, "logs/negative-schema.json", outcomes)] };
} },
{ id: "negative_context_case", run: async () => {
const contextual = join(ctx.run.root, "installation", "runtime", "contextual");
const contextualRemote = join(contextual, "remote.git");
const contextualAuthor = join(contextual, "author");
const primaryBefore = await primarySemanticState(ctx);
assert(primaryBefore.remote.head === ctx.contentCommit, "primary main was not last-valid before contextual scenario");
await mkdir(contextual, { recursive: true });
await git(["clone", "--bare", join(ctx.run.root, "remote.git"), contextualRemote], { cwd: contextual });
await git(["clone", contextualRemote, contextualAuthor], { cwd: contextual });
await git(["config", "user.name", "P1 Context Curator"], { cwd: contextualAuthor });
await git(["config", "user.email", "p1-context@example.invalid"], { cwd: contextualAuthor });
await git(["checkout", "-b", "invalid-context"], { cwd: contextualAuthor });
await git(["push", "-u", "origin", "invalid-context"], { cwd: contextualAuthor });
await mkdir(join(contextual, "runtime"), { recursive: true });
await mkdir(join(contextual, "data"), { recursive: true });
await atomicWrite(join(contextual, "runtime", "base.yaml"), "{}\n");
const contextualEnv = buildSafeEnvironment({ ambient: ctx.env, fixture: {
...ctx.env,
THT_DATA_ROOT: join(contextual, "data"),
SETTINGS_FILE: join(contextual, "data", "settings.json"),
MAINTENANCE_STATE_FILE: join(contextual, "data", "maintenance.json"),
THT_WORKSPACE_REGISTRY_ROOT: join(contextual, "registry"),
THT_WORKSPACE_GIT_REMOTE: contextualRemote,
THT_WORKSPACE_GIT_BRANCH: "invalid-context",
THT_WORKSPACE_INSTALLATION_ID: "p1-contextual-acceptance",
THT_HOME: join(contextual, "runtime", "tht-home"),
} });
const contextualService = await startProductionBackend(ctx, {
name: "contextual", env: contextualEnv, runtimeConfigPath: join(contextual, "runtime", "base.yaml"),
});
ctx.expectedGitRepositories.push(
"installation/runtime/contextual/remote.git",
"installation/runtime/contextual/author",
);
const contextualStatus = await request(ctx, "context-registry-status", "GET", "/workspace-registry/status", undefined, false, undefined, contextualService.baseUrl);
assert(contextualStatus.status === 200 && contextualStatus.body.head === ctx.contentCommit, "contextual registry bootstrap failed");
const contextualBaselinePull = await request(ctx, "context-registry-baseline-pull", "POST", "/workspace-registry/pull", undefined, false, undefined, contextualService.baseUrl);
assert(contextualBaselinePull.status === 200 && contextualBaselinePull.body.head === ctx.contentCommit, "contextual baseline pull failed");
const primaryAfterSetup = await primarySemanticState(ctx);
await invariantArtifact(ctx, "logs/negative-context-setup-state.json", { before: primaryBefore, after: primaryAfterSetup });
assertByteIdentical(primaryAfterSetup, primaryBefore, "primary state during contextual setup");
const missing = baseWorkspace("missing-context", { type: "filesystem", uri: "workspace-content/missing-context/evidence", patterns: ["**/*.md"], max_bytes: 100 });
const missingFixture = "fixtures/descriptors/missing-context.json";
await atomicWrite(join(ctx.run.root, missingFixture), `${JSON.stringify(missing, null, 2)}\n`);
trackArtifact(ctx, await fileArtifact(ctx.run.root, missingFixture));
const missingBefore = { primary: await primarySemanticState(ctx), secondary: await contextualSemanticState(contextual) };
await invariantArtifact(ctx, "logs/negative-context-missing-before.json", missingBefore);
const rejectedPublish = await request(ctx, "context-missing-publish", "POST", "/workspaces/publish", { action: "create", workspace: missing, baseCommit: ctx.contentCommit }, false, undefined, contextualService.baseUrl);
const missingAfter = { primary: await primarySemanticState(ctx), secondary: await contextualSemanticState(contextual) };
await invariantArtifact(ctx, "logs/negative-context-missing-after.json", missingAfter);
assertGenericWorkspaceInvalid(rejectedPublish, "context publish");
assertByteIdentical(missingAfter.secondary, missingBefore.secondary, "failed contextual publish full semantic state");
assertByteIdentical(missingAfter.primary, primaryBefore, "primary state after contextual publish");
await rm(join(contextualAuthor, "workspace-content", "p1-filesystem", "evidence"), { recursive: true });
await git(["add", "-A", "workspace-content/p1-filesystem/evidence"], { cwd: contextualAuthor });
await git(["commit", "-m", "Invalid contextual Evidence state"], { cwd: contextualAuthor });
await git(["push", "origin", "invalid-context"], { cwd: contextualAuthor });
const invalidRemoteCommit = (await git(["rev-parse", "HEAD"], { cwd: contextualAuthor })).stdout.trim();
const invalidBefore = { primary: await primarySemanticState(ctx), secondary: await contextualSemanticState(contextual) };
await invariantArtifact(ctx, "logs/negative-context-invalid-before.json", invalidBefore);
const rejectedPull = await request(ctx, "context-invalid-pull", "POST", "/workspace-registry/pull", undefined, false, undefined, contextualService.baseUrl);
const invalidAfter = { primary: await primarySemanticState(ctx), secondary: await contextualSemanticState(contextual) };
await invariantArtifact(ctx, "logs/negative-context-invalid-after.json", invalidAfter);
assertGenericWorkspaceInvalid(rejectedPull, "context pull");
assertByteIdentical(invalidAfter.primary, primaryBefore, "primary state after contextual pull");
assertByteIdentical(invalidAfter.secondary.remote, invalidBefore.secondary.remote, "pull mutated contextual fixture remote");
assertByteIdentical(invalidAfter.secondary.author, invalidBefore.secondary.author, "pull mutated contextual fixture author");
for (const key of ["active", "snapshots", "data", "runtime"]) {
assert(invalidAfter.secondary[key] === invalidBefore.secondary[key], `invalid pull changed last-valid ${key}`);
}
assert(invalidBefore.secondary.checkout.head === ctx.contentCommit, "contextual checkout was not last-valid before invalid pull");
assert(invalidAfter.secondary.checkout.head === invalidRemoteCommit, "invalid checkout did not advance as explicitly allowed");
assert(invalidAfter.secondary.checkout.refs !== invalidBefore.secondary.checkout.refs, "invalid checkout refs did not advance as explicitly allowed");
assert(invalidAfter.secondary.checkout.branch === "invalid-context" && invalidAfter.secondary.checkout.status === "", "invalid checkout branch/status mismatch");
assert(invalidAfter.secondary.checkout.indexTree === invalidAfter.secondary.remote.tree, "invalid checkout index did not match invalid remote tree");
assert(invalidAfter.primary.remote.head === ctx.contentCommit, "contextual scenario mutated primary main");
return { artifacts: [await evidence(ctx.run, "logs/negative-context.json", {
realSecondaryHttp: true, secondaryBranch: "invalid-context", primaryFullSemanticStateByteIdentical: true,
primaryMainUnchanged: true, missingPublishSecondaryFullSemanticStateByteIdentical: true,
invalidRemoteCommit, checkoutHeadIndexRefsAdvancedExplicitlyAllowed: true, remoteUnchangedByRequest: true,
lastValidActiveSnapshotsDataRuntimeByteIdentical: true, exactGenericEnvelopesNoStderr: true,
gitTransportTelemetryExcluded: [".git/logs", ".git/FETCH_HEAD", ".git/ORIG_HEAD", ".git/objects"],
}, ctx.forbiddenValues)] };
} },
{ id: "no_p1_scope_artifacts", run: async () => {
const forbidden = ["artifacts/evidence", "corpus/ACTIVE", "embedding-output", "qdrant-records", "preprocessing-invocation"];
const files = (await walkFiles(ctx.run.root)).map(({ rel }) => rel);
const present = files.filter((path) => forbidden.some((part) => path.includes(part)));
const prohibitedRoutesCalled = ctx.httpRequests.filter(({ path }) => /\/evidence|\/acquisition|\/preprocess/i.test(path));
const prohibitedCommands = (commandEventSink ?? []).filter(({ argvLabels }) => argvLabels.some((label) => /preprocess|acquire.*evidence|embedding|qdrant/i.test(label)));
const surfaceAudit = await assertNoP1ScopeEntrypoints(ctx);
const gitTraceProof = await assertProductionGitTrace(ctx);
assert(present.length === 0 && prohibitedRoutesCalled.length === 0 && prohibitedCommands.length === 0, "prohibited P1 scope operation observed");
assert(ctx.networkGuard.externalAttempts.length === 0, "external network connection attempted");
assert(ctx.services.length === 2 && ctx.services.every(({ baseUrl }) => ctx.networkGuard.hasOwnedOrigin(baseUrl)), "listener was not an owned loopback origin");
return await log("no-p1-scope-artifacts", {
absentArtifacts: forbidden, moduleEntrypointSurfaceAbsent: true, routeEntrypointSurfaceAbsent: true,
...surfaceAudit, productionGitTrace: gitTraceProof, networkGuardInstalledBeforeProduction: true, externalNetworkAttempts: [],
ownedLoopbackOrigins: ctx.services.map(({ name }) => name),
});
} },
{ id: "secret_scan", run: async () => {
const findings = await scanSecrets({ runRoot: ctx.run.root, forbiddenValues: ctx.forbiddenValues });
assert(findings.length === 0, "secret canary found outside exclusion");
return await log("secret-scan", { scanned: true, gitEnumerationFailClosed: true, excluded: "fixture-secrets", findings: [] });
} },
{ id: "cleanup_confinement", run: async () => {
const fakeRepo = join(ctx.run.root, "installation", "runtime", "cleanup-test");
await mkdir(join(fakeRepo, ".artifacts", "p1-integration"), { recursive: true });
const synthetic = await createOwnedRun({ repositoryRoot: fakeRepo });
const sibling = join(fakeRepo, ".artifacts", "p1-integration", `p1-${"e".repeat(32)}`);
await mkdir(sibling); await writeFile(join(sibling, "sentinel"), "foreign");
await cleanupOwnedRun({ repositoryRoot: fakeRepo, runRoot: synthetic.root, expectedNonce: synthetic.nonce });
assert(await readFile(join(sibling, "sentinel"), "utf8") === "foreign", "cleanup removed sibling");
await rm(fakeRepo, { recursive: true });
assert(!existsSync(fakeRepo), "cleanup test resource remained");
return await log("cleanup-confinement", { ownedRemoved: true, siblingPreservedDuringAssertion: true, testResourceRemoved: true });
} },
];
return scenarios.map((scenario) => wrapProductionCheck(ctx, scenario));
}
async function assertRejectsCode(fn, code) {
try { await fn(); } catch (error) { if (error?.code === code) return; throw error; }
throw new Error(`expected ${code}`);
}
function replaceProcessEnvironment(values) {
for (const key of Object.keys(process.env)) delete process.env[key];
Object.assign(process.env, values);
}
function failedCheck(id, startedAt, error) {
return { id, status: "FAIL", startedAt, finishedAt: nowIso(), commands: [], artifacts: [], error };
}
function completeFailedResults(results, firstError = "Acceptance setup failed safely.") {
const completed = [...results];
for (let index = completed.length; index < CHECK_IDS.length; index += 1) {
completed.push(failedCheck(CHECK_IDS[index], nowIso(), index === 0 ? firstError : "Not executed after earlier failure."));
}
return completed;
}
function deduplicateResultArtifacts(results) {
const seen = new Set();
for (const result of results) result.artifacts = result.artifacts.filter(({ path }) => !seen.has(path) && seen.add(path));
return results;
}
function minimalFailClosedReport(run, keep) {
const checks = CHECK_IDS.map((id, index) => failedCheck(
id, nowIso(), index === 0 ? "Acceptance audit failed closed." : "Not executed after fail-closed audit.",
));
return {
schemaVersion: 1, runId: run.runId, startedAt: run.startedAt, finishedAt: nowIso(),
command: `p1-acceptance integration${keep ? " --keep" : ""}`, overall: "FAIL", checks,
};
}
function reportBytes(report) {
validateReport(report);
return {
json: Buffer.from(`${JSON.stringify(report, null, 2)}
`),
markdown: Buffer.from(renderReportMarkdown(report)),
};
}
function attachResultArtifact(results, checkId, artifact) {
const result = results.find(({ id }) => id === checkId);
if (!result) throw new Error("trace artifact owner is absent");
result.artifacts.push(artifact);
}
export async function runIntegration({ repositoryRoot = defaultRepositoryRoot, keep = false, env = process.env, failAt = env.P1_ACCEPTANCE_FAIL_AT, checks, setup, announce } = {}) {
const savedEnv = { ...process.env };
let run; let ctx; let results = []; let fatal;
try {
run = await createOwnedRun({ repositoryRoot });
ctx = {
run, repositoryRoot, forbiddenValues: [], expectedGitRepositories: [], services: [],
originalFetch: globalThis.fetch,
};
commandEventSink = [];
const selectedSetup = setup ?? (checks === undefined ? setupContext : undefined);
if (selectedSetup) {
const configured = await selectedSetup(run, repositoryRoot, env, ctx);
if (configured && configured !== ctx) Object.assign(ctx, configured);
}
if (checks === undefined) {
if (!ctx.env) throw new Error("acceptance setup returned no environment");
replaceProcessEnvironment(ctx.env);
ctx.networkGuard = installNetworkGuard(ctx.originalFetch);
checks = productionChecks(ctx);
} else if (ctx.env) {
replaceProcessEnvironment(ctx.env);
}
results = await executeChecks({ checks, failAt });
} catch (error) {
fatal = error;
if (run) results = completeFailedResults(results);
} finally {
if (ctx?.services) {
for (const service of [...ctx.services].reverse()) {
await service.app.close().catch(() => {});
await writeOwnership(run, {
name: service.name, kind: "fastify", host: "127.0.0.1", requestedPort: 0,
actualPort: Number(new URL(service.baseUrl).port), pid: process.pid, state: "closed",
}).catch(() => {});
}
}
ctx?.networkGuard?.restore();
replaceProcessEnvironment(savedEnv);
}
if (!run) throw fatal;
results = deduplicateResultArtifacts(completeFailedResults(results));
let auditFailed = false;
const commandEvents = commandEventSink ?? [];
commandEventSink = undefined;
activeCommandCheckId = undefined;
try {
const commandArtifact = await evidence(run, "logs/command-events.json", { eventCount: commandEvents.length, events: commandEvents }, ctx.forbiddenValues);
attachResultArtifact(results, "preflight", commandArtifact);
if (ctx.gitTracePath) {
const gitTraceBytes = await readFile(ctx.gitTracePath);
for (const line of gitTraceBytes.toString("utf8").split("\n").filter(Boolean)) JSON.parse(line);
attachResultArtifact(results, "no_p1_scope_artifacts", await fileArtifact(run.root, relative(run.root, ctx.gitTracePath)));
}
} catch {
auditFailed = true;
}
deduplicateResultArtifacts(results);
let report = {
schemaVersion: 1, runId: run.runId, startedAt: run.startedAt, finishedAt: nowIso(),
command: `p1-acceptance integration${keep ? " --keep" : ""}`,
overall: !fatal && deriveOverall(results) === "PASS" ? "PASS" : "FAIL", checks: results,
};
let bytes;
try {
bytes = reportBytes(report);
const findings = await scanSecrets({
runRoot: run.root,
forbiddenValues: ctx.forbiddenValues,
expectedGitRepositories: ctx.expectedGitRepositories,
virtualFiles: [{ path: "report.json", bytes: bytes.json }, { path: "report.md", bytes: bytes.markdown }],
});
if (findings.length > 0) auditFailed = true;
} catch {
auditFailed = true;
}
if (auditFailed) {
report = minimalFailClosedReport(run, keep);
bytes = reportBytes(report);
if (containsAny(bytes.json, ctx.forbiddenValues) || containsAny(bytes.markdown, ctx.forbiddenValues)) {
throw new Error("sanitized fail-closed report unexpectedly contains a forbidden value");
}
}
await atomicWrite(join(run.root, "report.json"), bytes.json);
await atomicWrite(join(run.root, "report.md"), bytes.markdown);
const finalSuccess = report.overall === "PASS";
if (announce) await announce({ report, runRoot: run.root, keep });
const removed = await finalizeOwnedRun({ run, success: finalSuccess, keep });
return { exitCode: finalSuccess ? 0 : 1, runRoot: run.root, retained: !removed, report };
}
export async function main(argv = process.argv.slice(2), env = process.env) {
if (argv.length < 1 || argv[0] !== "integration" || argv.length > 2 || (argv.length === 2 && argv[1] !== "--keep")) {
console.error("usage: p1-acceptance integration [--keep]"); return 2;
}
try {
const result = await runIntegration({
repositoryRoot: defaultRepositoryRoot, keep: argv.includes("--keep"), env,
announce: async ({ report, runRoot, keep }) => {
console.log(`automated integration: ${report.overall}`);
console.log("manual acceptance: PENDING");
if (keep || report.overall !== "PASS") console.log(runRoot);
},
});
return result.exitCode;
} catch (error) {
console.error("P1 acceptance failed before owning a reportable run."); return 1;
}
}
if (resolve(process.argv[1] ?? "") === modulePath) process.exitCode = await main();