From dbf94804ad160d71f07548fb28e9ea9d1c1c97af Mon Sep 17 00:00:00 2001 From: Sinabina Date: Mon, 20 Jul 2026 16:58:31 -0700 Subject: [PATCH] fix: make runtime release six-platform safe Use an allowed local file URL for the browser smoke test, centralize keyless archive signing after native builds so Windows ARM64 does not require an unavailable Cosign binary, and advance the immutable bootstrap channel to RC2. --- .github/workflows/release-artifacts.yml | 21 +++++++++---------- runtime/runtime-bootstrap.mjs | 2 +- .../references/support/runtime-bootstrap.mjs | 2 +- .../references/support/runtime-bootstrap.mjs | 2 +- .../references/support/runtime-bootstrap.mjs | 2 +- .../references/support/runtime-bootstrap.mjs | 2 +- .../references/support/runtime-bootstrap.mjs | 2 +- .../references/support/runtime-bootstrap.mjs | 2 +- test/gstack2-runtime-release-channel.test.ts | 8 +++++++ test/release-hardening.test.ts | 13 +++++++++--- 10 files changed, 35 insertions(+), 21 deletions(-) diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index bb4c7b14d..b6aab910d 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -53,8 +53,6 @@ jobs: - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 22.23.1 - - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 - - name: Install frozen dependencies run: bun install --frozen-lockfile --ignore-scripts shell: bash @@ -114,8 +112,9 @@ jobs: "$GSTACK_HOME/bin/browse" stop >/dev/null 2>&1 || true } trap browser_cleanup EXIT + smoke_url=$(node -e 'const fs=require("fs"),p=require("path").join(process.env.RUNNER_TEMP,"gstack-runtime-smoke.html");fs.writeFileSync(p,"GStack runtime smoke\n");process.stdout.write(require("url").pathToFileURL(p).href)') PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \ - "$GSTACK_HOME/bin/browse" goto about:blank + "$GSTACK_HOME/bin/browse" goto "$smoke_url" PATH="$clean_path" GSTACK_NODE="$node_command" BROWSE_PARENT_PID=0 \ "$GSTACK_HOME/bin/browse" status browser_cleanup @@ -132,14 +131,6 @@ jobs: done shell: bash - - name: Keyless-sign component archives - run: | - set -euo pipefail - for archive in release-output/*.tar.gz; do - cosign sign-blob --yes --bundle "$archive.sigstore.json" "$archive" - done - shell: bash - - name: Attest component archive provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: @@ -171,6 +162,14 @@ jobs: merge-multiple: true - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + - name: Keyless-sign component archives + run: | + set -euo pipefail + for archive in release-output/*.tar.gz; do + cosign sign-blob --yes --bundle "$archive.sigstore.json" "$archive" + done + shell: bash + - name: Create strict six-target manifest run: node .github/scripts/create-runtime-release-manifest.mjs release-output "$GITHUB_REPOSITORY" 2.0.0 "$GITHUB_REF_NAME" diff --git a/runtime/runtime-bootstrap.mjs b/runtime/runtime-bootstrap.mjs index 9419f2a87..8270ed190 100644 --- a/runtime/runtime-bootstrap.mjs +++ b/runtime/runtime-bootstrap.mjs @@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; // Keep the runtime compatibility version separate from the immutable release // channel. Release candidates carry the 2.0.0 runtime contract while letting // fresh-machine production journeys run before the stable v2.0.0 tag exists. -export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); diff --git a/skills/debug/references/support/runtime-bootstrap.mjs b/skills/debug/references/support/runtime-bootstrap.mjs index 9419f2a87..8270ed190 100644 --- a/skills/debug/references/support/runtime-bootstrap.mjs +++ b/skills/debug/references/support/runtime-bootstrap.mjs @@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; // Keep the runtime compatibility version separate from the immutable release // channel. Release candidates carry the 2.0.0 runtime contract while letting // fresh-machine production journeys run before the stable v2.0.0 tag exists. -export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); diff --git a/skills/design/references/support/runtime-bootstrap.mjs b/skills/design/references/support/runtime-bootstrap.mjs index 9419f2a87..8270ed190 100644 --- a/skills/design/references/support/runtime-bootstrap.mjs +++ b/skills/design/references/support/runtime-bootstrap.mjs @@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; // Keep the runtime compatibility version separate from the immutable release // channel. Release candidates carry the 2.0.0 runtime contract while letting // fresh-machine production journeys run before the stable v2.0.0 tag exists. -export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); diff --git a/skills/plan/references/support/runtime-bootstrap.mjs b/skills/plan/references/support/runtime-bootstrap.mjs index 9419f2a87..8270ed190 100644 --- a/skills/plan/references/support/runtime-bootstrap.mjs +++ b/skills/plan/references/support/runtime-bootstrap.mjs @@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; // Keep the runtime compatibility version separate from the immutable release // channel. Release candidates carry the 2.0.0 runtime contract while letting // fresh-machine production journeys run before the stable v2.0.0 tag exists. -export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); diff --git a/skills/qa/references/support/runtime-bootstrap.mjs b/skills/qa/references/support/runtime-bootstrap.mjs index 9419f2a87..8270ed190 100644 --- a/skills/qa/references/support/runtime-bootstrap.mjs +++ b/skills/qa/references/support/runtime-bootstrap.mjs @@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; // Keep the runtime compatibility version separate from the immutable release // channel. Release candidates carry the 2.0.0 runtime contract while letting // fresh-machine production journeys run before the stable v2.0.0 tag exists. -export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); diff --git a/skills/review/references/support/runtime-bootstrap.mjs b/skills/review/references/support/runtime-bootstrap.mjs index 9419f2a87..8270ed190 100644 --- a/skills/review/references/support/runtime-bootstrap.mjs +++ b/skills/review/references/support/runtime-bootstrap.mjs @@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; // Keep the runtime compatibility version separate from the immutable release // channel. Release candidates carry the 2.0.0 runtime contract while letting // fresh-machine production journeys run before the stable v2.0.0 tag exists. -export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); diff --git a/skills/ship/references/support/runtime-bootstrap.mjs b/skills/ship/references/support/runtime-bootstrap.mjs index 9419f2a87..8270ed190 100644 --- a/skills/ship/references/support/runtime-bootstrap.mjs +++ b/skills/ship/references/support/runtime-bootstrap.mjs @@ -16,7 +16,7 @@ export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; // Keep the runtime compatibility version separate from the immutable release // channel. Release candidates carry the 2.0.0 runtime contract while letting // fresh-machine production journeys run before the stable v2.0.0 tag exists. -export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.2"; export const OFFICIAL_MANIFEST_URL = `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); diff --git a/test/gstack2-runtime-release-channel.test.ts b/test/gstack2-runtime-release-channel.test.ts index 53628ede0..964424ad3 100644 --- a/test/gstack2-runtime-release-channel.test.ts +++ b/test/gstack2-runtime-release-channel.test.ts @@ -57,10 +57,18 @@ describe("GStack runtime release channel", () => { test("release workflow publishes both RC and stable tags through the same signed manifest path", async () => { const workflow = await fs.readFile(WORKFLOW, "utf8"); + const buildSection = workflow.slice(workflow.indexOf(" build:"), workflow.indexOf("\n manifest:")); + const manifestSection = workflow.slice(workflow.indexOf("\n manifest:")); expect(workflow).toContain("v2.0.0-rc.*"); expect(workflow).toContain('2.0.0 "$GITHUB_REF_NAME"'); expect(workflow).toContain("PRERELEASE_FLAG:"); expect(workflow).toContain("--prerelease"); expect(workflow).toContain('gh release create "$GITHUB_REF_NAME"'); + expect(workflow).toContain("pathToFileURL(p).href"); + expect(workflow).not.toContain("goto about:blank"); + expect(buildSection).not.toContain("sigstore/cosign-installer"); + expect(manifestSection).toContain("sigstore/cosign-installer"); + expect(manifestSection.indexOf("Keyless-sign component archives")) + .toBeLessThan(manifestSection.indexOf("Create strict six-target manifest")); }); }); diff --git a/test/release-hardening.test.ts b/test/release-hardening.test.ts index 529683957..bde6c9351 100644 --- a/test/release-hardening.test.ts +++ b/test/release-hardening.test.ts @@ -53,14 +53,20 @@ describe("release and CI hardening", () => { expect(workflow).toContain("versions/current.json"); expect(workflow).not.toContain('active="$GSTACK_HOME/versions/2.0.0"'); expect(workflow).toContain(".gstack-runtime-browsers"); - expect(workflow).toContain('chromium.launch({ headless: true, channel: "chromium" })'); + // Exercise both the bundled browser and the explicit Chromium channel. Keep + // this semantic: the workflow intentionally loops over launch options so a + // harmless refactor does not invalidate release hardening. + expect(workflow).toMatch(/for \(const options of \[\{ headless: true \}, \{ headless: true, channel: ["']chromium["'] \}\]\)/); + expect(workflow).toContain("chromium.launch(options)"); + expect(workflow).toContain("await browser.close()"); expect(workflow).not.toContain("--with-deps"); expect(workflow).toContain(".gstack-runtime-tools/bun"); expect(workflow).toContain('"$GSTACK_HOME/bin/bun" --version'); expect(workflow).toContain("BUN-LICENSE-1.3.14.md"); expect(workflow).toContain("command -v bun"); expect(workflow).toContain("GSTACK_NODE=\"$node_command\""); - expect(workflow).toContain("goto about:blank"); + expect(workflow).toContain("pathToFileURL(p).href"); + expect(workflow).not.toContain("goto about:blank"); const manifest = read(".github/scripts/create-runtime-release-manifest.mjs"); expect(manifest).toContain("bytes: stat.size"); expect(manifest).toContain('certificateOidcIssuer: "https://token.actions.githubusercontent.com"'); @@ -80,7 +86,8 @@ describe("release and CI hardening", () => { expect(installer).toContain('entry(managedBunRelativePath(), "managed-bun", true)'); const browser = read("browse/src/cli.ts"); expect(browser).toContain("Every installed/compiled client must use the adjacent Node-compatible daemon"); - expect(browser).toContain("if (IS_COMPILED && !NODE_SERVER_SCRIPT)"); + expect(browser).toContain("export function resolveServerLaunchTarget("); + expect(browser).toContain("server-node.mjs not found. Rebuild the managed browser runtime"); }); test("Windows setup lane installs, doctors, and uninstalls rather than only building", () => {