返回 DeepSeek-Reasonix
ci-workflow.test.mjs
根目录 / scripts / ci-workflow.test.mjs
1 import assert from "node:assert/strict";
2 import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
3 import { spawnSync } from "node:child_process";
4 import os from "node:os";
5 import path from "node:path";
6 import vm from "node:vm";
7 import test from "node:test";
8 import { groups as windowsDesktopGroups, testArgs as windowsDesktopTestArgs } from "./desktop-windows-go-tests.mjs";
9
10 const workflow = name => readFileSync(new URL(`../.github/workflows/${name}.yml`, import.meta.url), "utf8");
11 function job(source, name) {
12 const body = source.match(new RegExp(`\\n ${name}:\\n([\\s\\S]*?)(?=\\n [a-z][a-z0-9-]*:|$)`))?.[1];
13 assert.ok(body, name);
14 return body;
15 }
16 function condition(body, context) {
17 const expression = body.match(/^ if: (.+)$/m)[1].replace(/^\$\{\{\s*|\s*\}\}$/g, "")
18 .replace(/needs\.([a-z][a-z0-9-]*)/g, 'needs["$1"]');
19 return vm.runInNewContext(expression, { always: () => true, cancelled: () => false, ...context });
20 }
21 function shellStep(body, name) {
22 return body.split(` - name: ${name}\n`)[1].match(/ run: \|\n((?: .*\n|\n)+)/)[1]
23 .replace(/^ /gm, "");
24 }
25 const ci = workflow("ci");
26 const release = workflow("release-desktop");
27 const promote = workflow("release-promote");
28 const appMemory = workflow("app-memory");
29
30 test("App memory artifacts support rerunning only failed shards", () => {
31 const prepare = job(appMemory, "prepare");
32 const shard = job(appMemory, "shard");
33 const aggregate = job(appMemory, "app-memory");
34 const upload = body => body.match(/uses: actions\/upload-artifact@[^\n]+\n (?:if: [^\n]+\n )?with:\n name: ([^\n]+)([\s\S]*?)(?=\n - |$)/);
35 const download = body => [...body.matchAll(/uses: actions\/download-artifact@[^\n]+\n (?:if: [^\n]+\n )?with:\n (name|pattern): ([^\n]+)/g)];
36 const render = (name, attempt, shardId = 1) => name
37 .replaceAll("${{ github.run_id }}", "123")
38 .replaceAll("${{ github.run_attempt }}", String(attempt))
39 .replaceAll("${{ matrix.shard }}", String(shardId));
40 const build = upload(prepare);
41 const shardReport = upload(shard);
42 const shardBuild = download(shard)[0][2];
43 const [reports, aggregateBuild] = download(aggregate).map(match => match[2]);
44 assert.ok(build && shardReport && shardBuild && reports && aggregateBuild);
45 assert.match(build[2], /\n overwrite: true\n/);
46 assert.match(shardReport[2], /\n overwrite: true\n/);
47 assert.equal(render(build[1], 1), render(shardBuild, 2));
48 assert.equal(render(build[1], 1), render(aggregateBuild, 2));
49 for (const shardId of [1, 2, 3]) {
50 assert.equal(render(shardReport[1], shardId === 1 ? 2 : 1, shardId),
51 render(reports, 2).replaceAll("*", String(shardId)));
52 }
53 });
54
55 test("frontend artifact workflows share one exact Node runtime", () => {
56 const version = readFileSync(new URL("../.node-version", import.meta.url), "utf8").trim();
57 assert.match(version, /^\d+\.\d+\.\d+$/);
58 for (const [name, source] of [["ci", ci], ["app-memory", appMemory], ["release-desktop", release]]) {
59 assert.doesNotMatch(source, /node-version: ["']?24(?:["']|\s)/, name);
60 assert.match(source, /node-version-file: \.node-version/, name);
61 }
62 const names = [...ci.matchAll(/^ ([a-z][a-z0-9-]*):$/gm)].map(match => match[1]);
63 const participants = names.map(name => [name, job(ci, name)])
64 .filter(([, body]) => /artifact-identity\.mjs (create|verify)/.test(body));
65 assert.ok(participants.length > 1, "cover both producer and consumers");
66 for (const [name, body] of participants) {
67 assert.match(body, /uses: actions\/setup-node@[^\n]+\n\s+with:\n\s+node-version-file: \.node-version/, name);
68 assert.doesNotMatch(body, /node-version:/, `${name} must not override the shared runtime`);
69 }
70 for (const name of ["prepare", "shard", "app-memory"]) {
71 const body = job(appMemory, name);
72 assert.match(body, /node-version-file: \.node-version/, name);
73 assert.doesNotMatch(body, /node-version:/, name);
74 }
75 });
76
77 test("Windows PR verifies credential aliases before full push CI", () => {
78 assert.match(ci, /name: test \(Windows credential ACL identity\)[\s\S]*?runner\.os == 'Windows' && github\.event_name == 'pull_request'[\s\S]*?go test -timeout=2m -run '\^TestCredentialAccessRepairsLegacyCredentialDeny\|\^TestRepairLegacyCredentialDenyMatchesFileAcrossPathAliases\$' \.\/internal\/config \.\/internal\/winaclresidue/);
79 });
80
81 test("release tag mutation follows approval with an explicit identity and read-only default token", () => {
82 assert.match(job(promote, "preflight"), /permissions:\n actions: read\n attestations: read\n contents: read/);
83 assert.match(job(promote, "authorize"), /environment: release[\s\S]*permissions:\n contents: read/);
84 const activation = job(promote, "activate");
85 assert.match(activation, /needs: \[preflight, authorize\]/);
86 assert.match(activation, /permissions:\n contents: read/);
87 assert.match(activation, /persist-credentials: false/);
88 assert.match(activation, /GH_TOKEN: \$\{\{ secrets.RELEASE_TAG_TOKEN \}\}/);
89 assert.match(activation, /RELEASE_TAG_ACTOR_ID: \$\{\{ needs.preflight.outputs.tag_actor_id \}\}/);
90 assert.doesNotMatch(job(promote, "preflight"), /release-candidate-tags.sh activate|git push/);
91 });
92
93 test("cancelled CI releases workers, aggregates, and metrics without hiding live failures", () => {
94 for (const name of ["test", "windows-control", "windows-isolated", "race", "sdk", "desktop-prepare",
95 "desktop-frontend", "desktop-browser-group", "desktop-go", "desktop-go-race", "desktop-macos",
96 "desktop-windows", "desktop-windows-go-group", "desktop-windows-package", "lint-code", "coverage", "prune-go-cache"]) {
97 assert.equal(condition(job(ci, name), { cancelled: () => true }), false, name);
98 }
99 for (const name of ["root", "lint", "desktop", "desktop-browser", "desktop-windows-go", "ci-metrics"]) {
100 assert.equal(condition(job(ci, name), { cancelled: () => true }), false, name);
101 // A failed dependency must still reach the fail-closed shell assertions.
102 assert.equal(condition(job(ci, name), { cancelled: () => false, success: () => false,
103 failure: () => true, needs: { child: { result: "failure" } } }), true, name);
104 }
105 });
106
107 test("packaging changes run native installer acceptance before merge", () => {
108 assert.match(job(ci, "desktop-prepare"), /REASONIX_COMMIT: \$\{\{ github.sha \}\}/,
109 "prepared frontend must budget the full source identity used by native packaging");
110 const body = job(ci, "desktop-windows-package");
111 assert.doesNotMatch(ci, /performance-benchmark\.mjs/);
112 const diagnostic = workflow("diagnostic-overhead");
113 assert.match(diagnostic, /schedule:/);
114 assert.match(diagnostic, /workflow_dispatch:/);
115 assert.match(diagnostic, /desktop\/electron\/\*\*/);
116 assert.match(diagnostic, /desktop\/frontend\/\*\*/);
117 assert.match(diagnostic, /run: pnpm install --frozen-lockfile/);
118 assert.match(diagnostic, /run: node electron\/scripts\/performance-benchmark\.mjs/);
119 assert.match(diagnostic, /if-no-files-found: error/);
120 assert.doesNotMatch(diagnostic, /continue-on-error/);
121 for (const event of ["pull_request", "push"]) {
122 for (const packaging of ["true", "false", ""]) {
123 const context = { github: { event_name: event }, needs: {
124 "desktop-prepare": { result: "success" }, changes: { outputs: { packaging, notes_only: "false" } },
125 } };
126 assert.equal(condition(body, context), event === "push" || packaging !== "false");
127 const aggregate = job(ci, "desktop").match(/PACKAGE_REQUIRED: \$\{\{ (.+) \}\}/)[1];
128 assert.equal(vm.runInNewContext(aggregate, context), condition(body, context));
129 }
130 }
131 });
132
133 test("notes pushes preserve required ancestor CI while code pushes cancel obsolete runs", () => {
134 const dir = mkdtempSync(path.join(os.tmpdir(), "reasonix-ci-cancel-"));
135 const git = (...args) => {
136 const result = spawnSync("git", args, { cwd: dir, encoding: "utf8" });
137 assert.equal(result.status, 0, result.stderr);
138 return result.stdout.trim();
139 };
140 try {
141 git("init", "-q");
142 git("config", "user.name", "test");
143 git("config", "user.email", "test@example.invalid");
144 const commit = (file, content) => {
145 writeFileSync(path.join(dir, file), content);
146 git("add", "."); git("commit", "-qm", "fixture");
147 return git("rev-parse", "HEAD");
148 };
149 const old = commit("code", "old");
150 const code = commit("code", "current");
151 mkdirSync(path.join(dir, "release-notes"));
152 const notes = commit("release-notes/record", "first");
153 const head = commit("release-notes/record", "reviewed");
154 const run = candidate => {
155 const script = `set -euo pipefail
156 sleep() { :; }
157 gh() {
158 if [ "$2" = "-X" ]; then printf '%s\\n' "$4" >> "$CANCEL_LOG";
159 elif [[ "$2" == *workflows/ci.yml/runs* ]]; then printf '%s\\n' "$ACTIVE_RUNS";
160 else echo completed; fi
161 }
162 ${shellStep(job(workflow("supersede-ci"), "cancel-superseded"), "Cancel CI runs this push supersedes")}`;
163 const log = path.join(dir, "cancel-log");
164 writeFileSync(log, "");
165 const result = spawnSync("bash", ["-c", script], { cwd: dir, encoding: "utf8", env: {
166 ...process.env, GITHUB_REPOSITORY: "example/repo", GITHUB_SHA: candidate, CANCEL_LOG: log,
167 ACTIVE_RUNS: `11 ${old}\n12 ${code}\n13 ${notes}\n14 ${head}`,
168 } });
169 assert.equal(result.status, 0, result.stderr);
170 return readFileSync(log, "utf8").trim().split("\n");
171 };
172 assert.deepEqual(run(head), ["repos/example/repo/actions/runs/11/cancel"]);
173 const next = commit("code", "next");
174 assert.deepEqual(run(next), [11, 12, 13, 14].map(id => `repos/example/repo/actions/runs/${id}/cancel`));
175 const group = ci.match(/ group: (ci-.+)/)[1];
176 assert.match(group, /github.event_name == 'push' && github.sha \|\| github.ref/);
177 } finally {
178 rmSync(dir, { recursive: true, force: true });
179 }
180 });
181
182 test("Certum signing survives skipped ancestor gates but requires successful inputs", () => {
183 const body = job(release, "windows-sign");
184 // A status function is required to override GitHub's implicit success(),
185 // which otherwise propagates a skipped standalone/orchestrator ancestor.
186 assert.match(body, /if:.*always\(\)/);
187 const context = {
188 needs: { resolve: { result: "success" }, "windows-build": { result: "success" }, "signing-contract": { result: "success" } },
189 github: { repository: "esengine/DeepSeek-Reasonix" },
190 inputs: { desktop_manual_only: false },
191 };
192 assert.equal(condition(body, context), true);
193 assert.equal(condition(body, { ...context, cancelled: () => true }), false);
194 for (const name of Object.keys(context.needs)) {
195 for (const result of ["failure", "skipped", "cancelled"]) {
196 assert.equal(condition(body, { ...context, needs: { ...context.needs, [name]: { result } } }), false);
197 }
198 }
199 assert.equal(condition(body, { ...context, inputs: { desktop_manual_only: true } }), false);
200 assert.equal(condition(body, { ...context, github: { repository: "example/fork" } }), false);
201 });
202
203 test("Windows full runs use the partitioned suite without a duplicate module sweep", () => {
204 const body = job(ci, "test");
205 const enabled = (name, os, event, run = "true") => {
206 const step = body.split(` - name: ${name}\n`)[1].split(/\n - /)[0];
207 const expression = step.match(/^ if: (.+)$/m)[1];
208 return vm.runInNewContext(expression, {
209 env: { RUN_STEPS: run }, runner: { os }, github: { event_name: event },
210 });
211 };
212 for (const event of ["pull_request", "push", "workflow_dispatch"]) {
213 for (const os of ["Linux", "macOS", "Windows"]) {
214 assert.equal(enabled("test", os, event), os === "Linux" || (os === "macOS" && event !== "pull_request"));
215 assert.equal(enabled("test (full)", os, event), os === "Windows" && event !== "pull_request");
216 assert.equal(enabled("test (Windows smoke)", os, event), os === "Windows" && event === "pull_request");
217 assert.equal(enabled("test", os, event, "false"), false);
218 assert.equal(enabled("test (full)", os, event, "false"), false);
219 }
220 }
221 assert.match(body, /run: node scripts\/windows-go-tests\.mjs full/);
222 assert.match(job(ci, "windows-isolated"), /group: \[acp, agent, boot, bot, serve, session, worktree\]/);
223 assert.match(job(ci, "windows-control"), /run: node scripts\/windows-go-tests\.mjs control/);
224 });
225
226 test("App memory workflow tiers pull requests and keeps full scheduled coverage", t => {
227 assert.match(appMemory, /schedule:\n - cron: "17 3 \* \* \*"/);
228 assert.match(appMemory, /\[ "\$EVENT_NAME" = workflow_dispatch \] \|\| \[ "\$EVENT_NAME" = schedule \]/);
229 assert.match(appMemory, /matrix:\n shard: \$\{\{ fromJSON\(needs\.changes\.outputs\.memory_shards\) \}\}/);
230 assert.match(appMemory, /REASONIX_APP_MEMORY_PROFILE: \$\{\{ needs\.changes\.outputs\.memory_profile \}\}/);
231 const script = shellStep(job(appMemory, "changes"), "Select memory profile");
232 const root = mkdtempSync(path.join(os.tmpdir(), "reasonix-memory-workflow-"));
233 t.after(() => rmSync(root, { recursive: true, force: true }));
234 let index = 0;
235 const run = env => {
236 const output = path.join(root, `output-${index++}`);
237 const result = spawnSync("bash", ["-e", "-c", script], {
238 env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: path.join(root, "summary"), ...env }, encoding: "utf8",
239 });
240 return { ...result, workflowOutput: result.status === 0 ? readFileSync(output, "utf8") : "" };
241 };
242 for (const [env, expected] of [
243 [{ EVENT_NAME: "pull_request", MEMORY: "true", MEMORY_FULL: "false" }, "profile=short\nshards=[1]\n"],
244 [{ EVENT_NAME: "pull_request", MEMORY: "true", MEMORY_FULL: "true" }, "profile=full\nshards=[1,2,3]\n"],
245 [{ EVENT_NAME: "push", MEMORY: "true", MEMORY_FULL: "false" }, "profile=full\nshards=[1,2,3]\n"],
246 [{ EVENT_NAME: "pull_request", MEMORY: "false", MEMORY_FULL: "false" }, "profile=off\nshards=[1]\n"],
247 ]) {
248 const result = run(env);
249 assert.equal(result.status, 0, result.stderr);
250 assert.equal(result.workflowOutput, expected);
251 }
252 });
253
254 test("macOS signing diagnostics require protected main and cannot publish", () => {
255 const source = workflow("macos-signing-check");
256 const verify = job(source, "verify");
257 const github = { repository: "esengine/DeepSeek-Reasonix", ref: "refs/heads/main-v2", ref_protected: true };
258 assert.equal(condition(verify, { github }), true);
259 for (const changed of [{ repository: "fork/Reasonix" }, { ref: "refs/tags/v1.0.0" }, { ref_protected: false }]) {
260 assert.equal(condition(verify, { github: { ...github, ...changed } }), false);
261 }
262 assert.match(verify, /environment: release/);
263 assert.match(verify, /ref: \$\{\{ github.sha \}\}/);
264 assert.match(source, /permissions:\n contents: read\n/);
265 assert.doesNotMatch(source, /: write|secrets\.(R2_|SIGNPATH_|MINISIGN_|NPM_)/);
266 assert.match(verify, /HAS_APPLE_CERT: "true"/);
267 assert.match(verify, /scripts\/desktop-build.sh darwin\/universal v0.0.0-signing-check stable/);
268 assert.match(verify, /path: \$\{\{ runner.temp \}\}\/apple-notarization\/\*\.json/);
269 assert.match(verify, /if: always\(\)/);
270 });
271
272 test("required desktop aggregate rejects every failed, cancelled or unexpectedly skipped child", () => {
273 const script = shellStep(job(ci, "desktop"), "Verify desktop validation jobs");
274 const success = { CHANGES_RESULT: "success", PREPARE_REQUIRED: "true", NATIVE_REQUIRED: "true", FRONTEND_REQUIRED: "true", BROWSER_REQUIRED: "true",
275 PACKAGE_REQUIRED: "true", RACE_REQUIRED: "true", PREPARE_RESULT: "success", GO_RESULT: "success", GO_RACE_RESULT: "success", FRONTEND_RESULT: "success", BROWSER_RESULT: "success",
276 MACOS_RESULT: "success", WINDOWS_RESULT: "success", WINDOWS_GO_RESULT: "success", PACKAGE_RESULT: "success" };
277 const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status;
278 assert.equal(run(success), 0);
279 for (const key of ["PREPARE_RESULT", "GO_RESULT", "GO_RACE_RESULT", "FRONTEND_RESULT", "BROWSER_RESULT", "CHANGES_RESULT",
280 "MACOS_RESULT", "WINDOWS_RESULT", "WINDOWS_GO_RESULT", "PACKAGE_RESULT"]) {
281 for (const value of ["failure", "cancelled", "skipped", ""]) assert.notEqual(run({ ...success, [key]: value }), 0, `${key}=${value}`);
282 }
283 // A pull request that cannot affect the desktop module: every child skips
284 // except the browser and Windows Go aggregates, which validate their groups.
285 assert.equal(run({ ...success, PREPARE_REQUIRED: "false", NATIVE_REQUIRED: "false", FRONTEND_REQUIRED: "false", BROWSER_REQUIRED: "false",
286 PACKAGE_REQUIRED: "false", RACE_REQUIRED: "false", PREPARE_RESULT: "skipped", GO_RESULT: "skipped", GO_RACE_RESULT: "skipped", FRONTEND_RESULT: "skipped",
287 BROWSER_RESULT: "success", MACOS_RESULT: "skipped", WINDOWS_RESULT: "skipped", WINDOWS_GO_RESULT: "success", PACKAGE_RESULT: "skipped" }), 0);
288 // A pull request that touches native code runs every native child except the race sweep.
289 assert.equal(run({ ...success, RACE_REQUIRED: "false", GO_RACE_RESULT: "skipped" }), 0);
290 assert.notEqual(run({ ...success, RACE_REQUIRED: "false", GO_RACE_RESULT: "success" }), 0);
291 // A pull request unrelated to packaging must skip it.
292 assert.equal(run({ ...success, PACKAGE_REQUIRED: "false", PACKAGE_RESULT: "skipped" }), 0);
293 assert.notEqual(run({ ...success, PACKAGE_REQUIRED: "false", PACKAGE_RESULT: "success" }), 0);
294 assert.equal(run({ ...success, FRONTEND_REQUIRED: "false", BROWSER_REQUIRED: "false", FRONTEND_RESULT: "skipped", BROWSER_RESULT: "success" }), 0);
295 const browserScript = shellStep(job(ci, "desktop-browser"), "Verify desktop browser groups");
296 const browser = spawnSync("bash", ["-e", "-c", browserScript], { env: { ...process.env,
297 CHANGES_RESULT: "success", SHOULD_RUN: "false", PREPARE_RESULT: "skipped", GROUP_RESULT: "skipped" } });
298 assert.equal(browser.status, 0, "an unneeded browser aggregate succeeds after validating skipped groups");
299 assert.notEqual(run({ ...success, BROWSER_REQUIRED: "false", BROWSER_RESULT: "skipped" }), 0);
300 assert.notEqual(run({ ...success, NATIVE_REQUIRED: "false", WINDOWS_GO_RESULT: "skipped" }), 0);
301 });
302
303 test("required lint aggregates code lint and the deduplicated frontend suite", () => {
304 const body = job(ci, "lint");
305 const script = shellStep(body, "Verify lint and frontend validation jobs");
306 const success = { CHANGES_RESULT: "success", LINT_CODE_RESULT: "success", LINT_CODE_REQUIRED: "true",
307 RELEASE_CONTROL_RESULT: "success", RELEASE_CONTROL_REQUIRED: "true",
308 PREPARE_RESULT: "success", FRONTEND_RESULT: "success", FRONTEND_REQUIRED: "true" };
309 const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status;
310 assert.equal(run(success), 0);
311 for (const key of ["CHANGES_RESULT", "LINT_CODE_RESULT", "RELEASE_CONTROL_RESULT", "PREPARE_RESULT", "FRONTEND_RESULT"])
312 for (const value of ["failure", "cancelled", "skipped", ""]) assert.notEqual(run({ ...success, [key]: value }), 0, `${key}=${value}`);
313 assert.equal(run({ ...success, LINT_CODE_REQUIRED: "false", LINT_CODE_RESULT: "skipped",
314 RELEASE_CONTROL_REQUIRED: "false", RELEASE_CONTROL_RESULT: "skipped",
315 FRONTEND_REQUIRED: "false", PREPARE_RESULT: "skipped", FRONTEND_RESULT: "skipped" }), 0);
316 assert.equal(run({ ...success, FRONTEND_REQUIRED: "false", PREPARE_RESULT: "success", FRONTEND_RESULT: "skipped" }), 0);
317 assert.doesNotMatch(job(ci, "lint-code"), /test:motion/);
318 assert.match(body, /needs: \[changes, lint-code, release-control, desktop-prepare, desktop-frontend\]/);
319 });
320
321 test("required root aggregate covers the jobs the per-OS test legs do not", () => {
322 const body = job(ci, "root");
323 const script = shellStep(body, "Verify root validation jobs");
324 const success = { CHANGES_RESULT: "success", CODE_REQUIRED: "true", COVERAGE_REQUIRED: "true",
325 CONTROL_RESULT: "success", ISOLATED_RESULT: "success", SDK_RESULT: "success", COVERAGE_RESULT: "success" };
326 const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status;
327 assert.equal(run(success), 0);
328 for (const key of ["CHANGES_RESULT", "CONTROL_RESULT", "ISOLATED_RESULT", "SDK_RESULT", "COVERAGE_RESULT"])
329 for (const value of ["failure", "cancelled", "skipped", ""]) assert.notEqual(run({ ...success, [key]: value }), 0, `${key}=${value}`);
330 // A pull request unrelated to code: the internally-gated jobs still
331 // report success, the skippable ones must actually be skipped.
332 assert.equal(run({ ...success, CODE_REQUIRED: "false", COVERAGE_REQUIRED: "false",
333 ISOLATED_RESULT: "skipped", COVERAGE_RESULT: "skipped" }), 0);
334 // Coverage is push-only; a pull request that ran it is a routing defect.
335 assert.notEqual(run({ ...success, COVERAGE_REQUIRED: "false", COVERAGE_RESULT: "success" }), 0);
336 assert.match(body, /needs: \[changes, windows-control, windows-isolated, sdk, coverage\]/);
337 // govulncheck sets continue-on-error, so needs.*.result is success even when
338 // it fails; aggregating it would be a tautology that reads like coverage.
339 assert.match(job(ci, "govulncheck"), /continue-on-error: true/);
340 assert.doesNotMatch(body, /GOVULN/);
341 });
342
343 // The gap that let a failing desktop-windows-go merge was a job nobody had
344 // wired into a required aggregate. Keep that unrepeatable: every job must be
345 // reachable from a required check, or be named here with a reason.
346 test("every ci job is reachable from a required aggregate", () => {
347 const required = ["test", "race", "lint", "desktop", "root"];
348 const advisory = {
349 changes: "asserted by line one of every aggregate",
350 "ci-metrics": "reports queue and stage timing; failure must not block merges",
351 "prune-go-cache": "push-only cache housekeeping; cannot report on a pull request",
352 govulncheck: "continue-on-error by design — stdlib advisories precede Go patch releases",
353 };
354 const jobs = ci.slice(ci.indexOf("\njobs:\n")); // `on:` also nests two-space keys
355 const names = [...jobs.matchAll(/^ {2}([a-z][a-z0-9-]*):$/gm)].map(match => match[1]);
356 assert.ok(names.length > 20, `expected the full job list, got ${names.length}`);
357 // A bracketed list may wrap across lines, so consume up to its closing ].
358 const edges = new Map(names.map(name => [name, (job(ci, name).match(/^ {4}needs:\s*(\[[^\]]*\]|\S.*)$/m)?.[1] ?? "")
359 .replace(/[[\]]/g, "").split(",").map(entry => entry.trim()).filter(Boolean)]));
360 const reachable = new Set(required);
361 for (const name of required) for (const dependency of edges.get(name) ?? []) reachable.add(dependency);
362 for (let size = 0; size !== reachable.size;) {
363 size = reachable.size;
364 for (const name of [...reachable]) for (const dependency of edges.get(name) ?? []) reachable.add(dependency);
365 }
366 for (const name of names) {
367 if (reachable.has(name)) continue;
368 assert.ok(advisory[name], `${name} is gated by no required check and is not declared advisory`);
369 }
370 for (const name of Object.keys(advisory))
371 assert.ok(names.includes(name), `${name} is declared advisory but no longer exists`);
372 });
373
374 test("reuse skips only build work and still gates every publisher on validation", () => {
375 const context = {
376 inputs: { preflight_artifact_prefix: "desktop-123-1-preflight", orchestrated: true, signing_preflight_verified: true, signing_preflight: false, production_signing_smoke: false },
377 needs: { resolve: { result: "success" }, "signing-contract": { result: "success" }, "mac-universal-intel": { result: "skipped" }, "windows-build": { result: "skipped" }, "windows-sign": { result: "skipped" }, "windows-runtime-acceptance": { result: "skipped" }, build: { result: "skipped" } },
378 };
379 assert.equal(condition(job(release, "build"), context), false);
380 assert.equal(condition(job(release, "publish"), context), true);
381 for (const key of ["resolve", "signing-contract", "mac-universal-intel", "windows-build", "windows-sign", "build"]) {
382 for (const result of ["failure", "cancelled"]) {
383 const changed = structuredClone(context);
384 changed.needs[key].result = result;
385 assert.equal(condition(job(release, "publish"), changed), false, `${key}=${result}`);
386 }
387 }
388 for (const key of ["orchestrated", "signing_preflight_verified"]) {
389 assert.equal(condition(job(release, "publish"), { ...context, inputs: { ...context.inputs, [key]: false } }), false);
390 }
391 for (const key of ["signing_preflight", "production_signing_smoke"]) {
392 assert.equal(condition(job(release, "publish"), { ...context, inputs: { ...context.inputs, [key]: true } }), false);
393 }
394 const fresh = structuredClone(context);
395 fresh.inputs.preflight_artifact_prefix = "";
396 assert.equal(condition(job(release, "build"), fresh), true);
397 assert.equal(condition(job(release, "publish"), fresh), false);
398 fresh.needs.build.result = "success";
399 fresh.needs["windows-build"].result = "success";
400 fresh.needs["mac-universal-intel"].result = "success";
401 assert.equal(condition(job(release, "publish"), fresh), false, "unsigned Windows bundles cannot publish");
402 fresh.needs["windows-sign"].result = "success";
403 assert.equal(condition(job(release, "publish"), fresh), false, "signed Windows installers must pass native runtime acceptance");
404 fresh.needs["windows-runtime-acceptance"].result = "success";
405 assert.equal(condition(job(release, "publish"), fresh), true);
406 });
407
408 test("Certum signing preserves native builds and gates publication and attestation", () => {
409 const packageJob = job(ci, "desktop-windows-package");
410 assert.match(packageJob, /test-windows-installer-startup\.ps1/);
411 assert.match(packageJob, /ExpectedVersion v0\.0\.0-ci/);
412 const windowsBuild = job(release, "windows-build");
413 const signer = job(release, "windows-sign");
414 assert.match(windowsBuild, /runner: windows-latest, platform: windows\/amd64/);
415 assert.match(windowsBuild, /runner: windows-11-arm, platform: windows\/arm64/);
416 assert.match(windowsBuild, /Smoke-test packaged Electron startup/);
417 assert.match(windowsBuild, /Upload Windows signing inputs/);
418 assert.match(job(ci, 'test'), /test-windows-installer-startup\.test\.ps1/);
419 assert.match(signer, /needs: \[resolve, windows-build, signing-contract\]/);
420 assert.match(signer, /runs-on: windows-2022/);
421 assert.match(signer, /ref: \$\{\{ github.workflow_sha \}\}/);
422 assert.equal(signer.match(/setup-certum/g)?.length, 1, "both architectures share one Certum session");
423 // Certum work stays in the one session; only the credential-free packaging
424 // of the two architectures runs at the same time, between the two.
425 const phases = [
426 "Sign both payloads in the shared Certum session",
427 "Package both architectures in parallel",
428 "Seal amd64 in the shared Certum session",
429 "Seal arm64 in the shared Certum session",
430 ].map((name) => signer.indexOf(`name: ${name}`));
431 assert.ok(phases.every((index) => index >= 0), "windows-sign names its sign, package and seal phases");
432 assert.deepEqual([...phases].sort((a, b) => a - b), phases, "sign, package, then seal each architecture");
433 for (const phase of ["sign", "package", "seal"]) {
434 assert.match(signer, new RegExp(`FINALIZE_PHASE=${phase} bash `));
435 }
436 assert.ok(signer.indexOf("Seal amd64 in the shared Certum session")
437 < signer.indexOf("name: ${{ needs.resolve.outputs.artifact_prefix }}-windows-amd64"));
438 assert.ok(signer.indexOf("Seal arm64 in the shared Certum session")
439 < signer.indexOf("name: ${{ needs.resolve.outputs.artifact_prefix }}-windows-arm64"));
440 assert.ok(!release.includes("secrets.SIGNPATH_API_TOKEN"));
441 const runtimeAcceptance = job(release, "windows-runtime-acceptance");
442 assert.match(runtimeAcceptance, /runner: windows-latest, arch: amd64/);
443 assert.match(runtimeAcceptance, /runner: windows-11-arm, arch: arm64/);
444 assert.match(runtimeAcceptance, /test-windows-installer-startup\.ps1/);
445 assert.match(runtimeAcceptance, /ExpectedVersion "\$\{\{ needs\.resolve\.outputs\.version \}\}"/);
446 const attestation = job(release, "attest-signing-contract");
447 assert.ok(!attestation.includes("gh api --method"), "GITHUB_TOKEN cannot mutate repository variables");
448 assert.match(attestation, /uses: actions\/upload-artifact@[0-9a-f]{40} # v7\b/);
449 assert.match(attestation, /verified-contract\.json/);
450 assert.match(attestation, /gh variable set/);
451 const context = { github: { repository: "esengine/DeepSeek-Reasonix" }, inputs: { signing_preflight: true, orchestrated: false },
452 needs: { "signing-contract": { result: "success" }, build: { result: "success" }, "windows-build": { result: "success" }, "windows-sign": { result: "success" }, "windows-runtime-acceptance": { result: "success" } } };
453 assert.equal(condition(attestation, context), true);
454 for (const key of ["windows-build", "windows-sign", "windows-runtime-acceptance"]) {
455 for (const result of ["failure", "cancelled", "skipped"]) {
456 assert.equal(condition(attestation, { ...context, needs: { ...context.needs, [key]: { result } } }), false);
457 }
458 }
459 });
460
461 test("reuse never moves artifact verification past public mutation or trusts candidate scripts", () => {
462 const publisher = job(release, "publish");
463 assert.ok(publisher.indexOf("Verify complete signed artifact handoff") < publisher.indexOf("name: Publish GitHub release"));
464 assert.ok(publisher.includes("node release-control/scripts/desktop-release-artifacts.mjs collect"));
465 assert.ok(publisher.includes("ref: ${{ github.workflow_sha }}"));
466 assert.ok(!publisher.includes("merge-multiple: true"));
467 const stable = workflow("release-stable");
468 assert.ok(job(stable, "desktop").includes("preflight_artifact_prefix: ${{ needs.signpath-preflight.outputs.artifact_prefix }}"));
469 for (const name of ["desktop", "cli", "npm"]) assert.ok(job(stable, name).includes("needs: [authorize, signpath-preflight]"));
470 });
471
472 test("all desktop consumers verify the prepared build and reject a failed preparation", () => {
473 const context = { github: { event_name: "pull_request" },
474 needs: { changes: { outputs: { desktop: "true" } }, "desktop-prepare": { result: "success" } } };
475 const aggregate = job(ci, "desktop");
476 const verifications = ci.match(/artifact-identity\.mjs verify/g)?.length ?? 0;
477 assert.equal(ci.match(/--attempt "\$\{\{ needs\.desktop-prepare\.outputs\.producer_attempt \}\}"/g)?.length, verifications);
478 assert.equal(ci.match(/test -n "\$\{\{ needs\.desktop-prepare\.outputs\.producer_attempt \}\}"/g)?.length, verifications);
479 for (const [name, variant] of [
480 ["desktop-go", "stable"], ["desktop-frontend", "stable"], ["desktop-browser-group", "stable"],
481 ["desktop-macos", "stable"], ["desktop-windows", "canary"], ["desktop-windows-go-group", "stable"],
482 ]) {
483 const body = job(ci, name);
484 if (["desktop-go", "desktop-frontend"].includes(name)) assert.ok(aggregate.includes(name));
485 assert.ok(body.includes("needs: [changes, desktop-prepare]"));
486 assert.ok(body.includes(`name: \${{ needs.desktop-prepare.outputs.${variant}_artifact_name }}`));
487 assert.ok(body.includes(`--shell electron --channel ${variant}`));
488 assert.ok(body.includes('test -n "${{ needs.desktop-prepare.outputs.producer_attempt }}"'));
489 assert.ok(body.includes('--attempt "${{ needs.desktop-prepare.outputs.producer_attempt }}"'));
490 assert.doesNotMatch(body, /artifact-identity\.mjs verify[^]*?--attempt "\$GITHUB_RUN_ATTEMPT"/);
491 assert.ok(!body.includes("pnpm --dir frontend build"));
492 assert.equal(condition(body, context), true);
493 assert.equal(condition(body, { ...context, needs: { ...context.needs, "desktop-prepare": { result: "failure" } } }), false);
494 }
495 for (const name of ["desktop-windows", "desktop-windows-package"]) {
496 const body = job(ci, name);
497 assert.match(body, /REASONIX_PACKAGE_REUSE_FRONTEND: "1"/);
498 assert.match(body, /REASONIX_FRONTEND_PNPM_VERSION="\$\(pnpm --version\)"\n\s+export REASONIX_FRONTEND_PNPM_VERSION/);
499 assert.match(body, /canary_artifact_name/);
500 }
501 assert.match(job(ci, "desktop-macos"), /REASONIX_FRONTEND_PNPM_VERSION="\$\(pnpm --version\)"\n\s+export REASONIX_FRONTEND_PNPM_VERSION/);
502 for (const name of ["desktop-macos", "desktop-windows", "desktop-windows-package"]) {
503 assert.ok(job(ci, name).includes("REASONIX_FRONTEND_PRODUCER_ATTEMPT: ${{ needs.desktop-prepare.outputs.producer_attempt }}"));
504 }
505 const prepare = job(ci, "desktop-prepare");
506 assert.match(prepare, /producer_attempt: \$\{\{ steps\.artifact-identity\.outputs\.attempt \}\}/);
507 assert.match(prepare, /id: artifact-identity\n\s+run: echo "attempt=\$GITHUB_RUN_ATTEMPT" >> "\$GITHUB_OUTPUT"/);
508 assert.match(prepare, /stable_artifact_name: desktop-frontend-stable-\$\{\{ github\.run_id \}\}-\$\{\{ steps\.artifact-identity\.outputs\.attempt \}\}/);
509 assert.equal(prepare.match(/desktop\/frontend\/sourcemaps\/\$\{\{ github\.sha \}\}/g)?.length, 2);
510 });
511
512
513
514 test("browser matrix preserves five entry points and fails closed through desktop-browser", () => {
515 const groups = job(ci, "desktop-browser-group");
516 assert.match(groups, /max-parallel: 2/);
517 assert.match(groups, /fail-fast: false/);
518 assert.match(groups, /group: \[app-settings-motion, transcript\]/);
519 assert.match(groups, /REASONIX_TRANSCRIPT_MODE=native-scrollbar REASONIX_LAYOUT_ARTIFACTS="\$evidence\/native-scrollbar"/);
520 assert.match(groups, /REASONIX_TRANSCRIPT_MODE=headless-reader REASONIX_LAYOUT_ARTIFACTS="\$evidence\/headless-reader"/);
521 assert.doesNotMatch(groups, /group: \[app-settings, motion, transcript\]/);
522 for (const command of ["test:app-browser", "test:settings-browser", "test:motion-browser", "test:transcript-browser", "test:transcript-reader-browser"])
523 assert.equal(ci.match(new RegExp(`pnpm --dir frontend ${command}(?:\\s|$)`, "g"))?.length, 1, command);
524 const summary = job(ci, "desktop-browser");
525 assert.match(summary, /needs: \[changes, desktop-prepare, desktop-browser-group\]/);
526 const script = shellStep(summary, "Verify desktop browser groups");
527 const run = env => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...env } }).status;
528 assert.equal(run({ CHANGES_RESULT: "success", SHOULD_RUN: "true", PREPARE_RESULT: "success", GROUP_RESULT: "success" }), 0);
529 for (const result of ["failure", "cancelled", "skipped", ""])
530 assert.notEqual(run({ CHANGES_RESULT: "success", SHOULD_RUN: "true", PREPARE_RESULT: "success", GROUP_RESULT: result }), 0);
531 assert.equal(run({ CHANGES_RESULT: "success", SHOULD_RUN: "false", PREPARE_RESULT: "success", GROUP_RESULT: "skipped" }), 0);
532 });
533
534 test("Desktop race uses every verified partition and one shared cache writer", () => {
535 const body = job(ci, "desktop-go-race");
536 assert.deepEqual(body.match(/group: \[([^\]]+)\]/)[1].split(",").map(value => value.trim()), windowsDesktopGroups);
537 assert.match(body, /fail-fast: false/);
538 assert.match(body, /run: node \.\.\/scripts\/desktop-windows-go-tests\.mjs \$\{\{ matrix.group \}\} --race/);
539 for (const group of windowsDesktopGroups) {
540 const args = windowsDesktopTestArgs(group, true);
541 assert.deepEqual(args.filter(arg => arg !== "-race"), windowsDesktopTestArgs(group));
542 assert.equal(args.filter(arg => arg === "-race").length, 1);
543 }
544 assert.match(body, /matrix.group == 'A-B' && steps.gocache.outputs.key/);
545 assert.match(job(ci, "desktop"), /GO_RACE_RESULT: \$\{\{ needs.desktop-go-race.result \}\}/);
546 });
547
548 test("Desktop race runs on pushes, never on pull requests, and the aggregate expects exactly that", () => {
549 const body = job(ci, "desktop-go-race");
550 const aggregate = job(ci, "desktop").match(/RACE_REQUIRED: \$\{\{ (.+) \}\}/)[1];
551 assert.match(job(ci, "desktop"), /test "\$GO_RACE_RESULT" = "\$\(expected "\$RACE_REQUIRED"\)"/);
552 for (const event of ["pull_request", "push", "workflow_dispatch"]) {
553 for (const native of ["true", "false"]) {
554 for (const notes_only of ["true", "false"]) {
555 const context = { github: { event_name: event }, needs: {
556 "desktop-prepare": { result: "success" }, changes: { outputs: { native, notes_only } },
557 } };
558 const runs = condition(body, context);
559 assert.equal(runs, event !== "pull_request" && notes_only !== "true", `${event} native=${native} notes=${notes_only}`);
560 assert.equal(vm.runInNewContext(aggregate, context), runs);
561 }
562 }
563 }
564 });
565
566 test("installer evidence excludes running payloads and cache files on every publisher", () => {
567 for (const body of [job(ci, "desktop-windows-package"), job(release, "windows-runtime-acceptance")]) {
568 const upload = body.match(/name: Upload (?:signed )?Windows installer acceptance evidence\n([\s\S]*?)(?=\n - |$)/)?.[1];
569 assert.ok(upload);
570 for (const extension of ["json", "png", "log"])
571 assert.ok(upload.includes(`reasonix-installer-acceptance/**/*.${extension}`));
572 for (const excluded of ["installed/**", "**/cache/**"])
573 assert.ok(upload.includes(`!\${{ runner.temp }}/reasonix-installer-acceptance/${excluded}`));
574 }
575 });
576
577 test("Windows desktop Go partitions tests without verbose JSON cache overhead", () => {
578 const windowsGo = job(ci, "desktop-windows-go-group");
579 const context = { github: { event_name: "pull_request" }, needs: {
580 "desktop-prepare": { result: "success" }, changes: { outputs: { native: "true" } },
581 } };
582 assert.equal(condition(windowsGo, context), true);
583 assert.equal(condition(windowsGo, { ...context, cancelled: () => true }), false,
584 "superseded Windows workers must release the workflow concurrency slot");
585 assert.match(windowsGo, /run: node \.\.\/scripts\/desktop-windows-go-tests\.mjs \$\{\{ matrix.group \}\}/);
586 const commands = windowsDesktopGroups.map(group => {
587 const args = windowsDesktopTestArgs(group);
588 assert.equal(args[0], "test");
589 assert.equal(args.at(-1), "./...");
590 assert.ok(args.includes("-timeout=25m"));
591 assert.ok(!args.some(arg => (arg.startsWith("-timeout") && arg !== "-timeout=25m") || arg === "-json" || arg === "-v"));
592 return { run: args.includes("-run") ? args[args.indexOf("-run") + 1] : undefined,
593 skip: args.includes("-skip") ? args[args.indexOf("-skip") + 1] : undefined };
594 });
595 assert.equal(commands.length, windowsDesktopGroups.length);
596 // Include non-test entry points and every possible first suffix character.
597 // The complement group retains names outside the selected ranges.
598 const names = ["Example", "ExampleSession", "FuzzSession", "Test"];
599 for (let code = 0; code <= 127; code++) names.push(`Test${String.fromCharCode(code)}Session`);
600 names.push("Test会话", "TestΩSession", "TestWindowsTerminalProcessConPTYSmoke");
601 for (const name of names) {
602 const owners = commands.filter(command =>
603 (!command.run || new RegExp(command.run).test(name))
604 && (!command.skip || !new RegExp(command.skip).test(name)));
605 if (name === "TestWindowsTerminalProcessConPTYSmoke") {
606 assert.equal(owners.length, 0, `${name} must be isolated from the correctness partition`);
607 continue;
608 }
609 assert.equal(owners.length, 1, `${name} must run in exactly one group`);
610 }
611 assert.doesNotMatch(windowsGo, /go test -json/);
612 assert.doesNotMatch(windowsGo, /go-test-timing/);
613 assert.doesNotMatch(windowsGo, /go test -run ['"]?\^\$/);
614
615 const groups = windowsGo.match(/group: \[([^\]]+)\]/)[1].split(",").map(value => value.trim());
616 assert.deepEqual(groups, windowsDesktopGroups);
617 assert.match(windowsGo, /fail-fast: false/);
618
619 assert.match(windowsGo, /name: probe \(Windows ConPTY host integration\)[\s\S]*?continue-on-error: true[\s\S]*?run: go test -run '\^TestWindowsTerminalProcessConPTYSmoke\$' \./);
620 assert.match(windowsGo, /name: probe \(Windows ConPTY host integration\)\n\s+if: matrix.group == 'T-Z'/);
621 assert.match(windowsGo, /name: test \(vendored systray identity\)\n\s+if: matrix.group == 'T-Z'/);
622 assert.match(windowsGo, /steps\.conpty-smoke\.outcome == 'failure'/);
623 });
624
625 test("Windows desktop Go aggregate rejects incomplete matrix results", () => {
626 const summary = job(ci, "desktop-windows-go");
627 assert.match(summary, /needs: \[changes, desktop-prepare, desktop-windows-go-group\]/);
628 const script = shellStep(summary, "Verify Windows desktop Go groups");
629 const success = { CHANGES_RESULT: "success", SHOULD_RUN: "true", PREPARE_RESULT: "success", GROUP_RESULT: "success" };
630 const run = patch => spawnSync("bash", ["-e", "-c", script], { env: { ...process.env, ...success, ...patch } }).status;
631 assert.equal(run({}), 0);
632 for (const key of ["CHANGES_RESULT", "PREPARE_RESULT", "GROUP_RESULT"])
633 for (const result of ["failure", "cancelled", "skipped", ""])
634 assert.notEqual(run({ [key]: result }), 0, `${key}=${result}`);
635 assert.equal(run({ SHOULD_RUN: "false", PREPARE_RESULT: "skipped", GROUP_RESULT: "skipped" }), 0);
636 assert.notEqual(run({ SHOULD_RUN: "false" }), 0);
637 });
638
638 lines Plain Text