返回 DeepSeek-Reasonix
resolve-release-candidate.test.mjs
根目录 / scripts / resolve-release-candidate.test.mjs
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("a rerun can seal Desktop artifacts built by an earlier attempt of the same producer", () => {
73 const reused = { ...record, source: { ...record.source, desktopPrefix: "desktop-11-1-preflight" } };
74 assert.equal(inspectRecord(reused, id, artifact, run).desktopPrefix, "desktop-11-1-preflight");
75 for (const desktopPrefix of ["desktop-12-1-preflight", "desktop-11-3-preflight", "desktop-11-0-preflight", "desktop-11-1-other"]) {
76 assert.throws(() => inspectRecord({ ...record, source: { ...record.source, desktopPrefix } }, id, artifact, run), /Desktop prefix/);
77 }
78 });
79
80 test("Desktop release contract accepts only the sealed producer's current or earlier build attempt", () => {
81 const script = fileURLToPath(new URL("./check-candidate-desktop-prefix.sh", import.meta.url));
82 for (const prefix of ["desktop-11-1-preflight", "desktop-11-2-preflight"]) {
83 assert.equal(spawnSync("bash", [script, prefix, "11", "2"]).status, 0, prefix);
84 }
85 for (const prefix of ["desktop-12-1-preflight", "desktop-11-3-preflight", "desktop-11-0-preflight", "desktop-11-1-other"]) {
86 assert.notEqual(spawnSync("bash", [script, prefix, "11", "2"]).status, 0, prefix);
87 }
88 });
89
90 test("rehearsal records cannot be selected or inspected for publication", () => {
91 const rehearsalArtifact = { ...artifact, name: `release-candidate-rehearsal-record-${id}` };
92 const rehearsalRecord = {
93 ...record, purpose: "rehearsal",
94 source: {
95 ...record.source,
96 payloadArtifactName: `release-candidate-rehearsal-payload-${id}`,
97 evidenceArtifactName: `release-candidate-rehearsal-evidence-${id}`,
98 },
99 };
100 assert.equal(selectRecordArtifact([rehearsalArtifact], id, false), null);
101 assert.equal(selectRecordArtifact([rehearsalArtifact], id, true, "rehearsal"), rehearsalArtifact);
102 assert.throws(() => inspectRecord(rehearsalRecord, id, rehearsalArtifact, run), /purpose mismatch/);
103 assert.throws(() => inspectRecord(rehearsalRecord, id, artifact, run, new Date(), "rehearsal"), /artifact identity/);
104 assert.equal(inspectRecord(rehearsalRecord, id, rehearsalArtifact, run, new Date(), "rehearsal").payloadArtifactId, "33");
105 });
106
107 test("accepts automatic preparation after the reviewed Notes PR merges", () => {
108 assert.doesNotThrow(() => validateCandidateRun({ ...run, event: "push" }, artifact, "esengine/DeepSeek-Reasonix"));
109 });
110
111 for (const change of [
112 { path: ".github/workflows/other.yml" }, { head_branch: "topic" }, { event: "pull_request" },
113 { conclusion: "failure" }, { repository: { full_name: "fork/Reasonix" } },
114 ]) {
115 test(`rejects untrusted producer ${JSON.stringify(change)}`, () => {
116 assert.throws(() => validateCandidateRun({ ...run, ...change }, artifact, "esengine/DeepSeek-Reasonix"));
117 });
118 }
119
120 test("rejects a payload artifact substituted after sealing", () => {
121 assert.throws(() => inspectRecord({ ...record, source: { ...record.source, payloadArtifactId: "" } }, id, artifact, run));
122 });
123
124 test("rejects expired or record-revoked candidates before payload reuse", () => {
125 assert.throws(() => inspectRecord(record, id, artifact, run, new Date("2027-01-02T00:00:00Z")), /expired/);
126 assert.throws(() => inspectRecord({ ...record, validity: { ...record.validity, revoked: true } }, id, artifact, run,
127 new Date("2026-01-02T00:00:00Z")), /revoked/);
128 });
129
130 test("rejects a candidate on the repository revocation list", () => {
131 assert.throws(() => requireNotRevoked(id, `v9.9.9-${"c".repeat(12)}-${"d".repeat(12)}, ${id}`), /is revoked/);
132 assert.doesNotThrow(() => requireNotRevoked(id, ""));
133 });
134
134 lines Plain Text