Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 65 additions & 114 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

204 changes: 94 additions & 110 deletions .github/workflows/cli-package-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ on:
value: ${{ jobs.build.outputs.release_candidate_run_attempt }}
release_predecessor_version:
description: Exact npm Nightly version qualified against this candidate
value: ${{ jobs.release-predecessor.outputs.version }}
value: ${{ jobs.build.outputs.release_predecessor_version }}
release_predecessor_tarball_url:
description: Exact npm Nightly tarball qualified against this candidate
value: ${{ jobs.release-predecessor.outputs.tarball_url }}
value: ${{ jobs.build.outputs.release_predecessor_tarball_url }}
release_predecessor_integrity:
description: npm SHA-512 integrity of the Nightly tarball qualified against this candidate
value: ${{ jobs.release-predecessor.outputs.integrity }}
value: ${{ jobs.build.outputs.release_predecessor_integrity }}
workflow_dispatch:

permissions:
Expand All @@ -82,26 +82,6 @@ concurrency:
group: cli-package-validation-${{ github.workflow }}-${{ github.ref }}

jobs:
release-predecessor:
name: Resolve immutable release predecessor
runs-on: ubuntu-24.04
timeout-minutes: 45
outputs:
version: ${{ steps.predecessor.outputs.version }}
tarball_url: ${{ steps.predecessor.outputs.tarball_url }}
integrity: ${{ steps.predecessor.outputs.integrity }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.source_commit || github.sha }}
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
- name: Resolve the current npm Nightly as immutable evidence
id: predecessor
run: node scripts/release-cli-publication.mjs resolve-nightly-predecessor "$GITHUB_OUTPUT"

