| 1 | import assert from "node:assert/strict"; |
| 2 | import { spawnSync } from "node:child_process"; |
| 3 | import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; |
| 4 | import { tmpdir } from "node:os"; |
| 5 | import path from "node:path"; |
| 6 | import { fileURLToPath } from "node:url"; |
| 7 | import test from "node:test"; |
| 8 | import { inspectRecord, requireNotRevoked, selectRecordArtifact, validateCandidateRun } from "./resolve-release-candidate.mjs"; |
| 9 | |
| 10 | const id = `v1.2.3-${"a".repeat(12)}-${"b".repeat(12)}`; |
| 11 | const artifact = { id: 22, name: `release-candidate-record-${id}`, expired: false, workflow_run: { id: 11 } }; |
| 12 | const run = { id: 11, run_attempt: 2, repository: { full_name: "esengine/DeepSeek-Reasonix" }, path: ".github/workflows/release-candidate.yml", head_branch: "main-v2", head_sha: "c".repeat(40), event: "workflow_dispatch", status: "completed", conclusion: "success" }; |
| 13 | const record = { candidateId: id, version: "1.2.3", sourceSHA: "a".repeat(40), control: { buildSHA: run.head_sha }, signing: { desktopFingerprint: "v1:example" }, validity: { createdAt: "2026-01-01T00:00:00Z", expiresAt: "2027-01-01T00:00:00Z", revoked: false }, source: { runId: "11", runAttempt: "2", desktopPrefix: "desktop-11-2-preflight", payloadArtifactId: "33", payloadArtifactName: `release-candidate-payload-${id}`, evidenceArtifactId: "34", evidenceArtifactName: `release-candidate-evidence-${id}` } }; |
| 14 | |
| 15 | for (const purpose of ["release", "rehearsal"]) { |
| 16 | test(`${purpose} CLI writes the exact workflow output contract`, t => { |
| 17 | const root = mkdtempSync(path.join(tmpdir(), "reasonix-candidate-outputs-")); |
| 18 | t.after(() => rmSync(root, { recursive: true, force: true })); |
| 19 | const namespace = purpose === "release" ? "release-candidate" : "release-candidate-rehearsal"; |
| 20 | const payloadName = `${namespace}-payload-${id}`; |
| 21 | const evidenceName = `${namespace}-evidence-${id}`; |
| 22 | const sealed = { ...record, purpose, |
| 23 | validity: { ...record.validity, expiresAt: "2099-01-01T00:00:00Z" }, |
| 24 | source: { ...record.source, payloadArtifactName: payloadName, evidenceArtifactName: evidenceName }, |
| 25 | }; |
| 26 | const inputs = [sealed, { ...artifact, name: `${namespace}-record-${id}` }, run].map((value, index) => { |
| 27 | const file = path.join(root, `${index}.json`); |
| 28 | writeFileSync(file, JSON.stringify(value)); |
| 29 | return file; |
| 30 | }); |
| 31 | const output = path.join(root, "outputs"); |
| 32 | const result = spawnSync(process.execPath, [fileURLToPath(new URL("./resolve-release-candidate.mjs", import.meta.url)), |
| 33 | purpose === "release" ? "inspect" : "inspect-rehearsal", id, ...inputs], |
| 34 | { encoding: "utf8", env: { ...process.env, GITHUB_OUTPUT: output } }); |
| 35 | assert.equal(result.status, 0, result.stderr); |
| 36 | const values = Object.fromEntries(readFileSync(output, "utf8").trim().split("\n").map(line => { |
| 37 | const separator = line.indexOf("="); |
| 38 | return [line.slice(0, separator), line.slice(separator + 1)]; |
| 39 | })); |
| 40 | assert.deepEqual(values, { |
| 41 | candidate_id: id, version: "1.2.3", source_sha: record.sourceSHA, |
| 42 | candidate_control_sha: run.head_sha, signing_fingerprint: "v1:example", |
| 43 | producer_run_id: "11", producer_run_attempt: "2", desktop_prefix: "desktop-11-2-preflight", |
| 44 | payload_artifact_id: "33", payload_artifact_name: payloadName, |
| 45 | evidence_artifact_id: "34", evidence_artifact_name: evidenceName, |
| 46 | }); |
| 47 | }); |
| 48 | } |
| 49 | |
| 50 | test("rejects missing or malformed source/control identities before emitting outputs", () => { |
| 51 | for (const sourceSHA of [undefined, "", "main-v2"]) { |
| 52 | assert.throws(() => inspectRecord({ ...record, sourceSHA }, id, artifact, run), /source SHA/); |
| 53 | } |
| 54 | for (const buildSHA of [undefined, "", "main-v2"]) { |
| 55 | assert.throws(() => inspectRecord({ ...record, control: { buildSHA } }, id, artifact, run), /control SHA/); |
| 56 | } |
| 57 | }); |
| 58 | |
| 59 | test("selects the newest active exact-name record", () => { |
| 60 | assert.equal(selectRecordArtifact([{ ...artifact, id: 20 }, artifact, { ...artifact, id: 30, expired: true }], id).id, 22); |
| 61 | assert.equal(selectRecordArtifact([], id, false), null); |
| 62 | assert.throws(() => selectRecordArtifact([], id), /not found/); |
| 63 | }); |
| 64 | |
| 65 | test("accepts a successful protected producer and exact record", () => { |
| 66 | assert.doesNotThrow(() => validateCandidateRun(run, artifact, "esengine/DeepSeek-Reasonix")); |
| 67 | const inspected = inspectRecord(record, id, artifact, run); |
| 68 | assert.equal(inspected.payloadArtifactId, "33"); |
| 69 | assert.equal(inspected.evidenceArtifactId, "34"); |
| 70 | }); |
| 71 | |
| 72 | test("rehearsal records cannot be selected or inspected for publication", () => { |
| 73 | const rehearsalArtifact = { ...artifact, name: `release-candidate-rehearsal-record-${id}` }; |
| 74 | const rehearsalRecord = { |
| 75 | ...record, purpose: "rehearsal", |
| 76 | source: { |
| 77 | ...record.source, |
| 78 | payloadArtifactName: `release-candidate-rehearsal-payload-${id}`, |
| 79 | evidenceArtifactName: `release-candidate-rehearsal-evidence-${id}`, |
| 80 | }, |
| 81 | }; |
| 82 | assert.equal(selectRecordArtifact([rehearsalArtifact], id, false), null); |
| 83 | assert.equal(selectRecordArtifact([rehearsalArtifact], id, true, "rehearsal"), rehearsalArtifact); |
| 84 | assert.throws(() => inspectRecord(rehearsalRecord, id, rehearsalArtifact, run), /purpose mismatch/); |
| 85 | assert.throws(() => inspectRecord(rehearsalRecord, id, artifact, run, new Date(), "rehearsal"), /artifact identity/); |
| 86 | assert.equal(inspectRecord(rehearsalRecord, id, rehearsalArtifact, run, new Date(), "rehearsal").payloadArtifactId, "33"); |
| 87 | }); |
| 88 | |
| 89 | test("accepts automatic preparation after the reviewed Notes PR merges", () => { |
| 90 | assert.doesNotThrow(() => validateCandidateRun({ ...run, event: "push" }, artifact, "esengine/DeepSeek-Reasonix")); |
| 91 | }); |
| 92 | |
| 93 | for (const change of [ |
| 94 | { path: ".github/workflows/other.yml" }, { head_branch: "topic" }, { event: "pull_request" }, |
| 95 | { conclusion: "failure" }, { repository: { full_name: "fork/Reasonix" } }, |
| 96 | ]) { |
| 97 | test(`rejects untrusted producer ${JSON.stringify(change)}`, () => { |
| 98 | assert.throws(() => validateCandidateRun({ ...run, ...change }, artifact, "esengine/DeepSeek-Reasonix")); |
| 99 | }); |
| 100 | } |
| 101 | |
| 102 | test("rejects a payload artifact substituted after sealing", () => { |
| 103 | assert.throws(() => inspectRecord({ ...record, source: { ...record.source, payloadArtifactId: "" } }, id, artifact, run)); |
| 104 | }); |
| 105 | |
| 106 | test("rejects expired or record-revoked candidates before payload reuse", () => { |
| 107 | assert.throws(() => inspectRecord(record, id, artifact, run, new Date("2027-01-02T00:00:00Z")), /expired/); |
| 108 | assert.throws(() => inspectRecord({ ...record, validity: { ...record.validity, revoked: true } }, id, artifact, run, |
| 109 | new Date("2026-01-02T00:00:00Z")), /revoked/); |
| 110 | }); |
| 111 | |
| 112 | test("rejects a candidate on the repository revocation list", () => { |
| 113 | assert.throws(() => requireNotRevoked(id, `v9.9.9-${"c".repeat(12)}-${"d".repeat(12)}, ${id}`), /is revoked/); |
| 114 | assert.doesNotThrow(() => requireNotRevoked(id, "")); |
| 115 | }); |
| 116 |