import { spawnSync } from "node:child_process"; import { createHash, generateKeyPairSync, sign } from "node:crypto"; import { cp, link, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { afterAll, afterEach, describe, expect, it } from "vitest"; import type { CiGateArtifact, CiGateArtifactSchema, } from "../../scripts/contracts/ci-gates.ts"; import { readBoundedRegularFile, validateCiArtifact } from "../../scripts/lib/ci-artifact-validator.ts"; import { writeCiGateLogAtomic } from "../../scripts/lib/ci-gate-log.ts"; import { captureCiCandidateArchive, verifyCiCandidateArchive } from "../../scripts/lib/ci-candidate-archive.ts"; import { CANDIDATE_ARCHIVE_USAGE, parseCandidateArchiveArguments, } from "../../scripts/lib/ci-candidate-archive-cli.ts"; import { validateProviderUpload } from "../../scripts/lib/provider-upload-validator.ts"; import { cleanupFinalizedPromotion, stageVerifiedPromotion, } from "../../scripts/lib/promotion-stager.ts"; import { PROMOTED_FILE_NAMES } from "../../scripts/contracts/promotion-artifacts.ts"; import { providerEvidenceSignaturePayload, providerPublicKeyFingerprint, providerVerificationArtifactSchema, } from "../../scripts/lib/provider-evidence.ts"; import { localEvidenceAssessmentArtifactSchema } from "../../scripts/contracts/release-artifacts.ts"; import { readProviderTrust } from "../../scripts/lib/promotion-verifier.ts"; import { createReleaseCandidateManifest, LOCAL_EVIDENCE_ASSESSMENT_PATH, RELEASE_CANDIDATE_EVIDENCE_PATHS, RELEASE_CANDIDATE_MANIFEST_PATH, } from "../../scripts/lib/release-candidate.ts"; const temporaryRoots: string[] = []; let providerBaseRoot: string | undefined; const sha256 = (value: Buffer | string) => createHash("sha256").update(value).digest("hex"); afterEach(async () => { await Promise.all( temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })), ); }); afterAll(async () => { if (providerBaseRoot) { await rm(providerBaseRoot, { recursive: true, force: true }); } }); async function temporaryRoot(prefix: string): Promise { const root = await mkdtemp(path.join(tmpdir(), prefix)); temporaryRoots.push(root); return root; } async function writeArtifact(root: string, relative: string, value: string | Buffer) { await mkdir(path.dirname(path.join(root, relative)), { recursive: true }); await writeFile(path.join(root, relative), value); } function artifact(pathname: string, schemaId: string): CiGateArtifact { return { id: `artifact-${schemaId}`, path: pathname, schemaId, production: "source-controlled", }; } describe("CI artifact validator", () => { it("does not create directories through a pre-existing log ancestor symlink", async () => { const root = await temporaryRoot("ci-log-root-"); const outside = await temporaryRoot("ci-log-outside-"); await symlink(outside, path.join(root, "linked")); await expect( writeCiGateLogAtomic({ root, relativePath: "linked/new/report.txt", content: "blocked\n", }), ).rejects.toThrow(/ancestor is unsafe/i); await expect( import("node:fs/promises").then(({ lstat }) => lstat(path.join(outside, "new"))), ).rejects.toMatchObject({ code: "ENOENT" }); }); it.each([ ["report.txt", { id: "text", kind: "text", maxBytes: 1_024 }, "gate output\n"], ["report.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, '\n'], ["report.html", { id: "html", kind: "html", maxBytes: 1_024 }, "\n"], ["report.md", { id: "markdown", kind: "markdown", maxBytes: 1_024 }, "# Review\n"], ["schema.json", { id: "json-schema", kind: "json-schema", maxBytes: 1_024 }, '{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object"}\n'], ["scan.sarif", { id: "sarif", kind: "sarif", maxBytes: 4_096 }, JSON.stringify({ version: "2.1.0", $schema: "https://json.schemastore.org/sarif-2.1.0.json", runs: [{ tool: { driver: { name: "ca-frontend-secret-scan", rules: [] } }, results: [] }] })], ] as const)("accepts a valid %s artifact", async (relative, schema, content) => { const root = await temporaryRoot("ci-artifact-kind-"); await writeArtifact(root, relative, content); await expect( validateCiArtifact({ root, artifact: artifact(relative, schema.id), schema: schema as CiGateArtifactSchema, }), ).resolves.toBeUndefined(); }); it("accepts schema-valid negative evidence without treating status as command authority", async () => { const root = await temporaryRoot("ci-artifact-negative-"); const relative = "negative.json"; await writeArtifact( root, relative, `${JSON.stringify({ schemaVersion: 2, sourceRoot: "src", status: "FAIL", facts: { scannedFiles: 0, visualBaselines: 0, sharedScenarios: 0, declaredScenarioExecutions: 0, executedScenarioExecutions: 0 }, failures: ["fixture"] })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact(relative, "test-evidence"), schema: { id: "test-evidence", kind: "json", maxBytes: 4_096, executableSchemaId: "test-evidence-report", }, }), ).resolves.toBeUndefined(); }); const invalidFixtures: ReadonlyArray< readonly [string, (root: string) => Promise, RegExp] > = [ ["missing", async (_root: string): Promise => undefined, /not a regular file|ENOENT/i], ["empty", async (root: string): Promise => { await writeArtifact(root, "report.json", ""); }, /size is outside/i], ["directory", async (root: string): Promise => { await mkdir(path.join(root, "report.json")); }, /not a regular file/i], ["oversized", async (root: string): Promise => { await writeArtifact(root, "report.json", "12345"); }, /size is outside/i], ["invalid UTF-8", async (root: string): Promise => { await writeArtifact(root, "report.json", Buffer.from([0xc3, 0x28])); }, /encoded data was not valid|UTF-8/i], ["primitive JSON", async (root: string): Promise => { await writeArtifact(root, "report.json", "1\n"); }, /record|object/i], ["array JSON", async (root: string): Promise => { await writeArtifact(root, "report.json", "[]\n"); }, /record|object/i], ]; it.each(invalidFixtures)("rejects %s artifacts", async (_name, setup, diagnostic) => { const root = await temporaryRoot("ci-artifact-invalid-"); await setup(root); await expect( validateCiArtifact({ root, artifact: artifact("report.json", "generic"), schema: { id: "generic", kind: "json", maxBytes: _name === "oversized" ? 4 : 4_096, executableSchemaId: "generic-json-object", }, }), ).rejects.toThrow(diagnostic); }); it("rejects leaf and ancestor symlinks before opening evidence", async () => { const root = await temporaryRoot("ci-artifact-symlink-"); await writeArtifact(root, "real.json", "{\"ok\":true}\n"); await symlink("real.json", path.join(root, "leaf.json")); await mkdir(path.join(root, "real-directory")); await writeArtifact(root, "real-directory/report.json", "{\"ok\":true}\n"); await symlink("real-directory", path.join(root, "linked-directory")); const schema = { id: "generic", kind: "json", maxBytes: 4_096, executableSchemaId: "generic-json-object", } as const; await expect( validateCiArtifact({ root, artifact: artifact("leaf.json", "generic"), schema }), ).rejects.toThrow(/not a regular file/i); await expect( validateCiArtifact({ root, artifact: artifact("linked-directory/report.json", "generic"), schema, }), ).rejects.toThrow(/ancestor is unsafe/i); }); it("rejects strict JSON evidence with unknown fields", async () => { const root = await temporaryRoot("ci-artifact-strict-"); await writeArtifact( root, "report.json", `${JSON.stringify({ schemaVersion: 2, nodeVersion: "24.14.0", gateCount: 26, commandDefinitionCount: 81, commandReferenceCount: 93, artifactCount: 105, jobCount: 9, workflowSha256: "a".repeat(64), durationStatus: "UNSUPPORTED", negativeFixtures: [], failures: [], passed: true, unknown: true })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact("report.json", "ci-contract"), schema: { id: "ci-contract", kind: "json", maxBytes: 8_192, executableSchemaId: "ci-contract-report", }, }), ).rejects.toThrow(/unrecognized|unknown/i); }); it.each([ ["broken.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, ""], ["mismatched.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, ""], ["trailing.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "garbage"], ["doctype.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "]>"], ["broken.html", { id: "html", kind: "html", maxBytes: 1_024 }, ""], ] as const)("rejects structurally incomplete %s", async (relative, schema, content) => { const root = await temporaryRoot("ci-artifact-structure-"); await writeArtifact(root, relative, content); await expect( validateCiArtifact({ root, artifact: artifact(relative, schema.id), schema: schema as CiGateArtifactSchema, }), ).rejects.toThrow(/invalid (?:JUnit|HTML) artifact/u); }); it("fails closed when a regular artifact grows after its bounded lstat", async () => { const root = await temporaryRoot("ci-artifact-growth-"); await writeArtifact(root, "report.txt", "1234"); const realHandle = await open(path.join(root, "report.txt"), "r"); await expect( readBoundedRegularFile( { root, relativePath: "report.txt", maxBytes: 4 }, { openFile: async () => ({ stat: async () => realHandle.stat(), read: async (buffer, offset) => { Buffer.from("12345").copy(buffer, offset); return { bytesRead: 5 }; }, close: async () => realHandle.close(), }), }, ), ).rejects.toThrow(/changed size or exceeds bound/i); }); it("rejects cross-field tampering in risk coverage evidence", async () => { const root = await temporaryRoot("ci-artifact-risk-"); const risk = { schemaVersion: 3, policy: "config/testing/risk-coverage.json", summary: "artifacts/tests/coverage/coverage-summary.json", status: "PASS", selectedTotal: 2, repositoryTotal: 2, counterBearingTotal: 1, instrumentedCounterBearingTotal: 1, counterlessTotal: 1, counterlessModules: ["src/types.ts"], preExclusionTotal: 2, generatedExclusionCount: 0, generatedExclusions: [], ownershipScope: "ALL_POLICY_HIGH_RISK", ownedHighRiskPaths: ["src/runtime.ts"], waivedHighRiskPaths: [], uncoveredModules: [], results: ["lines", "statements", "functions", "branches"].map((metric) => ({ scope: "summary", metric, threshold: 80, received: 90, passed: true, })), failures: [], }; const schema = { id: "risk", kind: "json", maxBytes: 16_384, executableSchemaId: "risk-coverage-v3", } as const; await writeArtifact(root, "risk.json", `${JSON.stringify(risk)}\n`); await expect( validateCiArtifact({ root, artifact: artifact("risk.json", "risk"), schema }), ).resolves.toBeUndefined(); await writeArtifact( root, "risk.json", `${JSON.stringify({ ...risk, counterlessTotal: 0 })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact("risk.json", "risk"), schema }), ).rejects.toThrow(/counter partition|counterless list length/u); for (const [mutation, diagnostic] of [ [{ preExclusionTotal: 3 }, /pre-exclusion inventory total drift/u], [{ status: "PASS", failures: [], results: risk.results.map((entry, index) => index === 0 ? { ...entry, received: 70, passed: false } : entry) }, /status must agree with failures and threshold results/u], [{ waivedHighRiskPaths: ["src/runtime.ts"] }, /owned and waived high-risk paths overlap/u], [{ results: risk.results.slice(0, 3) }, /all four metrics/u], [{ results: [...risk.results, risk.results[0]] }, /duplicated within scope/u], [{ results: [] }, /too small|at least 4/iu], ] as const) { await writeArtifact(root, "risk.json", `${JSON.stringify({ ...risk, ...mutation })}\n`); await expect( validateCiArtifact({ root, artifact: artifact("risk.json", "risk"), schema }), ).rejects.toThrow(diagnostic); } }); it("rejects coverage counters whose covered and skipped partitions exceed total", async () => { const root = await temporaryRoot("ci-artifact-coverage-"); const counter = { total: 10, covered: 8, skipped: 3, pct: 80 }; await writeArtifact( root, "coverage.json", `${JSON.stringify({ total: { lines: counter, statements: counter, functions: counter, branches: counter } })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact("coverage.json", "coverage"), schema: { id: "coverage", kind: "json", maxBytes: 4_096, executableSchemaId: "coverage-summary-v8", }, }), ).rejects.toThrow(/coverage counter exceeds total/u); }); }); describe("candidate archive and provider upload boundaries", () => { it("accepts only the manifest-bound candidate member set and bytes", async () => { const fixture = await createCandidateArchiveFixture(); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath }), ).resolves.toEqual( expect.objectContaining({ archiveSha256: sha256(await readFile(fixture.archivePath)) }), ); }); it("rejects an extra candidate member before extraction", async () => { const fixture = await createCandidateArchiveFixture({ extraMember: true }); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath }), ).rejects.toThrow(/exact member set drift before extraction/i); }); it("rejects duplicate archive members before extraction", async () => { const fixture = await createCandidateArchiveFixture({ duplicateMember: true }); await expect(verifyCiCandidateArchive({ archivePath: fixture.archivePath })) .rejects.toThrow(/duplicate member/i); }); it.each(["symlink", "hardlink"] as const)("rejects a %s archive member without touching an outside canary", async (kind) => { const root = await temporaryRoot(`ci-candidate-${kind}-`); const outside = await temporaryRoot(`ci-candidate-${kind}-outside-`); const canary = path.join(outside, "canary"); await writeFile(canary, "unchanged\n"); await writeArtifact(root, "target", "target\n"); if (kind === "symlink") await symlink("target", path.join(root, "unsafe")); else await link(path.join(root, "target"), path.join(root, "unsafe")); const archivePath = path.join(root, "unsafe.tar.gz"); const tar = spawnSync("/usr/bin/tar", ["-czf", archivePath, ...(kind === "hardlink" ? ["target"] : []), "unsafe"], { cwd: root, encoding: "utf8" }); if (tar.status !== 0) throw new Error(tar.stderr); await expect(verifyCiCandidateArchive({ archivePath })).rejects.toThrow(/non-regular member/i); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }); it("rejects traversal members and preserves the outside canary", async () => { const root = await temporaryRoot("ci-candidate-traversal-"); const canary = path.join(root, "outside-canary"); await writeArtifact(root, "safe", "safe\n"); await writeFile(canary, "unchanged\n"); const archivePath = path.join(root, "traversal.tar.gz"); const tar = spawnSync("/usr/bin/tar", ["-czf", archivePath, "--transform=s|safe|../outside-canary|", "safe"], { cwd: root, encoding: "utf8" }); if (tar.status !== 0) throw new Error(tar.stderr); await expect(verifyCiCandidateArchive({ archivePath })).rejects.toThrow(/unsafe member path/i); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }); it("rejects an oversized manifest from tar headers before full extraction", async () => { const fixture = await createCandidateArchiveFixture({ oversizedManifest: true }); await expect(verifyCiCandidateArchive({ archivePath: fixture.archivePath })) .rejects.toThrow(/manifest exceeds 8388608 bytes/i); }); it("rejects an expanded-byte bomb before extraction and preserves its canary", async () => { const root = await temporaryRoot("ci-candidate-expanded-bomb-"); const huge = path.join(root, "huge.bin"); const handle = await open(huge, "w"); await handle.truncate(268_435_457); await handle.close(); const canary = path.join(root, "canary"); await writeFile(canary, "unchanged\n"); const archivePath = path.join(root, "bomb.tar.gz"); const tar = spawnSync("/usr/bin/tar", ["-czf", archivePath, "huge.bin"], { cwd: root, encoding: "utf8", timeout: 30_000, }); if (tar.status !== 0) throw new Error(tar.stderr || String(tar.error)); await expect(verifyCiCandidateArchive({ archivePath })) .rejects.toThrow(/expanded bytes exceed the bound/i); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }, 40_000); it.each([ ["missing value", ["--archive"]], [ "option-like value", ["--archive", "missing.tar.gz", "--extract-to", "--github-output", "out"], ], ])("maps a %s to the deterministic CLI Usage result", (_label, arguments_) => { expect(parseCandidateArchiveArguments(arguments_)).toBeNull(); expect(CANDIDATE_ARCHIVE_USAGE).toBe( "Usage: verify-ci-candidate-archive --archive [--extract-to ] [--github-output ]\n", ); }); it("rejects archive digest mismatch and symlink substitution", async () => { const fixture = await createCandidateArchiveFixture(); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath, expectedSha256: "0".repeat(64), }), ).rejects.toThrow(/SHA-256 mismatch/u); const linked = `${fixture.archivePath}.link`; await symlink(path.basename(fixture.archivePath), linked); await expect( verifyCiCandidateArchive({ archivePath: linked }), ).rejects.toThrow(/regular non-symlink/u); }); it("rejects an excessive archive member universe before per-member reads", async () => { const fixture = await createCandidateArchiveFixture({ repeatedExtraMembers: 8_200 }); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath }), ).rejects.toThrow(/member count is outside 1\.\.8192|exceeds 8192 members/u); }); it("uses the captured archive inode when the pathname is replaced mid-verification", async () => { const original = await createCandidateArchiveFixture(); const replacement = await createCandidateArchiveFixture({ extraMember: true }); const originalArchiveSha256 = sha256(await readFile(original.archivePath)); const displaced = `${original.archivePath}.displaced`; const extractTo = path.join(path.dirname(original.archivePath), "verified-candidate"); await expect( verifyCiCandidateArchive( { archivePath: original.archivePath, extractTo, repositoryRoot: path.dirname(original.archivePath), }, { afterArchiveRead: async () => { await rename(original.archivePath, displaced); await rename(replacement.archivePath, original.archivePath); }, }, ), ).resolves.toEqual(expect.objectContaining({ archiveSha256: originalArchiveSha256 })); await expect(readFile(displaced)).resolves.toBeDefined(); await expect(readFile(path.join(extractTo, "dist/app.js"), "utf8")).resolves.toBe("app\n"); }); it("validates provider JSON against candidate dist and lockfile digests", async () => { const fixture = await createProviderFixture(); await expect( validateProviderUpload(providerValidationInput(fixture)), ).resolves.toEqual(expect.objectContaining({ provider: "fixture" })); const report = JSON.parse(await readFile(fixture.reportPath, "utf8")) as Record; report.candidate.distSha256 = "f".repeat(64); report.signature.value = sign( null, providerEvidenceSignaturePayload(report), fixture.privateKey, ).toString("base64"); await expect( validateProviderUpload( providerValidationInput(fixture, Buffer.from(`${JSON.stringify(report)}\n`)), ), ).rejects.toThrow(/candidate identity mismatch/i); }, 30_000); it("uses the reverified archive manifest when extracted candidate files are mutated", async () => { const fixture = await createProviderFixture(); await writeArtifact(fixture.candidateRoot, "dist/app.js", "mutated\n"); const mutableManifest = JSON.parse( await import("node:fs/promises").then(({ readFile }) => readFile(path.join(fixture.candidateRoot, RELEASE_CANDIDATE_MANIFEST_PATH), "utf8"), ), ) as Record; mutableManifest.distSha256 = "e".repeat(64); await writeFile( path.join(fixture.candidateRoot, RELEASE_CANDIDATE_MANIFEST_PATH), `${JSON.stringify(mutableManifest)}\n`, ); await expect( validateProviderUpload(providerValidationInput(fixture)), ).rejects.toThrow(/candidate root changed/i); }); it("rejects symlinked provider reports at the bounded file boundary", async () => { const fixture = await createProviderFixture(); const real = path.join(fixture.root, "real-report.json"); await writeFile(real, await readFile(fixture.reportPath)); await rm(fixture.reportPath); await symlink(path.relative(path.dirname(fixture.reportPath), real), fixture.reportPath); await expect( readBoundedRegularFile({ root: fixture.root, relativePath: path.relative(fixture.root, fixture.reportPath), maxBytes: 8_388_608, }), ).rejects.toThrow(/not a regular file/i); }); it("rejects oversized provider reports before JSON parsing", async () => { const fixture = await createProviderFixture(); await writeFile(fixture.reportPath, Buffer.alloc(8_388_609, 0x20)); await expect( readBoundedRegularFile({ root: fixture.root, relativePath: path.relative(fixture.root, fixture.reportPath), maxBytes: 8_388_608, }), ).rejects.toThrow(/size is outside/u); }); it("rejects a stale raw provider report before starting the provider", async () => { const fixture = await createProviderFixture(); const markerPath = path.join(fixture.root, "provider-started"); const result = runProviderSupervisor(fixture, { command: `node -e 'require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "started")'`, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), }); expect(result.status).not.toBe(0); expect(result.stderr).toMatch(/raw provider report already exists/i); await expect(readFile(markerPath)).rejects.toMatchObject({ code: "ENOENT" }); }); it("drains and kills provider background processes before sealing evidence", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const sealedPath = path.join( fixture.root, "provider-evidence/vulnerability-report.json", ); const mutatorMarker = path.join(fixture.root, "background-mutator-ran"); const providerScript = path.join(fixture.root, "provider.mjs"); const mutator = [ "process.on('SIGTERM', () => {});", "setTimeout(() => {", ` require('node:fs').writeFileSync(${JSON.stringify(fixture.protectedCandidatePath)}, 'mutated\\n');`, ` require('node:fs').writeFileSync(${JSON.stringify(sealedPath)}, '{"mutated":true}\\n');`, ` require('node:fs').writeFileSync(${JSON.stringify(mutatorMarker)}, 'ran\\n');`, "}, 1200);", ].join("\n"); await writeFile( providerScript, [ "import { spawn } from 'node:child_process';", providerV2WriterSource(), `const child = spawn(process.execPath, ['-e', ${JSON.stringify(mutator)}], { stdio: 'ignore' });`, "child.unref();", ].join("\n"), ); const result = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(providerScript)}`, sealedPath, }); expect(result.status, result.stderr).toBe(0); await delay(1_500); await expect(readFile(fixture.protectedCandidatePath)).resolves.toEqual( fixture.protectedCandidateBytes, ); await expect(readFile(mutatorMarker)).rejects.toMatchObject({ code: "ENOENT" }); expect(JSON.parse(await readFile(sealedPath, "utf8"))).toEqual( expect.objectContaining({ provider: "fixture" }), ); }, 10_000); it("does not expose or mutate a host path outside the sandboxed workspace", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const outside = await temporaryRoot("provider-host-canary-"); const canary = path.join(outside, "secret-canary"); await writeFile(canary, "host-secret\n"); const providerScript = path.join(fixture.root, "provider-host-boundary.mjs"); await writeFile(providerScript, [ "import { readFileSync, writeFileSync } from 'node:fs';", `try { readFileSync(${JSON.stringify(canary)}); process.exit(9); } catch {}`, `try { writeFileSync(${JSON.stringify(canary)}, 'mutated\\n'); } catch {}`, providerV2WriterSource({ importFs: false }), ].join("\n")); const result = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(providerScript)}`, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), }); expect(result.status, result.stderr).toBe(0); await expect(readFile(canary, "utf8")).resolves.toBe("host-secret\n"); }, 10_000); }); describe("verified promotion finalizer", () => { it("creates no input records and publishes deterministic exact-five strict v3 bindings", async () => { const fixture = await createPromotionStagingFixture(); let archiveCaptureCount = 0; await expect(readFile(path.join(fixture.root, "artifacts/security/provider-verification.json"))) .rejects.toMatchObject({ code: "ENOENT" }); await expect(readFile(path.join(fixture.root, "artifacts/security/promotion-verification.json"))) .rejects.toMatchObject({ code: "ENOENT" }); const dependencies = { ...fixture.dependencies, captureArchive: async (input: Parameters[0]) => { archiveCaptureCount += 1; return captureCiCandidateArchive(input); }, }; const finalized = await stageVerifiedPromotion(fixture.input, dependencies); expect(archiveCaptureCount).toBe(1); expect(finalized.files.map(({ name }) => name)).toEqual(PROMOTED_FILE_NAMES); expect((await readdir(finalized.stagingRoot)).sort()).toEqual([...PROMOTED_FILE_NAMES].sort()); expect(finalized.stagingRoot).toBe(path.join(fixture.runnerTempRoot, fixture.cleanupToken)); expect(finalized.stagingRoot).not.toContain(".release/promoted-staging"); expect((await lstat(finalized.stagingRoot)).mode & 0o777).toBe(0o700); for (const file of finalized.files) { expect((await lstat(path.join(finalized.stagingRoot, file.name))).mode & 0o777).toBe(0o400); expect(file.sha256).toBe(sha256(await readFile(path.join(finalized.stagingRoot, file.name)))); } const providerBytes = await readFile(path.join(finalized.stagingRoot, "provider-verification.json")); const promotionBytes = await readFile(path.join(finalized.stagingRoot, "promotion-verification.json")); const provider = providerVerificationArtifactSchema.parse(JSON.parse(providerBytes.toString("utf8"))); const promotion = providerVerificationArtifactSchema.parse(JSON.parse(promotionBytes.toString("utf8"))); expect(provider.artifactType).toBe("provider-verification"); expect(promotion.artifactType).toBe("promotion-verification"); if (provider.artifactType !== "provider-verification" || promotion.artifactType !== "promotion-verification") { throw new Error("verification record role narrowing failed"); } expect(provider.status).toBe("PASS"); expect(promotion.status).toBe("PASS"); expect(promotion.providerVerificationSha256).toBe(sha256(providerBytes)); expect(promotion.localEvidenceAssessmentSha256).toBe(fixture.assessmentSha256); expect(promotion.run).toEqual(fixture.expectedContext.run); expect(promotion.source).toEqual(fixture.expectedContext.source); expect(promotion.candidate).toEqual(fixture.expectedContext.candidate); expect(promotion.providerEvidence).toEqual(provider.providerEvidence); expect(promotion.providerEvidence).toEqual(fixture.expectedProviderEvidence); expect(promotion.trustPolicySha256).toBe(provider.trustPolicySha256); expect(provider.vulnerabilityStatus).toBe("PASS"); expect(provider.provenanceAttestationStatus).toBe("PASS"); await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); const repeated = await stageVerifiedPromotion(fixture.input, dependencies); expect(archiveCaptureCount).toBe(2); await expect(readFile(path.join(repeated.stagingRoot, "provider-verification.json"))) .resolves.toEqual(providerBytes); await expect(readFile(path.join(repeated.stagingRoot, "promotion-verification.json"))) .resolves.toEqual(promotionBytes); await cleanupFinalizedPromotion(finalizedCleanup(fixture, repeated)); }, 30_000); it("stages captured archive and reports and validates captured keys after source mutation", async () => { const fixture = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(fixture.input, { ...fixture.dependencies, afterCapture: async () => { await Promise.all([ writeFile(fixture.input.archivePath, "replaced archive\n"), writeFile(fixture.input.vulnerabilityReportPath, "replaced vulnerability\n"), writeFile(fixture.input.provenanceAttestationPath, "replaced provenance\n"), writeFile(fixture.input.vulnerabilityPublicKeyPath, "replaced vulnerability key\n"), writeFile(fixture.input.provenancePublicKeyPath, "replaced provenance key\n"), ]); }, }); for (const [name, bytes] of fixture.capturedSources) { await expect(readFile(path.join(finalized.stagingRoot, name))).resolves.toEqual(bytes); } await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); }, 30_000); it.each([ ["archive digest", async (fixture: Awaited>) => { fixture.input = { ...fixture.input, expectedArchiveSha256: "0".repeat(64) }; }, /archive SHA-256/u], ["report mutation", async (fixture: Awaited>) => { await writeFile(fixture.input.vulnerabilityReportPath, "mutated report\n"); }, /invalid|provider/u], ["key rotation", async (fixture: Awaited>) => { const rotated = generateKeyPairSync("ed25519"); await writeFile(fixture.input.vulnerabilityPublicKeyPath, rotated.publicKey.export({ type: "spki", format: "pem" })); }, /provider evidence failed|trust identity/u], ["expected nonce replay", async (fixture: Awaited>) => { fixture.input = { ...fixture.input, vulnerabilityInvocationNonce: "9".repeat(64) }; }, /invocation nonce/u], ] as const)("rejects %s without staging or PASS records", async (_label, mutate, diagnostic) => { const fixture = await createPromotionStagingFixture(); await mutate(fixture); await expect(stageVerifiedPromotion(fixture.input, fixture.dependencies)).rejects.toThrow(diagnostic); await expect(readdir(fixture.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("rejects symlinked and oversized captured sources without staging", async () => { const linked = await createPromotionStagingFixture(); const realReport = path.join(linked.root, "real-vulnerability-report.json"); await writeFile(realReport, await readFile(linked.input.vulnerabilityReportPath)); await rm(linked.input.vulnerabilityReportPath); await symlink(realReport, linked.input.vulnerabilityReportPath); await expect(stageVerifiedPromotion(linked.input, linked.dependencies)).rejects.toThrow(/regular file/i); await expect(readdir(linked.runnerTempRoot)).resolves.toEqual([]); const oversized = await createPromotionStagingFixture(); await writeFile(oversized.input.vulnerabilityReportPath, Buffer.alloc(16_777_217, 0x20)); await expect(stageVerifiedPromotion(oversized.input, oversized.dependencies)).rejects.toThrow(/size is outside/i); await expect(readdir(oversized.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("detects a runner-temp parent identity swap and removes its owned partial staging", async () => { const fixture = await createPromotionStagingFixture(); const displaced = `${fixture.runnerTempRoot}-displaced`; await expect(stageVerifiedPromotion(fixture.input, { ...fixture.dependencies, afterStagingWrite: async () => { await rename(fixture.runnerTempRoot, displaced); await mkdir(fixture.runnerTempRoot, { mode: 0o700 }); }, })).rejects.toThrow(/parent identity changed/u); await expect(readdir(displaced)).resolves.toEqual([]); await expect(readdir(fixture.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("cleanup is token-bound and removes only the finalized private directory", async () => { const fixture = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(fixture.input, fixture.dependencies); const canary = path.join(fixture.runnerTempRoot, "canary"); await writeFile(canary, "unchanged\n"); await expect(cleanupFinalizedPromotion({ runnerTempRoot: fixture.runnerTempRoot, stagingRoot: finalized.stagingRoot, cleanupToken: `${finalized.cleanupToken}-wrong`, runnerTempIdentity: finalized.runnerTempIdentity, })).rejects.toThrow(/root\/token mismatch/u); await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); await expect(lstat(finalized.stagingRoot)).rejects.toMatchObject({ code: "ENOENT" }); }, 30_000); it("cleanup rejects a swapped runner-temp ancestor and a symlinked staging leaf", async () => { const swapped = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(swapped.input, swapped.dependencies); const displaced = `${swapped.runnerTempRoot}-cleanup-displaced`; await expect(cleanupFinalizedPromotion(finalizedCleanup(swapped, finalized), { beforeRemove: async () => { await rename(swapped.runnerTempRoot, displaced); await mkdir(swapped.runnerTempRoot, { mode: 0o700 }); }, })).rejects.toThrow(/parent identity changed/u); await expect(readdir(swapped.runnerTempRoot)).resolves.toEqual([]); await expect(readdir(displaced)).resolves.toEqual([finalized.cleanupToken]); const linked = await createPromotionStagingFixture(); const linkedFinalized = await stageVerifiedPromotion(linked.input, linked.dependencies); const saved = `${linkedFinalized.stagingRoot}-saved`; const outside = await temporaryRoot("promotion-cleanup-outside-"); const canary = path.join(outside, "canary"); await writeFile(canary, "unchanged\n"); await rename(linkedFinalized.stagingRoot, saved); await symlink(outside, linkedFinalized.stagingRoot); await expect(cleanupFinalizedPromotion(finalizedCleanup(linked, linkedFinalized))) .rejects.toThrow(/leaf is unsafe/u); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }, 30_000); }); function runProviderSupervisor( fixture: Awaited>, input: Readonly<{ command: string; sealedPath: string }>, ) { return spawnSync( process.execPath, [path.resolve("scripts/run-and-validate-provider.ts"), "--kind", "vulnerability"], { cwd: fixture.root, encoding: "utf8", timeout: 8_000, env: { ...process.env, VULNERABILITY_PROVIDER_COMMAND: input.command, VULNERABILITY_PROVIDER_PRIVATE_KEY_PATH: fixture.privateKeyPath, VULNERABILITY_PUBLIC_KEY_PATH: fixture.publicKeyPath, VULNERABILITY_KEY_ID: fixture.keyId, VULNERABILITY_REPORT_PATH: fixture.reportPath, VALIDATED_PROVIDER_REPORT_PATH: input.sealedPath, CANDIDATE_ARCHIVE_PATH: fixture.archivePath, CANDIDATE_ARCHIVE_SHA256: fixture.archiveSha256, CI_RUN_ID: fixture.expectedContext.run.id, CI_RUN_ATTEMPT: String(fixture.expectedContext.run.attempt), EXPECTED_SOURCE_REVISION: fixture.expectedContext.source.revision, }, }, ); } function providerValidationInput( fixture: Awaited>, capturedReport: Buffer = fixture.reportBytes, ) { return { kind: "vulnerability" as const, verifiedManifest: fixture.candidate, archiveSha256: fixture.archiveSha256, candidateRoot: fixture.candidateRoot, capturedReport, expectedContext: fixture.expectedContext, trust: fixture.trust, nowEpochMs: () => fixture.now, }; } function providerV2WriterSource( options: Readonly<{ importFs?: boolean }> = {}, ): string { return [ "import { createPrivateKey, sign } from 'node:crypto';", ...(options.importFs === false ? [] : ["import { readFileSync, writeFileSync } from 'node:fs';"]), "const canonical = (value) => {", " if (Array.isArray(value)) return value.map(canonical).sort((left, right) => String(JSON.stringify(left)).localeCompare(String(JSON.stringify(right))));", " if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonical(item)]));", " return value;", "};", "const unsigned = {", " schemaVersion: Number(process.env.PROVIDER_EVIDENCE_SCHEMA_VERSION),", " evidenceType: process.env.PROVIDER_EVIDENCE_TYPE,", " provider: 'fixture',", " issuedAt: process.env.PROVIDER_ISSUED_AT,", " expiresAt: process.env.PROVIDER_EXPIRES_AT,", " run: { id: process.env.CI_RUN_ID, attempt: Number(process.env.CI_RUN_ATTEMPT), invocationNonce: process.env.PROVIDER_INVOCATION_NONCE },", " source: { revision: process.env.SOURCE_REVISION, sourceSetSha256: process.env.SOURCE_SET_SHA256 },", " candidate: { archiveSha256: process.env.CANDIDATE_ARCHIVE_SHA256, bundleSha256: process.env.CANDIDATE_BUNDLE_SHA256, distSha256: process.env.CANDIDATE_DIST_SHA256, lockfileSha256: process.env.CANDIDATE_LOCKFILE_SHA256 },", " findings: [],", "};", "const privateKey = createPrivateKey(readFileSync(process.env.VULNERABILITY_PROVIDER_PRIVATE_KEY_PATH));", "const value = sign(null, Buffer.from(JSON.stringify(canonical(unsigned))), privateKey).toString('base64');", "writeFileSync(process.env.VULNERABILITY_REPORT_PATH, `${JSON.stringify({ ...unsigned, signature: { algorithm: 'Ed25519', keyId: process.env.PROVIDER_KEY_ID, publicKeyFingerprint: process.env.PROVIDER_PUBLIC_KEY_FINGERPRINT, value } })}\\n`);", ].join("\n"); } async function createCandidateArchiveFixture( options: Readonly<{ extraMember?: boolean; repeatedExtraMembers?: number; duplicateMember?: boolean; oversizedManifest?: boolean }> = {}, ): Promise> { const root = await temporaryRoot("ci-candidate-archive-"); const files = new Map(); files.set("dist/app.js", Buffer.from("app\n")); for (const evidencePath of RELEASE_CANDIDATE_EVIDENCE_PATHS) { files.set(evidencePath, Buffer.from(`${evidencePath}\n`)); } for (const [relative, content] of files) await writeArtifact(root, relative, content); await writeArtifact( root, "artifacts/release/dependency-inventory.json", `${JSON.stringify({ lockfileSha256: sha256(files.get("pnpm-lock.yaml")!) })}\n`, ); const manifest = await createReleaseCandidateManifest(root); await writeArtifact( root, RELEASE_CANDIDATE_MANIFEST_PATH, `${JSON.stringify(manifest)}${options.oversizedManifest ? " ".repeat(8_388_609) : "\n"}`, ); if (options.extraMember || options.repeatedExtraMembers) { await writeArtifact(root, "extra.txt", "extra\n"); } const archivePath = path.join(root, "candidate.tar.gz"); const members = [ "dist", ...RELEASE_CANDIDATE_EVIDENCE_PATHS, RELEASE_CANDIDATE_MANIFEST_PATH, ...(options.duplicateMember ? ["pnpm-lock.yaml"] : []), ...(options.extraMember ? ["extra.txt"] : []), ...Array.from({ length: options.repeatedExtraMembers ?? 0 }, () => "extra.txt"), ]; const tar = spawnSync("tar", [...(options.duplicateMember ? ["--hard-dereference"] : []), "-czf", archivePath, ...members], { cwd: root, encoding: "utf8", }); if (tar.status !== 0) throw new Error(tar.stderr); return { root, archivePath }; } async function createProviderFixture() { const base = await ensureProviderBaseFixture(); const root = await temporaryRoot("ci-provider-upload-"); await cp(base, root, { recursive: true }); const candidateRoot = root; const candidate = JSON.parse( await readFile(path.join(root, RELEASE_CANDIDATE_MANIFEST_PATH), "utf8"), ) as Awaited>; const protectedCandidateRelative = candidate.files.find((file) => file.path.startsWith("dist/"), )?.path; if (!protectedCandidateRelative) { throw new Error("provider fixture candidate has no dist file"); } const protectedCandidatePath = path.join(root, protectedCandidateRelative); const protectedCandidateBytes = await readFile(protectedCandidatePath); const assessment = localEvidenceAssessmentArtifactSchema.parse( JSON.parse( await readFile(path.join(root, LOCAL_EVIDENCE_ASSESSMENT_PATH), "utf8"), ) as unknown, ); const archivePath = path.join(root, "candidate.tar.gz"); const archiveSha256 = sha256(await readFile(archivePath)); const keys = generateKeyPairSync("ed25519"); const keyId = "fixture-vulnerability-key"; const publicKeyPath = path.join(root, "keys/vulnerability.pem"); const privateKeyPath = path.join(root, "keys/vulnerability-private.pem"); await writeArtifact( root, "keys/vulnerability.pem", keys.publicKey.export({ type: "spki", format: "pem" }), ); await writeArtifact( root, "keys/vulnerability-private.pem", keys.privateKey.export({ type: "pkcs8", format: "pem" }), ); const trust = (await readProviderTrust(root, publicKeyPath, keyId))!; const now = Date.parse("2026-08-02T01:00:00.000Z"); const expectedContext = { run: { id: "fixture-run", attempt: 1 }, source: { revision: assessment.source.revision, sourceSetSha256: assessment.source.sourceSetSha256, }, candidate: { archiveSha256, bundleSha256: candidate.bundleSha256, distSha256: candidate.distSha256, lockfileSha256: candidate.lockfileSha256, }, vulnerabilityInvocationNonce: "1".repeat(64), provenanceInvocationNonce: "0".repeat(64), } as const; const unsigned = { schemaVersion: 2 as const, evidenceType: "vulnerability-report" as const, provider: "fixture", issuedAt: "2026-08-02T01:00:00.000Z", expiresAt: "2026-08-02T02:00:00.000Z", run: { ...expectedContext.run, invocationNonce: expectedContext.vulnerabilityInvocationNonce }, source: expectedContext.source, candidate: expectedContext.candidate, findings: [], }; const report = { ...unsigned, signature: { algorithm: "Ed25519" as const, keyId, publicKeyFingerprint: providerPublicKeyFingerprint(keys.publicKey), value: sign( null, providerEvidenceSignaturePayload(unsigned), keys.privateKey, ).toString("base64"), }, }; const reportPath = path.join( root, "provider-evidence/untrusted/vulnerability-report.json", ); await writeArtifact( root, "provider-evidence/untrusted/vulnerability-report.json", `${JSON.stringify(report)}\n`, ); const reportBytes = Buffer.from(`${JSON.stringify(report)}\n`); return { root, candidateRoot, candidate, protectedCandidatePath, protectedCandidateBytes, archivePath, archiveSha256, reportPath, reportBytes, expectedContext, trust, now, publicKeyPath, privateKeyPath, privateKey: keys.privateKey, keyId, providerWriter: path.join(root, "provider-v2-writer.mjs"), }; } async function ensureProviderBaseFixture(): Promise { if (providerBaseRoot) return providerBaseRoot; const sourceRoot = process.cwd(); const root = await mkdtemp(path.join(tmpdir(), "ci-provider-v2-base-")); await cp(sourceRoot, root, { recursive: true, filter: (source) => { const relative = path.relative(sourceRoot, source); if (!relative) return true; const first = relative.split(path.sep)[0]; return ![".release", "artifacts", "dist", "node_modules"].includes(first ?? ""); }, }); await cp(path.join(sourceRoot, "artifacts"), path.join(root, "artifacts"), { recursive: true, }); await rm(path.join(root, "artifacts/release"), { recursive: true, force: true }); await symlink(path.join(sourceRoot, "node_modules"), path.join(root, "node_modules"), "dir"); const git = spawnSync("git", ["show", "-s", "--format=%H%n%ct", "HEAD"], { cwd: sourceRoot, encoding: "utf8", }); if (git.status !== 0) throw new Error(git.stderr); const [revision, sourceDateEpoch] = git.stdout.trim().split(/\r?\n/u); const build = spawnSync("corepack", ["pnpm", "build:release-candidate"], { cwd: root, encoding: "utf8", timeout: 120_000, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, CI: "true", VITE_BUILD_ID: "provider-v2-fixture", VITE_COMMIT_SHA: revision, RELEASE_ID: "provider-v2-fixture", SOURCE_DATE_EPOCH: sourceDateEpoch, CI_RUNNER_IMAGE: `fixture@sha256:${"a".repeat(64)}`, }, }); if (build.status !== 0) throw new Error(`${build.stdout}\n${build.stderr}`); const archivePath = path.join(root, "candidate.tar.gz"); const tar = spawnSync( "/usr/bin/tar", [ "--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner", "-czf", archivePath, "dist", ...RELEASE_CANDIDATE_EVIDENCE_PATHS, RELEASE_CANDIDATE_MANIFEST_PATH, ], { cwd: root, encoding: "utf8" }, ); if (tar.status !== 0) throw new Error(tar.stderr); providerBaseRoot = root; return root; } async function createPromotionStagingFixture() { const base = await ensureProviderBaseFixture(); const root = await temporaryRoot("promotion-finalizer-"); const archivePath = path.join(root, "inputs/release-candidate.tar.gz"); await mkdir(path.dirname(archivePath), { recursive: true }); await cp(path.join(base, "candidate.tar.gz"), archivePath); const candidate = await verifyCiCandidateArchive({ archivePath }); const candidateArchiveBytes = await readFile(archivePath); const assessmentBytes = await readFile(path.join(base, LOCAL_EVIDENCE_ASSESSMENT_PATH)); const assessment = localEvidenceAssessmentArtifactSchema.parse( JSON.parse(assessmentBytes.toString("utf8")) as unknown, ); const vulnerabilityKeys = generateKeyPairSync("ed25519"); const provenanceKeys = generateKeyPairSync("ed25519"); const vulnerabilityKeyId = "fixture-vulnerability"; const provenanceKeyId = "fixture-provenance"; const vulnerabilityInvocationNonce = "5".repeat(64); const provenanceInvocationNonce = "6".repeat(64); const expectedContext = { run: { id: "fixture-run", attempt: 1 }, source: { revision: assessment.source.revision, sourceSetSha256: assessment.source.sourceSetSha256, }, candidate: { archiveSha256: sha256(candidateArchiveBytes), bundleSha256: candidate.manifest.bundleSha256, distSha256: candidate.manifest.distSha256, lockfileSha256: candidate.manifest.lockfileSha256, }, } as const; const vulnerabilityUnsigned = { schemaVersion: 2 as const, evidenceType: "vulnerability-report" as const, provider: "fixture-vulnerability", issuedAt: "2026-08-02T01:00:00.000Z", expiresAt: "2026-08-02T02:00:00.000Z", run: { ...expectedContext.run, invocationNonce: vulnerabilityInvocationNonce }, source: expectedContext.source, candidate: expectedContext.candidate, findings: [], }; const vulnerabilityReport = { ...vulnerabilityUnsigned, signature: { algorithm: "Ed25519" as const, keyId: vulnerabilityKeyId, publicKeyFingerprint: providerPublicKeyFingerprint(vulnerabilityKeys.publicKey), value: sign( null, providerEvidenceSignaturePayload(vulnerabilityUnsigned), vulnerabilityKeys.privateKey, ).toString("base64"), }, }; const provenanceUnsigned = { schemaVersion: 2 as const, evidenceType: "provenance-attestation" as const, provider: "fixture-provenance", signer: "fixture-signer", issuedAt: "2026-08-02T01:00:00.000Z", expiresAt: "2026-08-02T02:00:00.000Z", run: { ...expectedContext.run, invocationNonce: provenanceInvocationNonce }, source: expectedContext.source, candidate: expectedContext.candidate, subject: { name: "dist" as const, digest: { sha256: candidate.manifest.distSha256 }, }, }; const provenanceAttestation = { ...provenanceUnsigned, signature: { algorithm: "Ed25519" as const, keyId: provenanceKeyId, publicKeyFingerprint: providerPublicKeyFingerprint(provenanceKeys.publicKey), value: sign( null, providerEvidenceSignaturePayload(provenanceUnsigned), provenanceKeys.privateKey, ).toString("base64"), }, }; const vulnerabilityReportBytes = Buffer.from(`${JSON.stringify(vulnerabilityReport)}\n`); const provenanceAttestationBytes = Buffer.from(`${JSON.stringify(provenanceAttestation)}\n`); const vulnerabilityReportPath = path.join( root, "inputs/vulnerability-report.json", ); const provenanceAttestationPath = path.join( root, "inputs/provenance-attestation.json", ); const vulnerabilityPublicKeyPath = path.join(root, "keys/vulnerability.pem"); const provenancePublicKeyPath = path.join(root, "keys/provenance.pem"); await writeArtifact( root, "inputs/vulnerability-report.json", vulnerabilityReportBytes, ); await writeArtifact( root, "inputs/provenance-attestation.json", provenanceAttestationBytes, ); await writeArtifact( root, "keys/vulnerability.pem", vulnerabilityKeys.publicKey.export({ type: "spki", format: "pem" }), ); await writeArtifact( root, "keys/provenance.pem", provenanceKeys.publicKey.export({ type: "spki", format: "pem" }), ); const runnerTempRoot = path.join(root, "runner-temp"); await mkdir(runnerTempRoot, { mode: 0o700 }); const cleanupToken = `promotion-fixture-run-1-${"2a".repeat(16)}`; const expectedProviderEvidence = { vulnerabilityReportSha256: sha256(vulnerabilityReportBytes), provenanceAttestationSha256: sha256(provenanceAttestationBytes), vulnerabilityInvocationNonce, provenanceInvocationNonce, vulnerabilityKeyId, vulnerabilityKeyFingerprint: providerPublicKeyFingerprint(vulnerabilityKeys.publicKey), provenanceKeyId, provenanceKeyFingerprint: providerPublicKeyFingerprint(provenanceKeys.publicKey), }; return { root, runnerTempRoot, cleanupToken, assessmentSha256: sha256(assessmentBytes), expectedContext, expectedProviderEvidence, capturedSources: new Map([ ["release-candidate.tar.gz", candidateArchiveBytes], ["vulnerability-report.json", vulnerabilityReportBytes], ["provenance-attestation.json", provenanceAttestationBytes], ]), dependencies: { nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"), randomBytes: (bytes: number) => Buffer.alloc(bytes, 0x2a), }, input: { repositoryRoot: root, archivePath, expectedArchiveSha256: sha256(candidateArchiveBytes), vulnerabilityReportPath, provenanceAttestationPath, vulnerabilityPublicKeyPath, vulnerabilityKeyId, provenancePublicKeyPath, provenanceKeyId, expectedRun: { id: expectedContext.run.id, attempt: expectedContext.run.attempt, sourceRevision: expectedContext.source.revision, }, vulnerabilityInvocationNonce, provenanceInvocationNonce, runnerTempRoot, }, }; } function finalizedCleanup( fixture: Awaited>, finalized: Awaited>, ) { return { runnerTempRoot: fixture.runnerTempRoot, stagingRoot: finalized.stagingRoot, cleanupToken: finalized.cleanupToken, runnerTempIdentity: finalized.runnerTempIdentity, }; }