From cb0f36574c1b96c4241415b4a2ae9bf5384bac6d Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:12:24 -0700 Subject: [PATCH] fix(release): separate repair control from source --- .github/workflows/release.yml | 3 +- scripts/check-release-consistency.mjs | 35 +++++++++++++++++----- scripts/check-release-consistency.test.mjs | 23 ++++++++++++++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9027c6c..d208527e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,6 +72,7 @@ jobs: uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ steps.source.outputs.source_sha }} + path: .release-source persist-credentials: false - name: Install Node.js @@ -80,7 +81,7 @@ jobs: node-version: 24.13.1 - name: Verify tag, packages, clients, docs, and downloads agree - run: node scripts/check-release-consistency.mjs --tag "$RELEASE_TAG" + run: node scripts/check-release-consistency.mjs --tag "$RELEASE_TAG" --repo-root .release-source ci-authority: needs: verify diff --git a/scripts/check-release-consistency.mjs b/scripts/check-release-consistency.mjs index 9e4ca1f8..7d29d45f 100644 --- a/scripts/check-release-consistency.mjs +++ b/scripts/check-release-consistency.mjs @@ -1419,17 +1419,38 @@ export function checkReleaseConsistency(repoRoot, releaseTag) { return collectReleaseConsistencyErrors(loadReleaseContractFiles(repoRoot), releaseTag); } -function parseTagArgument(args) { - if (args.length === 0) return undefined; - if (args.length === 2 && args[0] === "--tag" && args[1]) return args[1]; - throw new Error("usage: node scripts/check-release-consistency.mjs [--tag vX.Y.Z]"); +export function parseCliArguments(args, cwd = process.cwd()) { + let releaseTag; + let repoRoot = resolve(cwd); + let repoRootProvided = false; + for (let index = 0; index < args.length; index += 2) { + const option = args[index]; + const value = args[index + 1]; + if (!value) { + throw new Error( + "usage: node scripts/check-release-consistency.mjs [--tag vX.Y.Z] [--repo-root path]", + ); + } + if (option === "--tag" && releaseTag === undefined) { + releaseTag = value; + } else if (option === "--repo-root" && !repoRootProvided) { + repoRoot = resolve(cwd, value); + repoRootProvided = true; + } else { + throw new Error( + "usage: node scripts/check-release-consistency.mjs [--tag vX.Y.Z] [--repo-root path]", + ); + } + } + return { releaseTag, repoRoot }; } const isMain = process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url)); if (isMain) { try { - const errors = checkReleaseConsistency(process.cwd(), parseTagArgument(process.argv.slice(2))); + const { releaseTag, repoRoot } = parseCliArguments(process.argv.slice(2)); + const errors = checkReleaseConsistency(repoRoot, releaseTag); if (errors.length > 0) { console.error( `Release consistency check failed with ${errors.length} error${errors.length === 1 ? "" : "s"}:`, @@ -1437,9 +1458,7 @@ if (isMain) { for (const error of errors) console.error(`- ${error}`); process.exitCode = 1; } else { - const version = JSON.parse( - readFileSync(resolve(process.cwd(), "package.json"), "utf8"), - ).version; + const version = JSON.parse(readFileSync(resolve(repoRoot, "package.json"), "utf8")).version; console.log(`Release consistency check passed for v${version}.`); } } catch (error) { diff --git a/scripts/check-release-consistency.test.mjs b/scripts/check-release-consistency.test.mjs index aad37c69..471d87f1 100644 --- a/scripts/check-release-consistency.test.mjs +++ b/scripts/check-release-consistency.test.mjs @@ -9,6 +9,7 @@ import { collectReleaseConsistencyErrors, discoverReleasePackagePaths, loadReleaseContractFiles, + parseCliArguments, } from "./check-release-consistency.mjs"; const repoRoot = resolve(import.meta.dirname, ".."); @@ -435,6 +436,28 @@ test("historical repair runs CI authority from trusted control while querying ol assert.notEqual(checkoutSha, queriedSha); }); +test("historical repair keeps trusted verification code separate from immutable source", () => { + const verifyJob = requiredWorkflowJob(files.get(".github/workflows/release.yml"), "verify"); + const controlCheckout = requiredNamedStep(verifyJob, "Check out trusted release-control source"); + const sourceCheckout = requiredNamedStep(verifyJob, "Check out immutable release source"); + const consistencyStep = requiredNamedStep( + verifyJob, + "Verify tag, packages, clients, docs, and downloads agree", + ); + + assert.equal(controlCheckout.with.ref, "${{ github.sha }}"); + assert.equal(sourceCheckout.with.ref, "${{ steps.source.outputs.source_sha }}"); + assert.equal(sourceCheckout.with.path, ".release-source"); + assert.equal( + consistencyStep.run, + 'node scripts/check-release-consistency.mjs --tag "$RELEASE_TAG" --repo-root .release-source', + ); + assert.deepEqual(parseCliArguments(["--tag", "v0.1.31", "--repo-root", ".release-source"]), { + releaseTag: "v0.1.31", + repoRoot: resolve(".release-source"), + }); +}); + test("rejects published app-wire version drift until release surfaces agree", () => { const drifted = changedRuntime("publishedAppWire", (record) => { record.version = "0.5.1";