peer-native:
name: Build direct-peer addon (${{ matrix.target }})
runs-on: ${{ matrix.runner }}
Expand Down Expand Up @@ -212,6 +192,9 @@ jobs:
outputs:
release_candidate_artifact_id: ${{ steps.release-candidate.outputs.artifact-id }}
release_candidate_run_attempt: ${{ github.run_attempt }}
release_predecessor_version: ${{ steps.predecessor.outputs.version }}
release_predecessor_tarball_url: ${{ steps.predecessor.outputs.tarball_url }}
release_predecessor_integrity: ${{ steps.predecessor.outputs.integrity }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -223,6 +206,12 @@ jobs:
cache: npm
- name: Select the release npm toolchain
run: npm install --global --no-audit --no-fund "$(node -p 'require("./package.json").packageManager')"
# Resolving one npm version is seconds of work against a registry this
# job already reaches, so it rides the runner that waits on the addon
# builds instead of holding a second one for the whole wait.
- name: Resolve the current npm Nightly as immutable evidence
id: predecessor
run: node scripts/release-cli-publication.mjs resolve-nightly-predecessor "$GITHUB_OUTPUT"
- name: Install cargo-deny
uses: taiki-e/install-action@1ed6d7be6168f6c9046541087ff549b6bc581fdf # v2
with:
Expand Down Expand Up @@ -259,29 +248,30 @@ jobs:
fail-fast: false
matrix:
include:
- name: Linux x64 / Node 22.19
# The two supported Node versions share a machine and a tarball, so
# they are two runs of the same smoke rather than two runners.
- name: Linux x64 / Node 22.19 and 24
runner: ubuntu-24.04
node: '22.19.0'
platform: linux
arch: x64
- name: Linux x64 / Node 24
runner: ubuntu-24.04
node: '24'
second_node: '24'
platform: linux
arch: x64
- name: Linux arm64 / Node 24
runner: ubuntu-24.04-arm
node: '24'
second_node: ''
platform: linux
arch: arm64
- name: macOS arm64 / Node 24
runner: macos-15
node: '24'
second_node: ''
platform: darwin
arch: arm64
- name: Windows x64 / Node 24
runner: windows-2025
node: '24'
second_node: ''
platform: win32
arch: x64
steps:
Expand All @@ -307,38 +297,22 @@ jobs:
path: packages/cli/release
- name: Validate the installed tarball
run: node scripts/smoke-release-cli-package.mjs
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
if: matrix.second_node != ''
with:
node-version: ${{ matrix.second_node }}
- name: Select the release npm toolchain for the second Node
if: matrix.second_node != ''
run: npm install --global --no-audit --no-fund "$(node -p 'require("./package.json").packageManager')"
- name: Validate the installed tarball on the second Node
if: matrix.second_node != ''
run: node scripts/smoke-release-cli-package.mjs

state-root-qualification:
name: Qualify released State Root (${{ matrix.name }})
needs: [build, release-predecessor]
name: Qualify released State Roots
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- name: cross epoch 74 to 76
source_url: https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.3.20260830.tgz
source_sha256: 66b1ce9307c9d5c06eaa7a6cbf533d4747d02caf71c1776c69c7dbfa12c3f414
target_kind: published
target_url: https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.4.20260830.tgz
target_sha256: b7d48adb466e16be7ffefbda3a0fcd833cc4108ea502b27778d0f4da680e1fc0
epoch_relation: different
- name: same epoch 76
source_url: https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.4.20260830.tgz
source_sha256: b7d48adb466e16be7ffefbda3a0fcd833cc4108ea502b27778d0f4da680e1fc0
target_kind: published
target_url: https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.5.20260830.tgz
target_sha256: e7a682157c6899fc7f1be86a2d7b0bd0696195a5771d8cc97bd1389a5b74989f
epoch_relation: same
- name: current Nightly predecessor to candidate
source_url: ${{ needs.release-predecessor.outputs.tarball_url }}
source_sha256: ''
source_integrity: ${{ needs.release-predecessor.outputs.integrity }}
target_kind: candidate
target_url: ''
target_sha256: ''
epoch_relation: any
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -355,80 +329,90 @@ jobs:
sudo apt-get install --yes bubblewrap
bwrap --version
- name: Download the release candidate
if: matrix.target_kind == 'candidate'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ needs.build.outputs.release_candidate_artifact_id }}
path: packages/cli/release
- name: Prepare exact source and target artifacts
# Three runs of one script against one sandbox, not three runners. Two of
# these transitions are between tarballs that were published and frozen,
# so nothing in a pull request can change their outcome except the
# qualifier itself, and the third reads the candidate this run built.
- name: Qualify the released State Root transitions
env:
SOURCE_URL: ${{ matrix.source_url }}
SOURCE_SHA256: ${{ matrix.source_sha256 }}
SOURCE_INTEGRITY: ${{ matrix.source_integrity }}
TARGET_KIND: ${{ matrix.target_kind }}
TARGET_URL: ${{ matrix.target_url }}
TARGET_SHA256: ${{ matrix.target_sha256 }}
MAKA_QUALIFICATION_BWRAP_USE_SUDO: '1'
PREDECESSOR_TARBALL_URL: ${{ needs.build.outputs.release_predecessor_tarball_url }}
PREDECESSOR_INTEGRITY: ${{ needs.build.outputs.release_predecessor_integrity }}
run: |
set -euo pipefail
evidence_root="$RUNNER_TEMP/released-state-root"
mkdir -p "$evidence_root"
source_path="$evidence_root/source.tgz"
curl --fail --location --max-filesize 67108864 --proto '=https' --tlsv1.2 "$SOURCE_URL" --output "$source_path"
source_sha256="$SOURCE_SHA256"
if [[ -n "$SOURCE_INTEGRITY" ]]; then
node - "$source_path" "$SOURCE_INTEGRITY" <<'NODE'

