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