Repository navigation
Fix Windows release preflight for versioned installations #1213
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Maple Agent CI | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| # Run unprivileged validation for stacked PRs as well as master targets. | |
| workflow_dispatch: | |
| # PR code, including forks, executes only on fresh managed runners with a | |
| # read-only token. No signing, release publication, environment, or deployment | |
| # credentials belong in this workflow. | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: maple-agent-ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_PROFILE_DEV_DEBUG: 0 | |
| RUSTFLAGS: -D warnings | |
| defaults: | |
| run: | |
| shell: bash | |
| working-directory: apps/maple-agent | |
| jobs: | |
| changes: | |
| name: Select Agent checks | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| actions: read | |
| outputs: | |
| agent: ${{ steps.classify.outputs.agent }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Retain Agent changes since the last validated master run | |
| id: master | |
| if: ${{ github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && github.event_name == 'push' }} | |
| working-directory: . | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: python3 scripts/ci/agent_master_selection.py --lane ci >> "$GITHUB_OUTPUT" | |
| - name: Classify changed paths | |
| id: classify | |
| working-directory: . | |
| env: | |
| BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} | |
| HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| MASTER_AGENT: ${{ steps.master.outputs.agent }} | |
| run: | | |
| set -euo pipefail | |
| enable_agent() { echo 'agent=true' >> "$GITHUB_OUTPUT"; } | |
| case "$GITHUB_EVENT_NAME" in | |
| pull_request) separator='...' ;; | |
| push) | |
| # A later unrelated push must not consume Agent work cancelled | |
| # before every workspace, provider and release check completed. | |
| if [ "$GITHUB_REPOSITORY" = MaplePrivacyLabs/Maple ] && [ "$GITHUB_REF" = refs/heads/master ]; then | |
| if [ "${MASTER_AGENT:-}" = false ]; then | |
| echo 'agent=false' >> "$GITHUB_OUTPUT" | |
| else | |
| enable_agent | |
| fi | |
| exit 0 | |
| fi | |
| separator='..' | |
| if [[ "$BASE_SHA" =~ ^0+$ ]]; then | |
| enable_agent | |
| exit 0 | |
| fi | |
| ;; | |
| *) enable_agent; exit 0 ;; | |
| esac | |
| if ! git cat-file -e "${BASE_SHA}^{commit}" || ! git cat-file -e "${HEAD_SHA}^{commit}"; then | |
| enable_agent | |
| exit 0 | |
| fi | |
| changed_paths="$(mktemp)" | |
| trap 'rm -f "$changed_paths"' EXIT | |
| if ! git diff --no-renames --name-only -z "${BASE_SHA}${separator}${HEAD_SHA}" > "$changed_paths"; then | |
| enable_agent | |
| elif ! python3 scripts/ci/agent_change_detection.py < "$changed_paths" >> "$GITHUB_OUTPUT"; then | |
| enable_agent | |
| fi | |
| desktop: | |
| name: Agent (${{ matrix.os }}) | |
| needs: changes | |
| # A failed selector must never silently omit validation. | |
| if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') }} | |
| runs-on: ${{ matrix.os }} | |
| permissions: | |
| contents: read | |
| actions: read | |
| timeout-minutes: 120 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [blacksmith-8vcpu-ubuntu-2404, macos-26, windows-latest] | |
| env: | |
| DEPENDENCY_CACHE_JOB: Agent (${{ matrix.os }}) | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Install pinned Nix environment | |
| if: runner.os != 'Windows' | |
| uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 | |
| with: | |
| source-tag: v3.23.3 | |
| github-token: "" | |
| - name: Verify pinned Nix runtime | |
| if: runner.os != 'Windows' | |
| working-directory: . | |
| run: bash scripts/ci/verify-nix-runtime.sh | |
| - name: Install pinned Windows Rust | |
| if: runner.os == 'Windows' | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable action, exact toolchain below | |
| with: | |
| # Matches the component's frozen rust-overlay input. | |
| toolchain: 1.98.0 | |
| - name: Select Windows MSVC and SDK | |
| if: runner.os == 'Windows' | |
| uses: ilammy/msvc-dev-cmd@a102174a2b586eec2ea151a69e6fd14404a8ce7c # v1.13.0 | |
| with: | |
| arch: x64 | |
| - name: Select native Windows Perl for the static crypto build | |
| if: runner.os == 'Windows' | |
| run: | | |
| native_perl=/c/Strawberry/perl/bin/perl.exe | |
| if [ ! -x "$native_perl" ]; then | |
| echo 'Native Windows Perl is missing from the runner' >&2 | |
| exit 1 | |
| fi | |
| "$native_perl" -MLocale::Maketext::Simple -MIPC::Cmd -e 'die "OpenSSL requires native Windows Perl\n" unless $^O eq "MSWin32"; print "$^V\n"' | |
| # Pin the native executable across later Git Bash steps and Rust's | |
| # process resolver instead of depending on their PATH ordering. | |
| native_perl_windows="$(cygpath -m "$native_perl")" | |
| printf 'OPENSSL_SRC_PERL=%s\n' "$native_perl_windows" >> "$GITHUB_ENV" | |
| # openssl-src uses NASM if available and otherwise selects no-asm. | |
| - name: Pin and smoke-test the selected Windows linker | |
| if: runner.os == 'Windows' | |
| run: python3 ../../scripts/ci/agent_windows.py | |
| - name: Fingerprint the actual build environment | |
| id: environment | |
| run: | | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| python3 ../../scripts/ci/agent_cache_scope.py --profile ci --output "$RUNNER_TEMP/agent-environment.json" | |
| else | |
| nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_cache_scope.py --profile ci --output "$RUNNER_TEMP/agent-environment.json" | |
| fi | |
| - name: Restore validated master dependency archive | |
| id: dependency-archive | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| python3 ../../scripts/ci/agent_dependency_archive.py restore \ | |
| --environment "$RUNNER_TEMP/agent-environment.json" --target "target" \ | |
| --job-name "$DEPENDENCY_CACHE_JOB" --workflow agent-ci.yml \ | |
| --receipt "$RUNNER_TEMP/agent-dependency-receipt.json" | |
| - name: Cache Agent Rust dependencies | |
| id: rust-cache | |
| if: ${{ steps.dependency-archive.outputs.restored != 'true' }} | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: apps/maple-agent -> target | |
| add-rust-environment-hash-key: "true" | |
| # Dependency hashes belong in the action's exact suffix, not its restore prefix. | |
| key: maple-agent-ci-v2-${{ steps.environment.outputs.compatibility }} | |
| cmd-format: python3 "${{ github.workspace }}/scripts/ci/agent_cache_command.py" {0} | |
| save-if: "false" | |
| - name: Check whether Cargo targets were restored | |
| id: restored-target | |
| run: | | |
| python3 - <<'PYTHON' | |
| import os | |
| from pathlib import Path | |
| target = Path(os.environ["GITHUB_WORKSPACE"]) / "apps/maple-agent/target" | |
| present = any( | |
| any(fingerprint.iterdir()) and any( | |
| output.is_file() and output.stat().st_size > 0 | |
| for pattern in ("*.rlib", "*.rmeta") | |
| for output in (fingerprint.parent / "deps").glob(pattern) | |
| ) | |
| for fingerprint in target.rglob(".fingerprint") if fingerprint.is_dir() | |
| ) | |
| with Path(os.environ["GITHUB_OUTPUT"]).open("a") as output: | |
| output.write(f"present={str(present).lower()}\n") | |
| PYTHON | |
| - name: Restore compatible legacy Cargo targets on a cache miss | |
| if: ${{ steps.dependency-archive.outputs.restored != 'true' && steps.restored-target.outputs.present != 'true' }} | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: apps/maple-agent -> target | |
| # Read only within the existing actual compiler/native ABI prefix. | |
| # Native bootstrap is read-only; durable artifacts own cache writes. | |
| add-rust-environment-hash-key: "false" | |
| key: maple-agent-ci-v2-${{ steps.environment.outputs.legacy_compatibility }} | |
| cmd-format: python3 "${{ github.workspace }}/scripts/ci/agent_cache_command.py" {0} | |
| save-if: "false" | |
| - name: Record the current build environment after restore | |
| run: | | |
| mkdir -p target/ci-metrics | |
| cp "$RUNNER_TEMP/agent-environment.json" target/ci-metrics/environment.json | |
| - name: Verify Git metadata freshness on native workspace builds | |
| if: runner.os != 'Linux' | |
| run: | | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| python3 scripts/test_git_metadata.py | |
| else | |
| nix develop --no-update-lock-file . -c python3 scripts/test_git_metadata.py | |
| fi | |
| - name: Verify the canonical SDK and proxy dependency graph | |
| run: | | |
| metadata="$RUNNER_TEMP/agent-workspace-metadata.json" | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| cargo metadata --locked --format-version 1 > "$metadata" | |
| else | |
| # shellcheck disable=SC2016 # The child shell expands its own positional argument. | |
| nix develop --no-update-lock-file . -c bash -c 'cargo metadata --locked --format-version 1 > "$1"' -- "$metadata" | |
| fi | |
| python3 ../../scripts/ci/verify-agent-rust-deps.py "$metadata" | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| python3 scripts/verify-sqlcipher-build.py "$metadata" | |
| else | |
| nix develop --no-update-lock-file . -c python3 scripts/verify-sqlcipher-build.py "$metadata" | |
| fi | |
| - name: Format, lint, build, and test Linux feature matrix | |
| if: runner.os == 'Linux' | |
| run: nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_measure.py --phase linux-ci --target target -- just ci | |
| - name: Build macOS workspace and test targets | |
| if: runner.os == 'macOS' | |
| run: nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_measure.py --phase macos-build --target target -- cargo build --workspace --all-targets --locked --timings | |
| - name: Package and smoke macOS debug app | |
| if: runner.os == 'macOS' | |
| run: nix develop --no-update-lock-file . -c ./scripts/macos-debug-app.sh | |
| - name: Test macOS workspace | |
| if: runner.os == 'macOS' | |
| run: nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_measure.py --phase macos-test --target target -- cargo test --workspace --locked --timings | |
| - name: Build Windows workspace and test targets | |
| if: runner.os == 'Windows' | |
| run: python3 ../../scripts/ci/agent_measure.py --phase windows-build --target target -- cargo build --workspace --all-targets --locked --timings | |
| - name: Test Windows workspace | |
| if: runner.os == 'Windows' | |
| run: python3 ../../scripts/ci/agent_measure.py --phase windows-test --target target -- cargo test --workspace --locked --timings | |
| - name: Audit Agent dependencies | |
| if: runner.os == 'Linux' | |
| run: nix develop --no-update-lock-file . -c python3 scripts/audit-sqlcipher-dependencies.py | |
| - name: Regenerate the signed SQLCipher source | |
| if: runner.os == 'Linux' | |
| run: | | |
| metadata="$(mktemp)" | |
| trap 'rm -f "$metadata"' EXIT | |
| # shellcheck disable=SC2016 # The child shell expands its own positional argument. | |
| nix develop --no-update-lock-file . -c bash -c 'cargo metadata --locked --format-version 1 > "$1"' -- "$metadata" | |
| nix develop --no-update-lock-file . -c python3 dependencies/sqlcipher/verify_sqlcipher.py --metadata "$metadata" | |
| - name: Prepare trusted dependency archive after validation | |
| id: dependency-seed | |
| if: ${{ success() && github.repository == 'MaplePrivacyLabs/Maple' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} | |
| run: | | |
| python3 ../../scripts/ci/agent_dependency_archive.py pack \ | |
| --environment "$RUNNER_TEMP/agent-environment.json" --metadata "$RUNNER_TEMP/agent-workspace-metadata.json" \ | |
| --target "target" --job-name "$DEPENDENCY_CACHE_JOB" --workflow agent-ci.yml \ | |
| --output-dir "$RUNNER_TEMP/agent-dependency-seed" \ | |
| --receipt "$RUNNER_TEMP/agent-dependency-receipt.json" | |
| - name: Upload durable dependency archive | |
| if: ${{ success() && github.repository == 'MaplePrivacyLabs/Maple' && github.event_name == 'push' && github.ref == 'refs/heads/master' && steps.dependency-seed.outputs.needs-upload == 'true' }} | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: ${{ steps.dependency-seed.outputs.artifact-name }} | |
| path: | | |
| ${{ runner.temp }}/agent-dependency-seed/manifest.json | |
| ${{ runner.temp }}/agent-dependency-seed/dependencies.tar.gz | |
| if-no-files-found: error | |
| compression-level: 0 | |
| overwrite: true | |
| retention-days: 3 | |
| - name: Summarize phase timings and target size | |
| if: always() | |
| run: python3 ../../scripts/ci/agent_measure.py --summarize target | |
| - name: Upload bounded CI measurements | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: maple-agent-ci-metrics-${{ runner.os }}-${{ runner.arch }}-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: apps/maple-agent/target/ci-metrics/ | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| provider: | |
| name: Agent provider (${{ matrix.os }}) | |
| needs: changes | |
| if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') }} | |
| runs-on: ${{ matrix.os }} | |
| permissions: | |
| contents: read | |
| actions: read | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [blacksmith-8vcpu-ubuntu-2404, macos-26, windows-latest] | |
| env: | |
| DEPENDENCY_CACHE_JOB: Agent provider (${{ matrix.os }}) | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Install pinned Nix environment | |
| if: runner.os != 'Windows' | |
| uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 | |
| with: | |
| source-tag: v3.23.3 | |
| github-token: "" | |
| - name: Verify pinned Nix runtime | |
| if: runner.os != 'Windows' | |
| working-directory: . | |
| run: bash scripts/ci/verify-nix-runtime.sh | |
| - name: Install pinned Windows Rust | |
| if: runner.os == 'Windows' | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable action, exact toolchain below | |
| with: | |
| # Matches the component's frozen rust-overlay input. | |
| toolchain: 1.98.0 | |
| - name: Select Windows MSVC and SDK | |
| if: runner.os == 'Windows' | |
| uses: ilammy/msvc-dev-cmd@a102174a2b586eec2ea151a69e6fd14404a8ce7c # v1.13.0 | |
| with: | |
| arch: x64 | |
| - name: Select native Windows Perl for the static crypto build | |
| if: runner.os == 'Windows' | |
| run: | | |
| native_perl=/c/Strawberry/perl/bin/perl.exe | |
| if [ ! -x "$native_perl" ]; then | |
| echo 'Native Windows Perl is missing from the runner' >&2 | |
| exit 1 | |
| fi | |
| "$native_perl" -MLocale::Maketext::Simple -MIPC::Cmd -e 'die "OpenSSL requires native Windows Perl\n" unless $^O eq "MSWin32"; print "$^V\n"' | |
| # Pin the native executable across later Git Bash steps and Rust's | |
| # process resolver instead of depending on their PATH ordering. | |
| native_perl_windows="$(cygpath -m "$native_perl")" | |
| printf 'OPENSSL_SRC_PERL=%s\n' "$native_perl_windows" >> "$GITHUB_ENV" | |
| # openssl-src uses NASM if available and otherwise selects no-asm. | |
| - name: Pin and smoke-test the selected Windows linker | |
| if: runner.os == 'Windows' | |
| run: python3 ../../scripts/ci/agent_windows.py | |
| - name: Select isolated provider paths | |
| run: | | |
| printf 'PROVIDER_WORK=%s/rusqlite-provider\nPROVIDER_TARGET=%s/rusqlite-provider-target\n' "$RUNNER_TEMP" "$RUNNER_TEMP" >> "$GITHUB_ENV" | |
| - name: Prepare and verify the resolved upstream provider test graph | |
| id: provider | |
| run: | | |
| metadata="$RUNNER_TEMP/provider-workspace-metadata.json" | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| cargo metadata --locked --format-version 1 > "$metadata" | |
| python3 scripts/test-rusqlite-provider.py "$metadata" --phase prepare --work-dir "$PROVIDER_WORK" --target-dir "$PROVIDER_TARGET" --github-output "$GITHUB_OUTPUT" | |
| else | |
| # shellcheck disable=SC2016 | |
| nix develop --no-update-lock-file . -c bash -c 'cargo metadata --locked --format-version 1 > "$1"' -- "$metadata" | |
| nix develop --no-update-lock-file . -c python3 scripts/test-rusqlite-provider.py "$metadata" --phase prepare --work-dir "$PROVIDER_WORK" --target-dir "$PROVIDER_TARGET" --github-output "$GITHUB_OUTPUT" | |
| fi | |
| - name: Fingerprint the actual build environment | |
| id: environment | |
| run: | | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| python3 ../../scripts/ci/agent_cache_scope.py --profile provider --cargo-config-dir "$PROVIDER_WORK" --output target/ci-metrics/environment.json | |
| else | |
| nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_cache_scope.py --profile provider --cargo-config-dir "$PROVIDER_WORK" --output target/ci-metrics/environment.json | |
| fi | |
| - name: Restore validated master dependency archive | |
| id: dependency-archive | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PROVIDER_DEPENDENCY_FINGERPRINT: ${{ steps.provider.outputs.dependency-fingerprint }} | |
| run: | | |
| python3 ../../scripts/ci/agent_dependency_archive.py restore \ | |
| --environment "target/ci-metrics/environment.json" --target "$PROVIDER_TARGET" \ | |
| --job-name "$DEPENDENCY_CACHE_JOB" --workflow agent-ci.yml \ | |
| --receipt "$RUNNER_TEMP/agent-dependency-receipt.json" \ | |
| --dependency-fingerprint "$PROVIDER_DEPENDENCY_FINGERPRINT" | |
| - name: Restore only matching verified provider artifacts | |
| id: provider-cache | |
| if: ${{ steps.dependency-archive.outputs.restored != 'true' }} | |
| uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: ${{ env.PROVIDER_TARGET }} | |
| key: maple-agent-provider-v2-${{ steps.environment.outputs.legacy_compatibility }}-${{ steps.provider.outputs.dependency-fingerprint }} | |
| - name: Test released rusqlite against the static provider | |
| run: | | |
| metadata="$RUNNER_TEMP/provider-workspace-metadata.json" | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| python3 ../../scripts/ci/agent_measure.py --phase provider-test --target "$PROVIDER_TARGET" -- python3 scripts/test-rusqlite-provider.py "$metadata" --phase run --work-dir "$PROVIDER_WORK" --target-dir "$PROVIDER_TARGET" | |
| else | |
| nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_measure.py --phase provider-test --target "$PROVIDER_TARGET" -- python3 scripts/test-rusqlite-provider.py "$metadata" --phase run --work-dir "$PROVIDER_WORK" --target-dir "$PROVIDER_TARGET" | |
| fi | |
| - name: Prepare trusted dependency archive after validation | |
| id: dependency-seed | |
| if: ${{ success() && github.repository == 'MaplePrivacyLabs/Maple' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} | |
| env: | |
| PROVIDER_DEPENDENCY_FINGERPRINT: ${{ steps.provider.outputs.dependency-fingerprint }} | |
| run: | | |
| python3 ../../scripts/ci/agent_dependency_archive.py pack \ | |
| --environment "target/ci-metrics/environment.json" --metadata "$PROVIDER_WORK/maple-provider-metadata.json" \ | |
| --target "$PROVIDER_TARGET" --job-name "$DEPENDENCY_CACHE_JOB" --workflow agent-ci.yml \ | |
| --output-dir "$RUNNER_TEMP/agent-dependency-seed" \ | |
| --receipt "$RUNNER_TEMP/agent-dependency-receipt.json" \ | |
| --dependency-fingerprint "$PROVIDER_DEPENDENCY_FINGERPRINT" | |
| - name: Upload durable dependency archive | |
| if: ${{ success() && github.repository == 'MaplePrivacyLabs/Maple' && github.event_name == 'push' && github.ref == 'refs/heads/master' && steps.dependency-seed.outputs.needs-upload == 'true' }} | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: ${{ steps.dependency-seed.outputs.artifact-name }} | |
| path: | | |
| ${{ runner.temp }}/agent-dependency-seed/manifest.json | |
| ${{ runner.temp }}/agent-dependency-seed/dependencies.tar.gz | |
| if-no-files-found: error | |
| compression-level: 0 | |
| overwrite: true | |
| retention-days: 3 | |
| - name: Summarize phase timings and target size | |
| if: always() | |
| run: python3 ../../scripts/ci/agent_measure.py --summarize "$PROVIDER_TARGET" | |
| - name: Upload bounded CI measurements | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: maple-agent-provider-metrics-${{ runner.os }}-${{ runner.arch }}-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: apps/maple-agent/target/ci-metrics/ | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| linux-release: | |
| name: Agent Linux release binary | |
| needs: changes | |
| if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') }} | |
| runs-on: blacksmith-8vcpu-ubuntu-2404 | |
| permissions: | |
| contents: read | |
| actions: read | |
| timeout-minutes: 90 | |
| env: | |
| DEPENDENCY_CACHE_JOB: Agent Linux release binary | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Install pinned Nix environment | |
| if: runner.os != 'Windows' | |
| uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 | |
| with: | |
| source-tag: v3.23.3 | |
| github-token: "" | |
| - name: Verify pinned Nix runtime | |
| if: runner.os != 'Windows' | |
| working-directory: . | |
| run: bash scripts/ci/verify-nix-runtime.sh | |
| - name: Fingerprint the actual build environment | |
| id: environment | |
| run: | | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| python3 ../../scripts/ci/agent_cache_scope.py --profile release --output "$RUNNER_TEMP/agent-environment.json" | |
| else | |
| nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_cache_scope.py --profile release --output "$RUNNER_TEMP/agent-environment.json" | |
| fi | |
| - name: Restore validated master dependency archive | |
| id: dependency-archive | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| python3 ../../scripts/ci/agent_dependency_archive.py restore \ | |
| --environment "$RUNNER_TEMP/agent-environment.json" --target "target" \ | |
| --job-name "$DEPENDENCY_CACHE_JOB" --workflow agent-ci.yml \ | |
| --receipt "$RUNNER_TEMP/agent-dependency-receipt.json" | |
| - name: Cache Agent release dependencies | |
| id: rust-cache | |
| if: ${{ steps.dependency-archive.outputs.restored != 'true' }} | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: apps/maple-agent -> target | |
| add-rust-environment-hash-key: "true" | |
| # Dependency hashes belong in the action's exact suffix, not its restore prefix. | |
| key: maple-agent-release-v2-${{ steps.environment.outputs.compatibility }} | |
| cmd-format: python3 "${{ github.workspace }}/scripts/ci/agent_cache_command.py" {0} | |
| save-if: "false" | |
| - name: Check whether Cargo targets were restored | |
| id: restored-target | |
| run: | | |
| python3 - <<'PYTHON' | |
| import os | |
| from pathlib import Path | |
| target = Path(os.environ["GITHUB_WORKSPACE"]) / "apps/maple-agent/target" | |
| present = any( | |
| any(fingerprint.iterdir()) and any( | |
| output.is_file() and output.stat().st_size > 0 | |
| for pattern in ("*.rlib", "*.rmeta") | |
| for output in (fingerprint.parent / "deps").glob(pattern) | |
| ) | |
| for fingerprint in target.rglob(".fingerprint") if fingerprint.is_dir() | |
| ) | |
| with Path(os.environ["GITHUB_OUTPUT"]).open("a") as output: | |
| output.write(f"present={str(present).lower()}\n") | |
| PYTHON | |
| - name: Restore compatible legacy Cargo targets on a cache miss | |
| if: ${{ steps.dependency-archive.outputs.restored != 'true' && steps.restored-target.outputs.present != 'true' }} | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: apps/maple-agent -> target | |
| add-rust-environment-hash-key: "false" | |
| key: maple-agent-release-v2-${{ steps.environment.outputs.legacy_compatibility }} | |
| cmd-format: python3 "${{ github.workspace }}/scripts/ci/agent_cache_command.py" {0} | |
| save-if: "false" | |
| - name: Record the current build environment after restore | |
| run: | | |
| mkdir -p target/ci-metrics | |
| cp "$RUNNER_TEMP/agent-environment.json" target/ci-metrics/environment.json | |
| - name: Verify the canonical SDK and proxy dependency graph | |
| run: | | |
| metadata="$RUNNER_TEMP/agent-workspace-metadata.json" | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| cargo metadata --locked --format-version 1 > "$metadata" | |
| else | |
| # shellcheck disable=SC2016 # The child shell expands its own positional argument. | |
| nix develop --no-update-lock-file . -c bash -c 'cargo metadata --locked --format-version 1 > "$1"' -- "$metadata" | |
| fi | |
| python3 ../../scripts/ci/verify-agent-rust-deps.py "$metadata" | |
| if [ "$RUNNER_OS" = Windows ]; then | |
| python3 scripts/verify-sqlcipher-build.py "$metadata" | |
| else | |
| nix develop --no-update-lock-file . -c python3 scripts/verify-sqlcipher-build.py "$metadata" | |
| fi | |
| - name: Generate and validate Linux third-party notices | |
| if: runner.os == 'Linux' | |
| run: | | |
| nix develop --no-update-lock-file . -c python3 scripts/generate-third-party-notices.py | |
| nix develop --no-update-lock-file . -c python3 scripts/generate-third-party-notices.py --check | |
| - name: Build Linux release binary | |
| if: runner.os == 'Linux' | |
| run: nix develop --no-update-lock-file . -c python3 ../../scripts/ci/agent_measure.py --phase linux-release --target target -- cargo build --release -p maple-agent-app --locked --timings | |
| # This is an unsigned CI build, never a GitHub Release or an updater feed. | |
| - name: Upload Linux CI binary | |
| if: runner.os == 'Linux' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: maple-agent-linux-x86_64-ci-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: | | |
| apps/maple-agent/target/release/maple-agent | |
| apps/maple-agent/target/third-party-notices | |
| if-no-files-found: error | |
| retention-days: 5 | |
| - name: Prepare trusted dependency archive after validation | |
| id: dependency-seed | |
| if: ${{ success() && github.repository == 'MaplePrivacyLabs/Maple' && github.event_name == 'push' && github.ref == 'refs/heads/master' }} | |
| run: | | |
| python3 ../../scripts/ci/agent_dependency_archive.py pack \ | |
| --environment "$RUNNER_TEMP/agent-environment.json" --metadata "$RUNNER_TEMP/agent-workspace-metadata.json" \ | |
| --target "target" --job-name "$DEPENDENCY_CACHE_JOB" --workflow agent-ci.yml \ | |
| --output-dir "$RUNNER_TEMP/agent-dependency-seed" \ | |
| --receipt "$RUNNER_TEMP/agent-dependency-receipt.json" | |
| - name: Upload durable dependency archive | |
| if: ${{ success() && github.repository == 'MaplePrivacyLabs/Maple' && github.event_name == 'push' && github.ref == 'refs/heads/master' && steps.dependency-seed.outputs.needs-upload == 'true' }} | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: ${{ steps.dependency-seed.outputs.artifact-name }} | |
| path: | | |
| ${{ runner.temp }}/agent-dependency-seed/manifest.json | |
| ${{ runner.temp }}/agent-dependency-seed/dependencies.tar.gz | |
| if-no-files-found: error | |
| compression-level: 0 | |
| overwrite: true | |
| retention-days: 3 | |
| - name: Summarize phase timings and target size | |
| if: always() | |
| run: python3 ../../scripts/ci/agent_measure.py --summarize target | |
| - name: Upload bounded CI measurements | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: maple-agent-release-metrics-${{ runner.os }}-${{ runner.arch }}-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: apps/maple-agent/target/ci-metrics/ | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| required: | |
| name: Agent CI required jobs | |
| needs: [changes, desktop, provider, linux-release] | |
| if: ${{ always() && !cancelled() }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| defaults: | |
| run: | |
| working-directory: . | |
| steps: | |
| - name: Require every selected platform and independent check | |
| env: | |
| SELECTOR_RESULT: ${{ needs.changes.result }} | |
| AGENT_SELECTED: ${{ needs.changes.outputs.agent }} | |
| DESKTOP_RESULT: ${{ needs.desktop.result }} | |
| PROVIDER_RESULT: ${{ needs.provider.result }} | |
| RELEASE_RESULT: ${{ needs.linux-release.result }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$SELECTOR_RESULT" = success ] && [ "$AGENT_SELECTED" = false ]; then | |
| test "$DESKTOP_RESULT" = skipped | |
| test "$PROVIDER_RESULT" = skipped | |
| test "$RELEASE_RESULT" = skipped | |
| else | |
| test "$DESKTOP_RESULT" = success | |
| test "$PROVIDER_RESULT" = success | |
| test "$RELEASE_RESULT" = success | |
| fi |