#!/usr/bin/env node import { createHash, randomBytes } from "node:crypto"; import { execFile, execFileSync } from "node:child_process"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; import { createServer } from "node:http"; import { closeSync, constants as fsConstants, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, rmSync, statSync } from "node:fs"; import { access, lstat, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises"; import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path"; import net from "node:net"; import process from "node:process"; import { stringify as yamlStringify } from "yaml"; import { buildSafeEnvironment, deriveOverall, scanSecrets } from "./p1-acceptance.mjs"; const execFileAsync = promisify(execFile); const modulePath = fileURLToPath(import.meta.url); const defaultRepositoryRoot = realpathSync(resolve(dirname(modulePath), "../..")); const RUN_ID = /^p2-[0-9a-f]{32}$/; const HEX32 = /^[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 COMMAND = /^[a-z0-9][a-z0-9-]*$/; const CHECK_RESULT_STATUS = new Set(["PASS", "FAIL"]); const CHECK_IDS = Object.freeze([ "preflight", "clean_state", "ownership", "inspect_identity", "dwh_processing", "schema_review", "schema_index", "evidence_processing", "negative_cases", "secret_scan", "cleanup_confinement", ]); const TOPOLOGY = [ "remote.git", "author", "installation", "installation/data", "installation/data/sessions", "installation/registry", "installation/pi-state", "fixture-secrets", "fixtures", "fixtures/logs", "logs", ]; const MAX_REPORT_JSON_BYTES = 64 * 1024; const MAX_REPORT_MD_BYTES = 32 * 1024; const MAX_STDIO_BYTES = 512 * 1024; const MAX_SECRET_SCAN_VIRTUAL_BYTES = 256 * 1024; function nowIso() { return new Date().toISOString(); } function sha256(value) { return createHash("sha256").update(value).digest("hex"); } function assert(condition, message) { if (!condition) throw new Error(message); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function canonicalRoot(repositoryRoot = defaultRepositoryRoot) { return realpathSync(repositoryRoot); } export function canonicalIntegrationBase(repositoryRoot = defaultRepositoryRoot) { return join(canonicalRoot(repositoryRoot), ".artifacts", "p2-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(12).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 }).catch(() => {}); throw error; } } function initialResources(run) { return [ run.root, join(run.root, "remote.git"), join(run.root, "author"), join(run.root, "installation"), join(run.root, "installation", "registry"), join(run.root, "installation", "data"), join(run.root, "fixture-secrets"), ]; } function ownershipValue(run) { return { schemaVersion: 1, kind: "p2-acceptance", runId: run.runId, runNonce: run.nonce, root: run.root, repositoryRoot: run.repositoryRoot, startedAt: run.startedAt, pid: run.pid, resources: initialResources(run), }; } async function writeOwnership(run) { await atomicWrite(join(run.root, "ownership.json"), `${JSON.stringify(ownershipValue(run), null, 2)}\n`); } export async function createOwnedRun({ repositoryRoot = defaultRepositoryRoot, 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; }); const id = runId ?? `p2-${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, }; 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"); if (value.schemaVersion !== 1 || value.kind !== "p2-acceptance" || value.runId !== run.runId || value.runNonce !== expectedNonce || value.root !== run.root || value.repositoryRoot !== run.repositoryRoot || value.pid !== process.pid || !ISO_UTC.test(value.startedAt ?? "") || JSON.stringify(value.resources) !== JSON.stringify(initialResources(run))) throw new Error("ownership identity mismatch"); return value; } export async function readAndValidateOwnership({ repositoryRoot = defaultRepositoryRoot, 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"); 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 }, expectedNonce); } export async function cleanupOwnedRun({ repositoryRoot = defaultRepositoryRoot, 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, force: false }); } async function finalizeOwnedRun({ run, success, keep }) { if (!success || keep) return false; await cleanupOwnedRun({ repositoryRoot: run.repositoryRoot, runRoot: run.root, expectedNonce: run.nonce }); return true; } function safeArtifactPath(path) { if (typeof path !== "string" || path.length === 0 || path.length > 255 || path.startsWith("/") || path.includes("..") || path.includes("\\") || /[\0\r\n]/.test(path)) { throw new Error("report artifact path is invalid"); } return path; } 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" || !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) || !CHECK_RESULT_STATUS.has(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)) throw new Error("report check is invalid"); ids.add(check.id); for (const artifact of check.artifacts) { safeArtifactPath(artifact.path); if (!HEX64.test(artifact.sha256 ?? "")) throw new Error("report check is invalid"); 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 [ "# P2 acceptance report", "", `Run: \`${report.runId}\``, "", "| Check | Status |", "|---|---|", rows, "", `P2 automated integration: ${report.overall}`, "P2 manual acceptance: PENDING", "", ].join("\n"); } async function walkFiles(root) { const files = []; async function visit(dir) { for (const entry of await readdir(dir, { withFileTypes: true })) { const path = join(dir, entry.name); const rel = relative(root, path).split(sep).join("/"); if (entry.isSymbolicLink()) throw new Error(`unsafe file tree: ${rel}`); if (entry.isDirectory()) await visit(path); else if (entry.isFile()) files.push({ path, rel }); } } if (existsSync(root)) await visit(root); files.sort((a, b) => a.rel.localeCompare(b.rel)); return files; } async function snapshotDigest(root, excludedPrefixes = []) { const result = {}; for (const file of await walkFiles(root)) { if (excludedPrefixes.some((prefix) => file.rel === prefix || file.rel.startsWith(`${prefix}/`))) continue; result[file.rel] = sha256(await readFile(file.path)); } return result; } async function fileArtifact(root, relativePath) { const bytes = await readFile(join(root, relativePath)); return { path: relativePath.split(sep).join("/"), sha256: sha256(bytes) }; } async function writeJson(path, value) { await atomicWrite(path, `${JSON.stringify(value, null, 2)}\n`); } async function writeReportFiles({ run, report }) { validateReport(report); const reportJsonPath = join(run.root, "report.json"); const reportMdPath = join(run.root, "report.md"); const reportMd = renderReportMarkdown(report); if (Buffer.byteLength(JSON.stringify(report)) > MAX_REPORT_JSON_BYTES) throw new Error("report.json exceeds bound"); if (Buffer.byteLength(reportMd) > MAX_REPORT_MD_BYTES) throw new Error("report.md exceeds bound"); await writeJson(reportJsonPath, report); await atomicWrite(reportMdPath, reportMd, 0o600); return { reportJson: await fileArtifact(run.root, "report.json"), reportMd: await fileArtifact(run.root, "report.md"), }; } function resolveSystemExecutable(name) { for (const candidate of [`/usr/bin/${name}`, `/bin/${name}`, `/opt/homebrew/bin/${name}`, `/usr/local/bin/${name}`]) { try { const resolved = realpathSync(candidate); if (statSync(resolved).isFile()) return resolved; } catch {} } throw new Error(`required executable not found: ${name}`); } 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); } async function manifestFiles(root, paths) { const files = []; const visit = async (absolute, rel) => { const entry = await lstat(absolute); if (entry.isSymbolicLink()) throw new Error(`provenance path is a symlink: ${rel}`); if (entry.isDirectory()) { for (const child of (await readdir(absolute, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) { await visit(join(absolute, child.name), rel ? `${rel}/${child.name}` : child.name); } } else if (entry.isFile()) { const bytes = await readFile(absolute); files.push({ path: rel, bytes: bytes.length, sha256: sha256(bytes) }); } else throw new Error(`provenance path is not a regular file: ${rel}`); }; for (const path of paths) await visit(join(root, path), path); files.sort((a, b) => a.path.localeCompare(b.path)); return { files, manifestSha256: sha256(JSON.stringify(files)) }; } async function collectRepositoryProvenance({ repositoryRoot, gitPath = resolveSystemExecutable("git") }) { const repo = canonicalRoot(repositoryRoot); const safeEnv = buildSafeEnvironment({ ambient: {}, fixture: { PATH: `${dirname(gitPath)}:/usr/bin:/bin`, HOME: repo, TMPDIR: join(repo, ".artifacts") } }); const run = async (argv) => await execFileAsync(gitPath, ["-C", repo, ...argv], { env: safeEnv, maxBuffer: MAX_STDIO_BYTES }); const beforeHead = (await run(["rev-parse", "HEAD"]).catch((error) => { throw error; })).stdout.trim(); const beforeTree = (await run(["rev-parse", "HEAD^{tree}"])).stdout.trim(); const beforeStatus = (await run(["status", "--porcelain=v1", "--untracked-files=no"])).stdout; if (!HEX40.test(beforeHead) || !HEX40.test(beforeTree) || beforeStatus !== "") throw new Error("repository is not clean at exact HEAD"); const backendRoot = join(repo, "backend"); const backendSource = await manifestFiles(backendRoot, [ "src", "scripts/p2-acceptance.mjs", "package.json", "package-lock.json", "tsconfig.json", ]); const backendDist = existsSync(join(backendRoot, "dist")) ? await manifestFiles(backendRoot, ["dist"]) : { files: [], manifestSha256: sha256("[]") }; const afterHead = (await run(["rev-parse", "HEAD"]).catch((error) => { throw error; })).stdout.trim(); const afterTree = (await run(["rev-parse", "HEAD^{tree}"])).stdout.trim(); const afterStatus = (await run(["status", "--porcelain=v1", "--untracked-files=no"])).stdout; if (afterHead !== beforeHead || afterTree !== beforeTree || afterStatus !== beforeStatus) throw new Error("repository provenance changed during binding"); return { schemaVersion: 1, head: beforeHead, tree: beforeTree, clean: true, backendSource, backendDist }; } 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 allocatePort() { const server = net.createServer(); await new Promise((resolve, reject) => server.listen(0, "127.0.0.1", resolve).on("error", reject)); const port = server.address().port; await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); return port; } function installationProjectName(installationPath) { return `thothii-${sha256(installationPath).slice(0, 12)}`; } function baseWorkspace(id, { dwhBaseUrl, evidenceSource }) { return { workspace: { schema_version: 3, id, name: `P2 ${id}`, language: "en" }, dwh: { engine: "postgres", database: "warehouse", schema: "dw", supported_transports: ["rest_api"] }, semantic_index: { vector_store: { engine: "qdrant", collection: id, dimensions: 1024, distance: "cosine" }, embedding: { provider: "ollama_internal", model: "qwen3-embedding:0.6b", dimensions: 1024 }, }, diagnostics: { dwh_rest: { method: "POST", path: "/rpc/ping", auth: "x-api-key", response: { database: "database", schema: "schema" } }, }, llm_policy: { allowed: ["zai/glm-5.2"] }, ...(evidenceSource ? { evidence: { source: evidenceSource, policy: { max_chunk_chars: 4000, retain_published_generations: 3 } } } : {}), }; } function namespace(id) { return id.toUpperCase().replaceAll("-", "_"); } function descriptorYaml(obj) { return yamlStringify(obj, { lineWidth: 0, sortMapEntries: false }); } async function setupSecrets(ctx) { const secretDir = join(ctx.run.root, "fixture-secrets"); const values = { dwhToken: `P2-DWH-${randomBytes(16).toString("hex")}`, signedToken: `P2-SIGNED-${randomBytes(16).toString("hex")}`, bundle: `P2-BUNDLE-${randomBytes(16).toString("hex")}`, }; ctx.forbiddenValues = Object.values(values); ctx.secretValues = values; const paths = { dwh: join(secretDir, "p2-dwh-api-key"), filesystemDwh: join(secretDir, "p2-filesystem-api-key"), signed: join(secretDir, "p2-dwh-evidence-signed-urls.json"), bundle: join(secretDir, "thothii.secrets"), }; await atomicWrite(paths.dwh, scalarSecretBytes(values.dwhToken)); await atomicWrite(paths.filesystemDwh, scalarSecretBytes(values.dwhToken)); await atomicWrite(paths.bundle, scalarSecretBytes(values.bundle)); ctx.secretPaths = paths; } async function setupFixtures(ctx) { ctx.fixturePorts = { dwh: await allocatePort(), evidence: await allocatePort(), embedding: await allocatePort(), qdrant: await allocatePort(), }; const dwhBaseUrl = `http://host.docker.internal:${ctx.fixturePorts.dwh}`; const evidenceProvenance = `http://host.docker.internal:${ctx.fixturePorts.evidence}/p2-dwh/guide.md`; ctx.workspaceObjects = { dwh: baseWorkspace("p2-dwh", { dwhBaseUrl, evidenceSource: { type: "http", uris: [evidenceProvenance], authentication: "signed_urls_file", connect_timeout_ms: 1250, read_timeout_ms: 30001, max_bytes: 65536, max_redirects: 2, allow_private_hosts: false, max_cache_bytes: 65536, }, }), filesystem: baseWorkspace("p2-filesystem", { dwhBaseUrl, evidenceSource: { type: "filesystem", uri: "p2-filesystem/evidence", patterns: ["**/*.md"], max_bytes: 1048576, }, }), }; const signedUrl = `${evidenceProvenance}?token=${ctx.secretValues.signedToken}`; await atomicWrite(ctx.secretPaths.signed, `${JSON.stringify([signedUrl], null, 2)}\n`); ctx.evidenceState = { content: "# P2 Evidence\n\nFirst generation.\n", token: ctx.secretValues.signedToken, }; ctx.dwhState = { tables: { patients: { comment: "Patients", rows: [ { id: "p1", name: "Alice" }, { id: "p2", name: "Bob" }, ], }, visits: { comment: "Visits", rows: [ { id: "v1", patient_id: "p1", note: "checkup" }, { id: "v2", patient_id: "p2", note: "xray" }, ], }, labs: { comment: "Labs", rows: [ { id: "l1", patient_id: "p1", code: "hemoglobin" }, { id: "l2", patient_id: "p2", code: "glucose" }, ], }, }, token: ctx.secretValues.dwhToken, }; } function inferColumnType(value) { return typeof value === "number" ? "integer" : "text"; } function topValues(rows, column, limit) { const counts = new Map(); for (const row of rows) { const value = row[column]; if (value === undefined || value === null || value === "") continue; counts.set(String(value), (counts.get(String(value)) ?? 0) + 1); } return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit).map(([value]) => ({ value })); } async function startHttpServer({ port, handler }) { const server = createServer(async (req, res) => { try { await handler(req, res); } catch { res.statusCode = 500; res.setHeader("content-type", "application/json"); res.end(JSON.stringify({ error: "fixture failed" })); } }); await new Promise((resolve, reject) => server.listen(port, "127.0.0.1", () => resolve()).on("error", reject)); return server; } async function startServers(ctx) { const dwhServer = await startHttpServer({ port: ctx.fixturePorts.dwh, handler: async (req, res) => { const body = await new Promise((resolve) => { const chunks = []; req.on("data", (chunk) => chunks.push(chunk)); req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); }); const json = body.length === 0 ? {} : JSON.parse(body); if (req.headers["x-api-key"] !== ctx.dwhState.token) { res.statusCode = 401; res.setHeader("content-type", "application/json"); res.end(JSON.stringify({ message: "unauthorized" })); return; } const send = (payload) => { res.statusCode = 200; res.setHeader("content-type", "application/json"); res.end(JSON.stringify(payload)); }; const url = new URL(req.url, `http://127.0.0.1:${ctx.fixturePorts.dwh}`); if (req.method !== "POST" || !url.pathname.startsWith("/rpc/")) { res.statusCode = 404; res.end(JSON.stringify({ message: "not found" })); return; } const fn = url.pathname.slice("/rpc/".length); const schemaName = json.schema_name ?? "dw"; if (schemaName !== "dw") { send([]); return; } if (fn === "ping") { send({ db_connected: true, schema_accessible: true, database: "warehouse", schema: "dw" }); return; } const table = typeof json.table_name === "string" ? json.table_name : ""; const tableData = ctx.dwhState.tables[table]; if (fn === "list_tables") { send(Object.entries(ctx.dwhState.tables).map(([name, info]) => ({ table: name, type: "TABLE", comment: info.comment, rows: info.rows.length }))); return; } if (!tableData) { send([]); return; } if (fn === "table_columns") { const first = tableData.rows[0] ?? {}; send(Object.keys(first).map((column) => ({ column, type: inferColumnType(first[column]), nullable: false, pk: column === "id", default: null, }))); return; } if (fn === "table_comments") { send(Object.keys(tableData.rows[0] ?? {}).map((column) => ({ object: "COLUMN", name: column, comment: `${table}.${column}` }))); return; } if (fn === "table_foreign_keys") { send([]); return; } if (fn === "top_values") { send(topValues(tableData.rows, json.column_name, Number(json.max_values ?? 10))); return; } if (fn === "column_stats") { send({}); return; } if (fn === "run_query") { send([]); return; } if (fn === "explain_query") { send([{ line: "Seq Scan" }]); return; } res.statusCode = 404; res.end(JSON.stringify({ message: "unknown rpc" })); }, }); const evidenceServer = await startHttpServer({ port: ctx.fixturePorts.evidence, handler: async (req, res) => { const url = new URL(req.url, `http://127.0.0.1:${ctx.fixturePorts.evidence}`); if (url.pathname !== "/p2-dwh/guide.md" || url.searchParams.get("token") !== ctx.evidenceState.token) { res.statusCode = 403; res.end("forbidden"); return; } res.statusCode = 200; res.setHeader("content-type", "text/markdown; charset=utf-8"); res.end(ctx.evidenceState.content); }, }); const embeddingServer = await startHttpServer({ port: ctx.fixturePorts.embedding, handler: async (req, res) => { const body = await new Promise((resolve) => { const chunks = []; req.on("data", (chunk) => chunks.push(chunk)); req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); }); const url = new URL(req.url, `http://127.0.0.1:${ctx.fixturePorts.embedding}`); if (req.method !== "POST" || url.pathname !== "/api/embed") { res.statusCode = 404; res.end(JSON.stringify({ error: "not found" })); return; } const payload = JSON.parse(body || "{}"); const inputs = Array.isArray(payload.input) ? payload.input : []; const embeddings = inputs.map((text) => { const seed = sha256(String(text)); return Array.from({ length: 1024 }, (_, index) => { const offset = (index * 2) % seed.length; const value = Number.parseInt(seed.slice(offset, offset + 2), 16); return (value / 255) - 0.5; }); }); res.statusCode = 200; res.setHeader("content-type", "application/json"); res.end(JSON.stringify({ model: payload.model, embeddings })); }, }); ctx.servers = [dwhServer, evidenceServer, embeddingServer]; } async function stopServers(ctx) { for (const server of ctx.servers ?? []) { await new Promise((resolve) => server.close(() => resolve())); } ctx.servers = []; } async function git(ctx, args, cwd = join(ctx.run.root, "author")) { return await runCommand({ executable: ctx.executables.gitPath, argv: args, cwd, env: ctx.execEnv }); } async function initializeGitAndRegistry(ctx) { const author = join(ctx.run.root, "author"); await git(ctx, ["init", "--bare", "--initial-branch=main", join(ctx.run.root, "remote.git")], ctx.run.root); await git(ctx, ["clone", join(ctx.run.root, "remote.git"), author], ctx.run.root); await git(ctx, ["config", "user.name", "P2 Fixture Curator"], author); await git(ctx, ["config", "user.email", "p2-curator@example.invalid"], author); const writeWorkspaces = async () => { const catalog = { schema_version: 1, workspaces: [ { id: "p2-dwh", name: ctx.workspaceObjects.dwh.workspace.name }, { id: "p2-filesystem", name: ctx.workspaceObjects.filesystem.workspace.name }, ], }; await writeFile(join(author, "thoth-workspaces.yaml"), yamlStringify(catalog, { lineWidth: 0, sortMapEntries: false })); for (const [id, workspace] of Object.entries(ctx.workspaceObjects)) { const pathId = workspace.workspace.id; await mkdir(join(author, pathId), { recursive: true }); const yaml = descriptorYaml(workspace); await writeFile(join(author, pathId, "workspace.yaml"), yaml); const docs = ctx.workspaceModules.renderWorkspaceDocs(workspace); await mkdir(join(author, "workspace-docs", pathId), { recursive: true }); await writeFile(join(author, "workspace-docs", pathId, "contract.env.example"), docs.envExample); await writeFile(join(author, "workspace-docs", pathId, "README.md"), docs.markdown); } await mkdir(join(author, "p2-filesystem", "evidence"), { recursive: true }); await writeFile(join(author, "p2-filesystem", "evidence", "guide.md"), "# P2 Filesystem Evidence\n\nCommitted fixture.\n"); }; await writeWorkspaces(); await git(ctx, ["add", "."], author); await git(ctx, ["commit", "-m", "Bootstrap P2 fixtures"], author); await git(ctx, ["push", "origin", "main"], author); ctx.registryCommit = (await git(ctx, ["rev-parse", "HEAD"], author)).stdout.trim(); const registry = new ctx.workspaceModules.WorkspaceRegistry({ root: join(ctx.run.root, "installation", "registry"), remoteUrl: join(ctx.run.root, "remote.git"), branch: "main", gitAuthorName: "P2 Acceptance", gitAuthorEmail: "p2-acceptance@example.invalid", installationId: "p2-acceptance", secretRoots: [join(ctx.run.root, "fixture-secrets")], maxImportBytes: 16 * 1024 * 1024, maxImportEntries: 1024, }); await registry.bootstrap(); ctx.registry = registry; } async function mutateWorkspaceDescriptor(ctx, workspaceId, mutator, commitMessage) { const author = join(ctx.run.root, "author"); const workspace = structuredClone(ctx.workspaceObjects[workspaceId === "p2-dwh" ? "dwh" : "filesystem"]); mutator(workspace); ctx.workspaceObjects[workspaceId === "p2-dwh" ? "dwh" : "filesystem"] = workspace; await writeFile(join(author, workspaceId, "workspace.yaml"), descriptorYaml(workspace)); const docs = ctx.workspaceModules.renderWorkspaceDocs(workspace); await writeFile(join(author, "workspace-docs", workspaceId, "contract.env.example"), docs.envExample); await writeFile(join(author, "workspace-docs", workspaceId, "README.md"), docs.markdown); await git(ctx, ["add", `${workspaceId}/workspace.yaml`, `workspace-docs/${workspaceId}/contract.env.example`, `workspace-docs/${workspaceId}/README.md`], author); await git(ctx, ["commit", "-m", commitMessage], author); await git(ctx, ["push", "origin", "main"], author); await ctx.registry.pull(); ctx.registryCommit = (await git(ctx, ["rev-parse", "HEAD"], author)).stdout.trim(); } async function writeInstallationFiles(ctx) { const installationDir = join(ctx.run.root, "installation"); const operatorEnvPath = join(installationDir, "operator.env"); const bindingsEnvPath = join(installationDir, "workspace-bindings.env"); const connectorOverridePath = join(installationDir, "connector-secrets.override.yaml"); const fixtureOverridePath = join(installationDir, "fixture.override.yaml"); const installationPath = join(installationDir, "thothii-installation.yaml"); ctx.installationPath = installationPath; ctx.composeProject = installationProjectName(installationPath); const qdrantPort = ctx.fixturePorts.qdrant; const bindings = [ `THT_WS_P2_DWH_DWH_TRANSPORT=rest_api`, `THT_WS_P2_DWH_DWH_BASE_URL=http://host.docker.internal:${ctx.fixturePorts.dwh}`, `THT_WS_P2_DWH_DWH_API_KEY_FILE=/run/secrets/p2-dwh-api-key`, `THT_WS_P2_DWH_EVIDENCE_SIGNED_URLS_FILE=/run/secrets/p2-dwh-evidence-signed-urls`, `THT_WS_P2_FILESYSTEM_DWH_TRANSPORT=rest_api`, `THT_WS_P2_FILESYSTEM_DWH_BASE_URL=http://host.docker.internal:${ctx.fixturePorts.dwh}`, `THT_WS_P2_FILESYSTEM_DWH_API_KEY_FILE=/run/secrets/p2-filesystem-api-key`, ].join("\n") + "\n"; await atomicWrite(bindingsEnvPath, bindings); const operatorEnv = [ `THT_DATA_ROOT=${join(ctx.run.root, "installation", "data")}`, `THT_WORKSPACE_REGISTRY_ROOT=${join(ctx.run.root, "installation", "registry")}`, `THT_PI_STATE_ROOT=${join(ctx.run.root, "installation", "pi-state")}`, `PI_AUTH_FILE=${join(ctx.run.root, "installation", "pi-auth.json")}`, `THT_SECRETS_FILE=${ctx.secretPaths.bundle}`, `THT_WORKSPACE_BINDINGS_ENV_FILE=${bindingsEnvPath}`, `THT_WORKSPACE_GIT_REMOTE=${join(ctx.run.root, "remote.git")}`, `THT_WORKSPACE_GIT_BRANCH=main`, `THT_WORKSPACE_GIT_AUTHOR_NAME=P2 Acceptance`, `THT_WORKSPACE_GIT_AUTHOR_EMAIL=p2-acceptance@example.invalid`, `THT_WORKSPACE_INSTALLATION_ID=p2-acceptance`, `THT_DB_NAME=warehouse`, `THT_DWH_REST_URL=http://host.docker.internal:${ctx.fixturePorts.dwh}`, `THT_LLM_URL=http://127.0.0.1:9`, `THOTH_SERVER_BIND=127.0.0.1`, `THOTH_HTTP_PORT=18080`, `THOTH_CORE_HTTP_PORT=18787`, `THT_WS_P2_DWH_DWH_API_KEY_SOURCE=${ctx.secretPaths.dwh}`, `THT_WS_P2_DWH_EVIDENCE_SIGNED_URLS_SOURCE=${ctx.secretPaths.signed}`, `THT_WS_P2_FILESYSTEM_DWH_API_KEY_SOURCE=${ctx.secretPaths.filesystemDwh}`, ].join("\n") + "\n"; await atomicWrite(operatorEnvPath, operatorEnv); await atomicWrite(join(ctx.run.root, "installation", "pi-auth.json"), JSON.stringify({ fixture: true })); const embeddingStubPath = join(installationDir, "embedding-stub.py"); await atomicWrite(embeddingStubPath, EMBEDDING_STUB_SOURCE); const override = { services: { core: { image: ctx.coreImageTag, extra_hosts: ["host.docker.internal:host-gateway"], }, "workspace-maintenance": { image: ctx.coreImageTag, environment: { P2_CHILD_DEBUG: "1" }, extra_hosts: ["host.docker.internal:host-gateway"], }, qdrant: { ports: [`127.0.0.1:${qdrantPort}:6333`], restart: "no", }, // Deterministic Ollama-compatible embedding fixture on the internal allowlisted host // name `embedding` (http://embedding:11434). Replaces the real Ollama service entirely. embedding: { image: ctx.coreImageTag, entrypoint: ["python3", "/stub.py"], volumes: [ { type: "bind", source: embeddingStubPath, target: "/stub.py", read_only: true }, ], healthcheck: { disable: true }, }, }, }; await atomicWrite(fixtureOverridePath, yamlStringify(override, { lineWidth: 0, sortMapEntries: false })); const generated = await runCommand({ executable: join(ctx.repositoryRoot, "scripts", "generate-connector-secrets-override.sh"), argv: [ "--bindings-env", bindingsEnvPath, "--operator-env", operatorEnvPath, "--output", connectorOverridePath, "--service", "workspace-maintenance", "--role", "all", ], env: ctx.execEnv, }); if (generated.exitCode !== 0) throw new Error(`connector override generation failed: ${generated.stderr || generated.stdout}`); const installation = { profile: "server", projectDirectory: ctx.repositoryRoot, envFile: operatorEnvPath, overrides: [ join(ctx.repositoryRoot, "deploy", "compose.server.yaml"), fixtureOverridePath, connectorOverridePath, ], }; await atomicWrite(installationPath, yamlStringify(installation, { lineWidth: 0, sortMapEntries: false })); ctx.installation = installation; } function thothctlBinaryPath(repositoryRoot) { const platform = { darwin: "darwin", linux: "linux", win32: "windows" }[process.platform] ?? "linux"; const arch = { x64: "amd64", arm64: "arm64" }[process.arch] ?? "amd64"; const suffix = platform === "windows" ? ".exe" : ""; const candidates = [ join(repositoryRoot, "dist", "thothctl", `thothctl-${platform}-${arch}${suffix}`), join(repositoryRoot, "tools", "thothctl", "bin", `thothctl${suffix}`), ]; for (const candidate of candidates) if (existsSync(candidate)) return candidate; throw new Error("built thothctl binary is unavailable"); } async function runCommand({ executable, argv = [], cwd, env, input, maxOutputBytes = MAX_STDIO_BYTES }) { const result = await execFileAsync(executable, argv, { cwd, env, encoding: "utf8", maxBuffer: maxOutputBytes, ...(input === undefined ? {} : { input }), }).then( ({ stdout, stderr }) => ({ exitCode: 0, stdout, stderr }), (error) => ({ exitCode: error.code ?? 1, stdout: error.stdout ?? "", stderr: error.stderr ?? error.message ?? "" }), ); return result; } async function buildCoreImage(ctx) { const tag = `thothii-core:p2-${ctx.run.runId.slice(3, 15)}`; ctx.coreImageTag = tag; const build = await runCommand({ executable: ctx.executables.dockerPath, argv: ["build", "-f", join(ctx.repositoryRoot, "docker", "core.Dockerfile"), "-t", tag, ctx.repositoryRoot], env: { ...ctx.execEnv, DOCKER_BUILDKIT: "1" }, maxOutputBytes: 4 * 1024 * 1024, }); if (build.exitCode !== 0) throw new Error(`core image build failed: ${build.stderr || build.stdout}`); } async function buildThothctl(ctx) { const command = await runCommand({ executable: join(ctx.repositoryRoot, "scripts", "build-thothctl.sh"), argv: [], env: { ...ctx.execEnv, THT_THOTHCTL_OUTPUT_DIRECTORY: join(ctx.repositoryRoot, "dist", "thothctl") }, maxOutputBytes: 4 * 1024 * 1024, }); if (command.exitCode !== 0) throw new Error(`build-thothctl failed: ${command.stderr || command.stdout}`); ctx.thothctlPath = thothctlBinaryPath(ctx.repositoryRoot); } function composeBaseArgs(ctx) { const args = [ "compose", "--project-name", ctx.composeProject, "--project-directory", ctx.installation.projectDirectory, "--env-file", ctx.installation.envFile, "-f", join(ctx.repositoryRoot, "compose.yaml"), ]; for (const override of ctx.installation.overrides) args.push("-f", override); return args; } async function dockerCompose(ctx, commandArgs, { allowFailure = false, maxOutputBytes = 2 * 1024 * 1024 } = {}) { const result = await runCommand({ executable: ctx.executables.dockerPath, argv: [...composeBaseArgs(ctx), ...commandArgs], env: ctx.execEnv, maxOutputBytes, }); if (!allowFailure && result.exitCode !== 0) throw new Error(`docker compose ${commandArgs.join(" ")} failed: ${result.stderr || result.stdout}`); return result; } async function startQdrant(ctx) { await dockerCompose(ctx, ["up", "-d", "qdrant", "embedding"]); for (let attempt = 0; attempt < 60; attempt += 1) { try { const response = await fetch(`http://127.0.0.1:${ctx.fixturePorts.qdrant}/collections`); if (response.ok) return; } catch {} await sleep(1000); } throw new Error("qdrant did not become ready"); } async function qdrantJson(ctx, method, path, body) { const response = await fetch(`http://127.0.0.1:${ctx.fixturePorts.qdrant}${path}`, { method, headers: { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const payload = response.status === 204 ? {} : await response.json().catch(() => ({})); if (!response.ok) throw new Error(`qdrant request failed: ${method} ${path} ${response.status}`); return payload; } async function preprovisionCollection(ctx, workspaceId) { await qdrantJson(ctx, "PUT", `/collections/${workspaceId}`, { vectors: { size: 1024, distance: "Cosine" }, }); for (const field of ["content_hash", "document_id", "kind", "record_key", "record_kind", "vector_generation", "workspace_id", "workspace_revision"]) { await qdrantJson(ctx, "PUT", `/collections/${workspaceId}/index`, { field_name: field, field_schema: "keyword" }); } } async function listCollections(ctx) { const payload = await qdrantJson(ctx, "GET", "/collections"); const collections = payload.result?.collections ?? []; return collections.map((item) => item.name).sort(); } async function dumpQdrantPayloads(ctx, workspaceId) { const response = await qdrantJson(ctx, "POST", `/collections/${workspaceId}/points/scroll`, { limit: 128, with_payload: true, with_vector: false }); return JSON.stringify(response.result?.points ?? []); } async function runThothctlJson(ctx, label, workspaceArgs, expectedExitCode) { const stdoutPath = join(ctx.run.root, "logs", `${label}.stdout.json`); const stderrPath = join(ctx.run.root, "logs", `${label}.stderr.txt`); const result = await runCommand({ executable: ctx.thothctlPath, argv: ["--installation", ctx.installationPath, ...workspaceArgs, "--json"], env: ctx.execEnv, maxOutputBytes: 2 * 1024 * 1024, }); await atomicWrite(stdoutPath, result.stdout || ""); await atomicWrite(stderrPath, result.stderr || ""); if (expectedExitCode !== undefined && result.exitCode !== expectedExitCode) { throw new Error(`${label} exit ${result.exitCode} != ${expectedExitCode}`); } let payload; try { payload = JSON.parse(result.stdout); } catch (error) { throw new Error(`${label} returned non-JSON stdout`); } return { result, payload, artifacts: [await fileArtifact(ctx.run.root, relative(ctx.run.root, stdoutPath)), await fileArtifact(ctx.run.root, relative(ctx.run.root, stderrPath))] }; } async function loadWorkspaceSnapshot(ctx, workspaceId) { const active = JSON.parse(await readFile(join(ctx.run.root, "installation", "registry", "state", "active.json"), "utf8")); const revision = active.revisions.find((entry) => entry.id === workspaceId); const snapshotPath = revision.snapshotPath; const contents = await readFile(snapshotPath, "utf8"); return { active, revision, contents }; } async function assertNoCoreFrontendRunning(ctx) { const ps = await dockerCompose(ctx, ["ps", "--status", "running", "--format", "json"], { allowFailure: true }); if (ps.exitCode !== 0) return []; const lines = ps.stdout.trim() === "" ? [] : ps.stdout.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)); const services = lines.map((item) => item.Service); if (services.includes("core") || services.includes("frontend") || services.includes("workspace-maintenance")) { throw new Error("core/frontend/maintenance is unexpectedly running"); } return services; } function sameSet(left, right) { return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort()); } const EMBEDDING_STUB_SOURCE = String.raw`import json from http.server import BaseHTTPRequestHandler, HTTPServer class _Handler(BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers.get("Content-Length", "0")) payload = json.loads(self.rfile.read(length)) inputs = payload.get("input", []) if isinstance(inputs, str): inputs = [inputs] embeddings = [[0.01] * 1024 for _ in inputs] body = json.dumps({"model": payload.get("model", "qwen3-embedding:0.6b"), "embeddings": embeddings}).encode("utf-8") self.send_response(200) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def log_message(self, *args): pass HTTPServer(("0.0.0.0", 11434), _Handler).serve_forever() `; function realUserHome() { try { const output = execFileSync("bash", ["-lc", 'printf "%s" ~'], { encoding: "utf8" }).trim(); return output.length > 0 ? output : undefined; } catch { return undefined; } } async function setupRealContext({ repositoryRoot = defaultRepositoryRoot, env = process.env }) { const run = await createOwnedRun({ repositoryRoot }); const provenance = await collectRepositoryProvenance({ repositoryRoot }); const execs = { gitPath: resolveSystemExecutable("git"), dockerPath: resolveSystemExecutable("docker"), bashPath: resolveSystemExecutable("bash"), }; const pathValue = [...new Set([dirname(execs.gitPath), dirname(execs.dockerPath), "/usr/bin", "/bin", "/opt/homebrew/bin", "/usr/local/bin"])].join(":"); // Docker CLI plugins (buildx) live under the real user's ~/.docker; the wrapper runs with a // scrubbed environment, so derive the real home from the passwd entry and expose DOCKER_CONFIG. const realHome = env.P2_REAL_HOME ?? realUserHome(); const execEnv = buildSafeEnvironment({ ambient: env, fixture: { PATH: pathValue, HOME: run.root, TMPDIR: join(run.root, "tmp"), ...(realHome ? { DOCKER_CONFIG: join(realHome, ".docker") } : {}), } }); const workspaceModules = await import("../dist/workspaces/registry.js").then(async (registryModule) => ({ WorkspaceRegistry: registryModule.WorkspaceRegistry, ...(await import("../dist/workspaces/schema.js")), })); const ctx = { run, repositoryRoot: canonicalRoot(repositoryRoot), provenance, executables: execs, execEnv, workspaceModules, forbiddenValues: [], deviations: [], servers: [], }; await createTopology(run); await mkdir(join(run.root, "tmp"), { recursive: true, mode: 0o700 }); await setupSecrets(ctx); await setupFixtures(ctx); return ctx; } async function executeChecksLocal({ checks, failAt } = {}) { 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) throw new Error("injected acceptance failure"); result = { id: scenario.id, status: "PASS", startedAt, finishedAt: nowIso(), commands: output.commands ?? [], artifacts: output.artifacts ?? [] }; } catch (error) { const detail = error instanceof Error ? error.message : String(error); result = { id: scenario.id, status: "FAIL", startedAt, finishedAt: nowIso(), commands: [], artifacts: [], error: `Acceptance scenario failed safely: ${detail}` }; stopped = true; } } results.push(result); } return results; } async function syntheticChecks(ctx) { const artifact = async (name, value) => { const path = join(ctx.run.root, "logs", `${name}.json`); await writeJson(path, value); return await fileArtifact(ctx.run.root, relative(ctx.run.root, path)); }; return CHECK_IDS.map((id, index) => ({ id, async run() { return { commands: [index === 0 ? "node" : "git"], artifacts: [await artifact(id, { id, synthetic: true })], }; }, })); } async function realChecks(ctx) { const state = {}; return [ { id: "preflight", async run() { await buildThothctl(ctx); await buildCoreImage(ctx); await writeInstallationFiles(ctx); return { commands: ["docker", "node", "git"], artifacts: [ { path: "logs/provenance.json", sha256: sha256(JSON.stringify(ctx.provenance)) }, ], }; }, }, { id: "clean_state", async run() { await startServers(ctx); await initializeGitAndRegistry(ctx); await startQdrant(ctx); await preprovisionCollection(ctx, "p2-dwh"); await preprovisionCollection(ctx, "p2-filesystem"); state.collectionsBefore = await listCollections(ctx); state.runningServices = await assertNoCoreFrontendRunning(ctx); await writeJson(join(ctx.run.root, "logs", "collections-before.json"), state.collectionsBefore); return { commands: ["git", "docker"], artifacts: [await fileArtifact(ctx.run.root, "logs/collections-before.json")] }; }, }, { id: "ownership", async run() { await readAndValidateOwnership({ repositoryRoot: ctx.repositoryRoot, runRoot: ctx.run.root, expectedNonce: ctx.run.nonce }); const installStat = await stat(ctx.installationPath); assert(installStat.isFile(), "installation descriptor missing"); return { commands: ["git"], artifacts: [await fileArtifact(ctx.run.root, "ownership.json")] }; }, }, { id: "inspect_identity", async run() { const response = await runThothctlJson(ctx, "inspect-p2-dwh", ["workspace", "inspect", "--workspace", "p2-dwh"], 0); const snapshot = await loadWorkspaceSnapshot(ctx, "p2-dwh"); assert(response.payload.workspaceId === "p2-dwh", "inspect workspace id mismatch"); assert(response.payload.workspaceRevision === snapshot.active.head, "inspect revision mismatch"); assert(`sha256:${sha256(snapshot.contents)}` === response.payload.descriptorBlob, "inspect descriptor mismatch"); state.inspect = response.payload; return { commands: ["thothctl"], artifacts: response.artifacts }; }, }, { id: "dwh_processing", async run() { const first = await runThothctlJson(ctx, "preprocess-dwh-first", ["workspace", "preprocess", "dwh", "--workspace", "p2-dwh"], 0); assert(first.payload.status === "succeeded" && first.payload.code === "ok", "dwh first run failed"); const rerun = await runThothctlJson(ctx, "preprocess-dwh-rerun", ["workspace", "preprocess", "dwh", "--workspace", "p2-dwh"], 0); const resume = await runThothctlJson(ctx, "preprocess-dwh-resume", ["workspace", "preprocess", "dwh", "--workspace", "p2-dwh", "--resume", first.payload.runId], 0); assert(["unchanged", "succeeded"].includes(rerun.payload.status), "dwh rerun not idempotent"); assert(["unchanged", "succeeded"].includes(resume.payload.status), "dwh resume failed"); state.dwhRunId = first.payload.runId; return { commands: ["thothctl"], artifacts: [...first.artifacts, ...rerun.artifacts, ...resume.artifacts] }; }, }, { id: "schema_review", async run() { const suggest = await runThothctlJson(ctx, "schema-suggest-filesystem", ["workspace", "schema", "suggest-fks", "--workspace", "p2-filesystem"], 3); assert(suggest.payload.code === "manual_review_required", "suggest did not block"); assert(typeof suggest.payload.suggestedFksYaml === "string" && suggest.payload.suggestedFksYaml.length > 0, "suggested FK YAML missing"); const digest = suggest.payload.artifactIdentities?.[0]?.digest; assert(/^sha256:[0-9a-f]{64}$/.test(digest ?? ""), "candidate digest missing"); const candidatePath = join(ctx.run.root, "fixtures", "p2-filesystem.candidates.yaml"); await atomicWrite(candidatePath, suggest.payload.suggestedFksYaml); assert(`sha256:${sha256(suggest.payload.suggestedFksYaml)}` === digest, "candidate digest mismatch"); const annotationsPath = join(ctx.run.root, "fixtures", "p2-filesystem.annotations.yaml"); await atomicWrite(annotationsPath, "tables: {}\n"); const checked = await runThothctlJson(ctx, "schema-check-filesystem", [ "workspace", "schema", "check", "--workspace", "p2-filesystem", "--annotations", annotationsPath, "--reviewed-candidates", digest, ], 0); assert(checked.payload.status === "succeeded", "schema check failed"); state.filesystemCandidateDigest = digest; return { commands: ["thothctl"], artifacts: [...suggest.artifacts, ...checked.artifacts, await fileArtifact(ctx.run.root, "fixtures/p2-filesystem.candidates.yaml"), await fileArtifact(ctx.run.root, "fixtures/p2-filesystem.annotations.yaml")] }; }, }, { id: "schema_index", async run() { const first = await runThothctlJson(ctx, "index-schema-filesystem", ["workspace", "index-schema", "--workspace", "p2-filesystem"], 0); const second = await runThothctlJson(ctx, "index-schema-filesystem-rerun", ["workspace", "index-schema", "--workspace", "p2-filesystem"], 0); assert(["succeeded", "unchanged"].includes(first.payload.status), "index schema first failed"); assert(["unchanged", "succeeded"].includes(second.payload.status), "index schema rerun failed"); return { commands: ["thothctl"], artifacts: [...first.artifacts, ...second.artifacts] }; }, }, { id: "evidence_processing", async run() { const full = await runThothctlJson(ctx, "preprocess-run-dwh-blocked", ["workspace", "preprocess", "run", "--workspace", "p2-dwh"], 3); assert(full.payload.code === "manual_review_required", "full run did not block for review"); const digest = full.payload.artifactIdentities?.[0]?.digest; assert(/^sha256:[0-9a-f]{64}$/.test(digest ?? ""), "full run digest missing"); const reviewPath = join(ctx.run.root, "fixtures", "p2-dwh.annotations.yaml"); await atomicWrite(reviewPath, "tables: {}\n"); const reviewed = await runThothctlJson(ctx, "schema-check-dwh", [ "workspace", "schema", "check", "--workspace", "p2-dwh", "--annotations", reviewPath, "--reviewed-candidates", digest, ], 0); const resumed = await runThothctlJson(ctx, "preprocess-run-dwh-resume", ["workspace", "preprocess", "run", "--workspace", "p2-dwh", "--resume", full.payload.runId], 0); const dryRun = await runThothctlJson(ctx, "preprocess-evidence-dry-run", ["workspace", "preprocess", "evidence", "--workspace", "p2-dwh", "--dry-run"], 0); const publish = await runThothctlJson(ctx, "preprocess-evidence-publish", ["workspace", "preprocess", "evidence", "--workspace", "p2-dwh"], 0); const rerun = await runThothctlJson(ctx, "preprocess-evidence-rerun", ["workspace", "preprocess", "evidence", "--workspace", "p2-dwh"], 0); ctx.evidenceState.content = "# P2 Evidence\n\nSecond generation.\n"; const mutated = await runThothctlJson(ctx, "preprocess-evidence-mutated", ["workspace", "preprocess", "evidence", "--workspace", "p2-dwh"], 0); const fsBlocked = await runThothctlJson(ctx, "preprocess-evidence-filesystem", ["workspace", "preprocess", "evidence", "--workspace", "p2-filesystem"], 3); assert(fsBlocked.payload.code === "evidence_materialization_required", "filesystem evidence did not block"); state.fullRunId = full.payload.runId; return { commands: ["thothctl"], artifacts: [ ...full.artifacts, ...reviewed.artifacts, ...resumed.artifacts, ...dryRun.artifacts, ...publish.artifacts, ...rerun.artifacts, ...mutated.artifacts, ...fsBlocked.artifacts, ] }; }, }, { id: "negative_cases", async run() { const missing = await runThothctlJson(ctx, "negative-missing-workspace", ["workspace", "inspect", "--workspace", "missing-workspace"], 1); const resumeMismatch = await runThothctlJson(ctx, "negative-resume-mismatch", ["workspace", "preprocess", "dwh", "--workspace", "p2-dwh", "--resume", "0".repeat(32)], 1); const annotationInvalid = await runThothctlJson(ctx, "negative-annotation-invalid", [ "workspace", "schema", "check", "--workspace", "p2-filesystem", "--annotations", join(ctx.run.root, "fixtures", "p2-filesystem.annotations.yaml"), "--reviewed-candidates", `sha256:${"0".repeat(64)}`, ], 1); await mutateWorkspaceDescriptor(ctx, "p2-dwh", (workspace) => { delete workspace.evidence; }, "Remove P2 Evidence"); const noEvidence = await runThothctlJson(ctx, "negative-no-evidence-run", ["workspace", "preprocess", "run", "--workspace", "p2-dwh"], 0); const conflict = await runThothctlJson(ctx, "negative-revision-conflict", ["workspace", "preprocess", "run", "--workspace", "p2-dwh", "--resume", state.fullRunId], 1); const after = await listCollections(ctx); assert(sameSet(after, state.collectionsBefore), "product path created or removed a collection"); assert(missing.payload.code === "workspace_not_activatable" || missing.payload.code === "workspace_not_found", "missing workspace code mismatch"); assert(annotationInvalid.payload.code === "annotation_invalid", "annotation invalid code mismatch"); assert(noEvidence.payload.warnings?.includes("workspace has no Evidence source"), "no-Evidence warning missing"); assert(conflict.payload.code === "preprocessing_conflict", "revision conflict code mismatch"); const inspectServices = await assertNoCoreFrontendRunning(ctx); await writeJson(join(ctx.run.root, "logs", "services-after.json"), inspectServices); return { commands: ["thothctl", "docker"], artifacts: [ ...missing.artifacts, ...resumeMismatch.artifacts, ...annotationInvalid.artifacts, ...noEvidence.artifacts, ...conflict.artifacts, await fileArtifact(ctx.run.root, "logs/services-after.json"), ] }; }, }, { id: "secret_scan", async run() { const virtualFiles = []; const qdrantDump = await dumpQdrantPayloads(ctx, "p2-dwh"); if (Buffer.byteLength(qdrantDump) <= MAX_SECRET_SCAN_VIRTUAL_BYTES) virtualFiles.push({ path: "virtual/qdrant-p2-dwh.json", bytes: qdrantDump }); const findings = await scanSecrets({ runRoot: ctx.run.root, forbiddenValues: ctx.forbiddenValues, virtualFiles, expectedGitRepositories: ["remote.git", "author"], }); await writeJson(join(ctx.run.root, "logs", "secret-scan.json"), findings); if (findings.length > 0) throw new Error(`secret scan found ${findings.length} leak(s)`); return { commands: ["git"], artifacts: [await fileArtifact(ctx.run.root, "logs/secret-scan.json")] }; }, }, { id: "cleanup_confinement", async run() { const foreignRoot = join(canonicalIntegrationBase(ctx.repositoryRoot), `p2-${"f".repeat(32)}`); await mkdir(foreignRoot, { recursive: true }); await atomicWrite(join(foreignRoot, "foreign.txt"), "foreign"); assert(readFileSync(join(foreignRoot, "foreign.txt"), "utf8") === "foreign", "foreign sentinel changed unexpectedly"); return { commands: ["git"], artifacts: [] }; }, }, ]; } async function cleanupRuntime(ctx) { await stopServers(ctx).catch(() => {}); if (ctx.installation) await dockerCompose(ctx, ["down", "--remove-orphans", "--timeout", "5"], { allowFailure: true }).catch(() => {}); if (ctx.coreImageTag) await runCommand({ executable: ctx.executables.dockerPath, argv: ["image", "rm", "-f", ctx.coreImageTag], env: ctx.execEnv, maxOutputBytes: MAX_STDIO_BYTES }).catch(() => {}); } export async function runIntegration({ repositoryRoot = defaultRepositoryRoot, keep = false, env = process.env, announce } = {}) { const synthetic = env.P2_ACCEPTANCE_SYNTHETIC === "1"; const failAt = env.P2_ACCEPTANCE_FAIL_AT; const ctx = synthetic ? { run: await createOwnedRun({ repositoryRoot }), repositoryRoot: canonicalRoot(repositoryRoot) } : await setupRealContext({ repositoryRoot, env }); let success = false; try { const checks = synthetic ? await syntheticChecks(ctx) : await realChecks(ctx); const results = await executeChecksLocal({ checks, failAt }); const report = { schemaVersion: 1, runId: ctx.run.runId, startedAt: ctx.run.startedAt, finishedAt: nowIso(), command: "p2-acceptance integration --keep", overall: deriveOverall(results), checks: results, }; await writeReportFiles({ run: ctx.run, report }); success = report.overall === "PASS"; if (announce) await announce({ report, runRoot: ctx.run.root }); return { exitCode: success ? 0 : 1, runRoot: ctx.run.root, retained: !(await finalizeOwnedRun({ run: ctx.run, success, keep })) }; } finally { if (!synthetic) await cleanupRuntime(ctx).catch(() => {}); } } export async function main(argv = process.argv.slice(2), env = process.env) { if (argv.length < 1 || argv[0] !== "integration" || argv.length > 2 || (argv[1] && argv[1] !== "--keep")) { throw new Error("usage: p2-acceptance.mjs integration [--keep]"); } const result = await runIntegration({ keep: argv.includes("--keep"), env }); return result.exitCode; } if (process.argv[1] && realpathSync(process.argv[1]) === modulePath) { try { const code = await main(); process.exitCode = code; } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; } } export { CHECK_IDS };