Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ self-hosted-runner:
- blacksmith-6vcpu-macos-26
- blacksmith-8vcpu-ubuntu-2404
- blacksmith-32vcpu-ubuntu-2404
# GitHub-hosted larger runner (org runner group "Maple"); actionlint only knows standard labels.
- maple-windows-2025-16core
3 changes: 2 additions & 1 deletion .github/workflows/agent-desktop-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ jobs:
name: Build Windows binary (${{ matrix.variant }})
needs: changes
if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') && github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
runs-on: blacksmith-8vcpu-windows-2025
# GitHub-hosted, like the signing and verify jobs that consume its binary.
runs-on: maple-windows-2025-16core
permissions:
contents: read
timeout-minutes: 120
Expand Down
56 changes: 45 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
CI_LINUX_HEAVY_RUNNER: ${{ vars.CI_LINUX_HEAVY_RUNNER }}
CI_MACOS_RUNNER: ${{ vars.CI_MACOS_RUNNER }}
CI_WINDOWS_RUNNER: ${{ vars.CI_WINDOWS_RUNNER }}
CI_WINDOWS_GITHUB_RUNNER: ${{ vars.CI_WINDOWS_GITHUB_RUNNER }}
run: python3 scripts/ci/plan.py github

nix:
Expand Down Expand Up @@ -101,7 +102,7 @@ jobs:
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
- name: Rust cache (restore everywhere, save only on master and on Blacksmith)
- name: Rust cache (restore everywhere, save only on master and in the lane's home store)
if: matrix.cache != ''
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
Expand All @@ -113,8 +114,8 @@ jobs:
add-rust-environment-hash-key: "false"
# The action runs cargo from the workspace directory: use an absolute flake path.
cmd-format: nix develop --no-update-lock-file ${{ github.workspace }}/${{ matrix.flake }} -c {0}
# A failover to a GitHub-hosted runner must not fill GitHub's small cache store.
save-if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'schedule') && startsWith(matrix.runner, 'blacksmith-') }}
# cache_save (plan.py): a failover runner never saves into another vendor's store.
save-if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'schedule') && matrix.cache_save }}
- name: Run lane
env:
LANE: ${{ matrix.id }}
Expand All @@ -138,30 +139,30 @@ jobs:
with:
persist-credentials: false
- uses: ./.github/actions/setup-agent-windows
- name: Rust cache (restore everywhere, save only on master and on Blacksmith)
- name: Rust cache (restore everywhere, save only on master and in the lane's home store)
if: matrix.cache != ''
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
prefix-key: ci-v1
shared-key: ${{ matrix.cache }}-${{ matrix.cache_key }}
workspaces: ${{ matrix.workspaces }}
add-rust-environment-hash-key: "false"
# A failover to a GitHub-hosted runner must not fill GitHub's small cache store.
save-if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'schedule') && startsWith(matrix.runner, 'blacksmith-') }}
# cache_save (plan.py): a failover runner never saves into another vendor's store.
save-if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'schedule') && matrix.cache_save }}
- name: Run lane
env:
LANE: ${{ matrix.id }}
run: python3 scripts/ci/plan.py run "$LANE"