qualify() {
local slug="$1" source_url="$2" source_sha256="$3" source_integrity="$4"
local target_kind="$5" target_url="$6" target_sha256="$7" epoch_relation="$8"
local source_path target_path
echo "::group::Qualify $slug"
source_path="$evidence_root/$slug-source.tgz"
curl --fail --location --max-filesize 67108864 --proto '=https' --tlsv1.2 "$source_url" --output "$source_path"
if [[ -n "$source_integrity" ]]; then
node - "$source_path" "$source_integrity" <<'NODE'
const { createHash } = require('node:crypto');
const { readFileSync } = require('node:fs');
const bytes = readFileSync(process.argv[2]);
const actual = `sha512-${createHash('sha512').update(bytes).digest('base64')}`;
if (actual !== process.argv[3]) throw new Error('Source tarball integrity mismatch');
NODE
source_sha256="$(sha256sum "$source_path" | cut -d ' ' -f 1)"
else
test -n "$source_sha256"
fi
if [[ "$TARGET_KIND" == 'published' ]]; then
target_path="$evidence_root/target.tgz"
curl --fail --location --max-filesize 67108864 --proto '=https' --tlsv1.2 "$TARGET_URL" --output "$target_path"
target_sha256="$TARGET_SHA256"
else
target_path="$(find packages/cli/release -maxdepth 1 -name '*.tgz' -print -quit)"
test -n "$target_path"
target_path="$(realpath "$target_path")"
target_sha256="$(sha256sum "$target_path" | cut -d ' ' -f 1)"
fi
{
echo "SOURCE_PATH=$source_path"
echo "SOURCE_SHA256=$source_sha256"
echo "TARGET_PATH=$target_path"
echo "TARGET_SHA256=$target_sha256"
} >> "$GITHUB_ENV"
- name: Qualify the released State Root transition
env:
EXPECTED_EPOCH_RELATION: ${{ matrix.epoch_relation }}
MAKA_QUALIFICATION_BWRAP_USE_SUDO: '1'
run: |
set -o pipefail
npm run --silent release:cli:qualify-state-root -- \
--source "$SOURCE_PATH" \
--source-sha256 "$SOURCE_SHA256" \
--target "$TARGET_PATH" \
--target-sha256 "$TARGET_SHA256" \
--expect-epoch-relation "$EXPECTED_EPOCH_RELATION" \
| tee "$RUNNER_TEMP/released-state-root-report.json"
- name: Preserve the qualification report
source_sha256="$(sha256sum "$source_path" | cut -d ' ' -f 1)"
else
test -n "$source_sha256"
fi
if [[ "$target_kind" == 'published' ]]; then
target_path="$evidence_root/$slug-target.tgz"
curl --fail --location --max-filesize 67108864 --proto '=https' --tlsv1.2 "$target_url" --output "$target_path"
test -n "$target_sha256"
else
target_path="$(find packages/cli/release -maxdepth 1 -name '*.tgz' -print -quit)"
test -n "$target_path"
target_path="$(realpath "$target_path")"
target_sha256="$(sha256sum "$target_path" | cut -d ' ' -f 1)"
fi
npm run --silent release:cli:qualify-state-root -- \
--source "$source_path" \
--source-sha256 "$source_sha256" \
--target "$target_path" \
--target-sha256 "$target_sha256" \
--expect-epoch-relation "$epoch_relation" \
| tee "$evidence_root/$slug-report.json"
echo "::endgroup::"
}

qualify cross-epoch-74-to-76 \
https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.3.20260830.tgz \
66b1ce9307c9d5c06eaa7a6cbf533d4747d02caf71c1776c69c7dbfa12c3f414 '' \
published https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.4.20260830.tgz \
b7d48adb466e16be7ffefbda3a0fcd833cc4108ea502b27778d0f4da680e1fc0 different

qualify same-epoch-76 \
https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.4.20260830.tgz \
b7d48adb466e16be7ffefbda3a0fcd833cc4108ea502b27778d0f4da680e1fc0 '' \
published https://registry.npmjs.org/maka-agent/-/maka-agent-0.2.0-dev.5.20260830.tgz \
e7a682157c6899fc7f1be86a2d7b0bd0696195a5771d8cc97bd1389a5b74989f same