# Native Windows updater tests and the app typecheck, compiled from source with
# no cache, credential or environment. Their receipts and Cargo logs are kept
# as an artifact even when a script fails.
# Native Windows updater tests and the app typecheck, with no credential or
# environment. Their receipts and Cargo logs are kept as an artifact even when
# a script fails.
agent-windows-updater:
name: agent-windows-updater
needs: plan
if: fromJSON(needs.plan.outputs.lanes)['agent-windows-updater']
# GitHub's image, as the native updater tests require (scripts/ci/lanes.py GITHUB_WINDOWS).
runs-on: windows-2025
runs-on: ${{ vars.CI_WINDOWS_GITHUB_RUNNER || 'maple-windows-2025-16core' }}
timeout-minutes: 60
defaults:
run:
Expand All @@ -171,6 +172,17 @@ jobs:
with:
persist-credentials: false
- uses: ./.github/actions/setup-agent-windows
- name: Rust cache (restore everywhere, save only on master and on GitHub's runners)
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
prefix-key: ci-v1
# Its own key: these checks compile other units than agent-full-windows'
# workspace build (check-mode metadata, one package's features).
shared-key: agent-windows-updater-${{ hashFiles('apps/maple-agent/flake.lock', 'apps/maple-agent/Cargo.lock') }}
workspaces: apps/maple-agent -> target
add-rust-environment-hash-key: "false"
# A failover to a Blacksmith runner must not fill Blacksmith's store.
save-if: ${{ github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'schedule') && !startsWith(runner.name, 'blacksmith') }}
- name: Typecheck the Windows app and run the native updater tests
run: python3 scripts/ci/plan.py run agent-windows-updater
- name: Keep the receipts and Cargo logs
Expand Down Expand Up @@ -279,7 +291,7 @@ jobs:
needs: plan
if: fromJSON(needs.plan.outputs.lanes)['agent-package-windows']
# GitHub's image, where master's install smoke runs (scripts/ci/lanes.py GITHUB_WINDOWS).
runs-on: windows-2025
runs-on: ${{ vars.CI_WINDOWS_GITHUB_RUNNER || 'maple-windows-2025-16core' }}
timeout-minutes: 90
defaults:
run:
Expand All @@ -291,8 +303,30 @@ jobs:
- uses: ./.github/actions/setup-agent-windows
with:
release: "true"
- name: Restore the packaging rehearsal cache
id: package-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
# Thin-LTO release artifacts; master's fat-LTO cache cannot serve them.
key: ci-v1-agent-package-${{ runner.os }}-${{ hashFiles('apps/maple-agent/flake.lock', 'apps/maple-agent/Cargo.lock') }}
restore-keys: ci-v1-agent-package-${{ runner.os }}-
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
apps/maple-agent/target
- name: Build, package and verify an unsigned Dev installer
run: python3 scripts/ci/plan.py run agent-package-windows
- name: Save the packaging rehearsal cache (nightly, GitHub's runners only)
if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'schedule' && !startsWith(runner.name, 'blacksmith') && steps.package-cache.outputs.cache-hit != 'true' }}
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
key: ${{ steps.package-cache.outputs.cache-primary-key }}
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
apps/maple-agent/target
- name: Upload the unsigned package
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/desktop-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ jobs:
build-windows:
needs: changes
if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.frontend != 'false' || needs.changes.outputs.windows != 'false') }}
runs-on: windows-2025
runs-on: maple-windows-2025-16core
timeout-minutes: 150
environment: windows-signing
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ jobs:

build-windows:
needs: classify-app-release
runs-on: windows-2025
runs-on: maple-windows-2025-16core
environment: windows-signing
env:
MAPLE_WINDOWS_ARTIFACT_SIGNING_ENDPOINT: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
Expand Down
2 changes: 1 addition & 1 deletion apps/maple-agent/docs/desktop-builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ continues to use the macOS feed. Dev has no update feed on either platform.
Both platforms retain one release version and source commit; separate feed
paths preserve compatibility with existing macOS clients.