qualify current-nightly-predecessor-to-candidate \
"$PREDECESSOR_TARBALL_URL" '' "$PREDECESSOR_INTEGRITY" \
candidate '' '' any
- name: Preserve the qualification reports
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: released-state-root-${{ strategy.job-index }}
path: ${{ runner.temp }}/released-state-root-report.json
name: released-state-root
path: ${{ runner.temp }}/released-state-root/*-report.json
if-no-files-found: error
retention-days: 7
- name: Require the qualified Nightly predecessor to remain current
if: matrix.target_kind == 'candidate'
env:
PREDECESSOR_VERSION: ${{ needs.release-predecessor.outputs.version }}
PREDECESSOR_TARBALL_URL: ${{ needs.release-predecessor.outputs.tarball_url }}
PREDECESSOR_INTEGRITY: ${{ needs.release-predecessor.outputs.integrity }}
PREDECESSOR_VERSION: ${{ needs.build.outputs.release_predecessor_version }}
PREDECESSOR_TARBALL_URL: ${{ needs.build.outputs.release_predecessor_tarball_url }}
PREDECESSOR_INTEGRITY: ${{ needs.build.outputs.release_predecessor_integrity }}
run: |
node scripts/release-cli-publication.mjs assert-nightly-predecessor \
"$PREDECESSOR_VERSION" \
Expand Down
32 changes: 29 additions & 3 deletions .github/workflows/release-windows-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ on:
- 'scripts/package-windows-autoupdate-next.mjs'
# The Abort-path rollback hook ships inside the installer itself.
- 'apps/desktop/build/installer.nsh'
# The packaged updater's feed behavior — and the boot wiring that hands
# MAKA_UPDATE_TEST_FEED to it — is only observable on this path.
# The packaged updater's feed behavior is only observable on this path.
# The boot wiring that hands MAKA_UPDATE_TEST_FEED to it is not here:
# that is a few lines, `scripts/update-test-feed-wiring.test.mjs` asserts
# them on every change for free, and naming the file made a 1900-line
# module the single largest source of runs on this 25-minute lane.
- 'apps/desktop/src/main/app-update-service.ts'
- 'apps/desktop/src/main/main-window.ts'
- 'apps/desktop/src/main/runtime-host-boot.ts'
- 'packages/runtime-host/src/client/connect-or-spawn.ts'
- 'packages/runtime-host/src/client/launcher.ts'
- 'apps/desktop/src/main/windows-maximize-renderer-sync.ts'
Expand Down Expand Up @@ -94,6 +96,11 @@ on:
- 'apps/desktop/resources/licenses/cargo/THIRD_PARTY_NOTICES.txt'
- '.github/workflows/release.yml'
- '.github/workflows/release-windows-check.yml'
# The list above is a pre-filter, not this lane's import closure, so a
# transitive edit it cannot match would otherwise first be observed on release
# day. Same pairing as the other two Windows lanes.
schedule:
- cron: '17 5 * * *'
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -135,6 +142,19 @@ jobs:
version="$(node -p "require('./apps/desktop/package.json').version")"
npm run verify:windows-x64 -- "apps/desktop/release/Maka-${version}-win-x64.exe"

# The baseline is pinned by version, tag, asset name and SHA-256 in
# `scripts/windows-upgrade-baseline.json`, so it is immutable and the
# cache key is that file. Downloading it is this lane's most frequent
# failure — 14 of 36 across 300 runs, always on branches that could not
# have caused it, each false red costing another 25-minute Windows
# runner. A hit skips the network; the step below re-verifies the
# checksum either way, so a corrupt entry still fails loudly.
- name: Restore the pinned Windows upgrade baseline
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: artifacts/windows-upgrade-baseline
key: windows-upgrade-baseline-${{ hashFiles('scripts/windows-upgrade-baseline.json') }}

- name: Download and verify the pinned Windows upgrade baseline
id: previous
env:
Expand All @@ -145,6 +165,9 @@ jobs:
"$version" artifacts/windows-upgrade-baseline)"
echo "exe=$previous_exe" >> "$GITHUB_OUTPUT"

# The pinned release is only one input here. The other is the installer
# this run just built, which this step installs over it, so a pull request
# does decide the outcome.
- name: Exercise pinned-version upgrade and uninstall
run: |
version="$(node -p "require('./apps/desktop/package.json').version")"
Expand All @@ -162,6 +185,9 @@ jobs:
"apps/desktop/release/Maka-${version}-win-x64.exe" \
apps/desktop/release-autoupdate-next

# Consumes only what this run built — the candidate installer and the
# version-bumped one — so it is the pull request's own evidence that the
# `installer.nsh` Abort path still restores the previous installation.
- name: Prove deterministic mid-install failure rollback
run: |
version="$(node -p "require('./apps/desktop/package.json').version")"
Expand Down
Loading
Loading