Windows builds run on `blacksmith-8vcpu-windows-2025` with the pinned Rust/MSVC
Windows builds run on the org's 16-core GitHub-hosted `maple-windows-2025-16core` with the pinned Rust/MSVC
environment from `.github/actions/setup-agent-windows` and
`RUSTFLAGS='-D warnings -C target-feature=+crt-static'`; signing stays on
GitHub's `windows-2025`. `build-release.sh` selects the explicit x64 MSVC target
Expand Down
13 changes: 10 additions & 3 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ of the lanes your staged files select.
- Never add `paths:` to `ci.yml`; `ci-ok` must report on every pull request.
- Pull requests never save caches (one branch-scoped exception, outside
`ci.yml`, is listed under [caches](repository-workflows.md#caches)). Master
and nightly runs save lane caches, on Blacksmith runners only.
and nightly runs save lane caches into the store of the lane's own runner
vendor: Blacksmith's, or GitHub's for the Agent lanes on GitHub's Windows
image.
- Never cancel master, signed, evidence or nightly runs.
- Never edit the release or publisher workflows, signing environments or
OpenSecret approval gating to clear a check. A failing contract test means
Expand All @@ -114,8 +116,13 @@ of the lanes your staged files select.

## Runners

Lanes use Blacksmith runners or free GitHub-hosted runners; the runner
Lanes use Blacksmith runners or GitHub-hosted runners; the runner
constants are at the top of `scripts/ci/lanes.py`. During an outage, set the
repository variable `CI_LINUX_RUNNER`, `CI_LINUX_HEAVY_RUNNER`,
`CI_MACOS_RUNNER` or `CI_WINDOWS_RUNNER` to another label; no pull request is
needed, and a GitHub-hosted failover never saves a cache.
needed. The three Agent lanes that need GitHub's Windows image
(`agent-full-windows`, `agent-windows-updater`, `agent-package-windows`) run on
the org's 16-core GitHub-hosted runner `maple-windows-2025-16core` (billed by the
minute); `CI_WINDOWS_GITHUB_RUNNER=windows-2025` moves them back to the standard
runner, which uses the same cache store. A runner from the other vendor never
saves a cache.
44 changes: 26 additions & 18 deletions docs/repository-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ branches in step with the lane table.
goes to the run's branch-scoped Actions cache, which `master` and other
pull requests cannot read.
- Lane caches (`Swatinem/rust-cache`, prefix `ci-v1`, keyed by lane and
lockfile hashes) are saved by master pushes and the nightly run on Blacksmith
runners only. Heavy Rust caches therefore live in Blacksmith's store; GitHub's
own store, which is small, holds only what GitHub-hosted jobs need (the
Research Windows signed build, the release, the Nix installer and that EIF
check).
lockfile hashes) are saved by master pushes and the nightly run, into the
store of the lane's own runner vendor; a runner from the other vendor (a
failover) only restores. Heavy Rust caches therefore live in Blacksmith's
store. GitHub's holds what GitHub-hosted jobs need: the lane caches of
`agent-full-windows` and `agent-windows-updater`, the
`agent-package-windows` packaging cache (below), the unsigned Agent and signed
Research Windows builds, the release, the Nix installer and that EIF check.
`test_workflow_policy.py` lists these writers.
- The signed master builds write the keys `release.yml` restores by prefix
(`<os>-sccache-*`, `xcode-cache-*`, `release-onnxruntime-ios-*`,
`<os>-bun-*`, `<os>-gradle-*`). No pull-request job writes these prefixes;
Expand All @@ -50,8 +53,10 @@ branches in step with the lane table.
starts with a cold Bun cache; Bun installs in seconds.
- `agent-desktop-build.yml`'s unsigned builders save the exact key
`ci-v1-agent-release-<os>-<hash of both lockfiles>` on master pushes, with
no fallback keys; the Agent packaging lanes only restore it. Signing,
attestation and verification jobs restore nothing.
no fallback keys; no `ci.yml` lane reads it. The Agent packaging lanes build
thin LTO into their own `ci-v1-agent-package-<os>-*` caches, which only the
nightly master run saves. Signing, attestation and verification jobs restore
nothing.

## Concurrency

Expand Down Expand Up @@ -143,8 +148,10 @@ merging source.
The `agent-windows-updater` lane runs on pull requests and master pushes that
change the updater, the Windows bootstrap, the app's update and desktop code,
the release profiles or the Windows packaging scripts, and with `ci:windows` or
`ci:full`. It compiles from source, with no cache, signing credential or
protected environment. `scripts/ci/windows-app-typecheck.ps1` typechecks
`ci:full`. It has no signing credential or protected environment. Its
dependencies come from its own cache, which master pushes and the nightly run
save; the Agent's own crates always compile from source.
`scripts/ci/windows-app-typecheck.ps1` typechecks
`maple-agent-app --all-targets` with `cargo check --locked`, Windows-only test
fixtures included, without code generation. `scripts/ci/windows-updater-tests.ps1`
refuses signing and publication credentials, then runs the native updater,
Expand Down Expand Up @@ -208,15 +215,16 @@ Compilation runs on Blacksmith runners (ARM64 `blacksmith-6vcpu-macos-26` for
macOS); lane runners are the constants in `scripts/ci/lanes.py`. The signed
Research macOS, Linux, Android and iOS builds compile and sign on Blacksmith.
Artifact verifiers, TestFlight submission, and the Agent's signing and
attestation use GitHub-hosted runners. Windows signing and the Windows release
stay on GitHub's `windows-2025`; unsigned Agent and Research Windows builds use
`blacksmith-8vcpu-windows-2025`, with Rust, MSVC, Perl and the linker pinned by
`.github/actions/setup-agent-windows` for the Agent. The Agent's native Windows
test lanes (`agent-windows-updater`, `agent-full-windows`) and the Windows
packaging rehearsal also run on GitHub's `windows-2025`: Blacksmith's workspace
inherits a foreign write ACE that the updater's storage validation rejects, and
master's install smoke runs on GitHub's image. The Research Windows PR package has no
cache, because its signed build's cache lives in GitHub's store.
attestation use GitHub-hosted runners. Windows builds that compile run on the
org's 16-core GitHub-hosted runner `maple-windows-2025-16core`: the unsigned Agent
build, the signed Research build, the release's `build-windows`, and the Agent's
native Windows test lanes and packaging rehearsal ([CI runners](ci.md#runners)).
The short signing and verify jobs stay on GitHub's standard `windows-2025`. Rust,
MSVC, Perl and the linker are pinned by `.github/actions/setup-agent-windows` for
the Agent. Blacksmith's Windows workspace inherits a foreign write ACE that the
updater's storage validation rejects, so only the Agent Windows Clippy lane and
the Research Windows PR package use `blacksmith-8vcpu-windows-2025`; the latter
has no cache, because its signed build's cache lives in GitHub's store.

Research desktop and iOS builds select Xcode 26.5 (`17F42`) from
[`scripts/ci/apple-toolchain.json`](../scripts/ci/apple-toolchain.json) through
Expand Down
15 changes: 11 additions & 4 deletions scripts/ci/lanes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ class Runner:
variable: Optional[str] = None # repository variable that overrides the label


def cache_store(label: str) -> str:
"""Whose Actions cache a runner uses: Blacksmith runners have their own store, every other runner GitHub's."""
return "blacksmith" if label.startswith("blacksmith-") else "github"


# The override variables are unset; set one to fail over during an outage.
LINUX = Runner("blacksmith-8vcpu-ubuntu-2404", "CI_LINUX_RUNNER")
LINUX_HEAVY = Runner("blacksmith-32vcpu-ubuntu-2404", "CI_LINUX_HEAVY_RUNNER")
Expand All @@ -37,7 +42,9 @@ class Runner:
# The Agent updater's native Windows tests and the installer smoke test need GitHub's
# runner: Blacksmith's workspace inherits a foreign write ACE the updater rejects, and
# master's Windows install smoke (agent-desktop-build.yml verify-windows) runs there too.
GITHUB_WINDOWS = Runner("windows-2025")
# Its home is the org's 16-core GitHub-hosted Windows 2025 runner (the same image family,
# GitHub's cache store); CI_WINDOWS_GITHUB_RUNNER=windows-2025 falls back to the standard one.
GITHUB_WINDOWS = Runner("maple-windows-2025-16core", "CI_WINDOWS_GITHUB_RUNNER")
# .github/actions/setup-agent-windows pins this toolchain (Windows has no Nix);
# agent-lint checks that the Agent flake's rustc still reports it.
AGENT_WINDOWS_RUST = "1.98.0"
Expand Down Expand Up @@ -224,8 +231,8 @@ def deny(manifest: str, component: str) -> str:
" && cargo clippy --workspace --all-targets --locked -- -D warnings",
cache="agent-windows", timeout=45, **AGENT_WIN),
# The Windows app typecheck and the native updater, filesystem, Authenticode and
# bootstrap tests, compiled without a cache. Both scripts always run; each needs the
# Actions context and leaves a receipt and its Cargo log, which the job keeps.
# bootstrap tests, from the job's own dependency cache. Both scripts always run; each
# needs the Actions context and leaves a receipt and its Cargo log, which the job keeps.
Lane("agent-windows-updater", AGENT_WINDOWS_UPDATER, labels=("ci:windows", "ci:full"),
job="agent-windows-updater", runner=GITHUB_WINDOWS,
run="status=0; pwsh -NoProfile -File scripts/ci/windows-app-typecheck.ps1 || status=1;"
Expand Down Expand Up @@ -261,7 +268,7 @@ def deny(manifest: str, component: str) -> str:
events=PACKAGE_EVENTS, job="agent-package-macos", runner=MACOS, run="just package-rehearsal", timeout=60,
gate=False, **AGENT_PACKAGE_FLAKE),
# Windows is on the Agent feed, so its own packaging inputs select it too.
Lane("agent-package-windows", AGENT_WINDOWS_PACKAGING, labels=("ci:package", "ci:full"), events=PR_SIDE,
Lane("agent-package-windows", AGENT_WINDOWS_PACKAGING, labels=("ci:package", "ci:full"), events=PACKAGE_EVENTS,
job="agent-package-windows", runner=GITHUB_WINDOWS, workdir="apps/maple-agent", run=WINDOWS_PACKAGE,
env=(("RUSTFLAGS", "-D warnings -C target-feature=+crt-static"),) + FAST_RELEASE, timeout=90, gate=False),
# Every root flake check: actionlint, workflow policy and contracts, CI script
Expand Down
Loading
Loading