From ed0f5e07b6fadbe0a874c32465eb25efa23fe84b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 30 May 2026 21:54:50 +0100 Subject: [PATCH 1/5] Use self-hosted native runners --- .github/workflows/claude.yml | 6 +++--- .github/workflows/extended-validation.yml | 8 ++++---- .github/workflows/lint.yml | 4 +--- .github/workflows/pr-fast-ci.yml | 8 ++++---- .github/workflows/release.yml | 2 +- .github/workflows/rust-ci.yml | 4 +--- AGENTS.md | 2 +- docs/bootstrap/onboarding.md | 8 ++++---- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 7c75a81..f6cd94a 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -32,7 +32,7 @@ jobs: contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association)) - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 30 permissions: contents: write @@ -69,8 +69,8 @@ jobs: Use CLAUDE.md and docs/bootstrap/onboarding.md as repo policy context. Keep required PR status checks aligned with CI Gate. Preserve the split fast and extended validation model. - Shell-safe jobs may use `[self-hosted, synology, shell-only, public]`. - Docker, service-container, browser, and `container:` jobs stay on GitHub-hosted runners. + Shell-safe jobs must use `[self-hosted, linux, shell-only, public]`. + Docker, service-container, browser, and `container:` jobs require a dedicated self-hosted pool with matching capability labels. Prefer the smallest safe change and add tests for behavior changes. MANUAL TASK: ${{ github.event.inputs.prompt }} diff --git a/.github/workflows/extended-validation.yml b/.github/workflows/extended-validation.yml index 3bb391f..a5c2852 100644 --- a/.github/workflows/extended-validation.yml +++ b/.github/workflows/extended-validation.yml @@ -25,7 +25,7 @@ defaults: jobs: changes: name: Detect Extended Validation Scope - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] outputs: app: ${{ steps.preset.outputs.app || steps.filter.outputs.app || 'false' }} ci: ${{ steps.preset.outputs.ci || steps.filter.outputs.ci || 'false' }} @@ -77,7 +77,7 @@ jobs: fast-checks: name: Fast Checks - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 15 needs: changes if: needs.changes.outputs.app == 'true' || needs.changes.outputs.ci == 'true' @@ -111,7 +111,7 @@ jobs: validate-secrets: name: Validate Secrets - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 10 steps: - uses: actions/checkout@v4 @@ -120,7 +120,7 @@ jobs: extended-validation-gate: name: Extended Validation Gate - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] if: always() needs: - changes diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 39cbcbb..25fb69c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,9 +5,7 @@ on: jobs: lint: - # Hosted fallback: the Synology shell-only pool does not provide a C toolchain, - # and apt-based provisioning is blocked by container permissions. - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/pr-fast-ci.yml b/.github/workflows/pr-fast-ci.yml index 749539c..c835e11 100644 --- a/.github/workflows/pr-fast-ci.yml +++ b/.github/workflows/pr-fast-ci.yml @@ -23,7 +23,7 @@ defaults: jobs: changes: name: Detect Relevant Changes - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] outputs: app: ${{ steps.filter.outputs.app }} ci: ${{ steps.filter.outputs.ci }} @@ -58,7 +58,7 @@ jobs: fast-checks: name: Fast Checks - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 15 needs: changes if: >- @@ -100,7 +100,7 @@ jobs: validate-secrets: name: Validate Secrets - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 10 if: github.event.pull_request.draft == false steps: @@ -112,7 +112,7 @@ jobs: ci-gate: name: CI Gate - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] if: always() needs: - changes diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 112000d..d16297f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -244,7 +244,7 @@ jobs: name: Publish Homebrew tap PR needs: release if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] continue-on-error: true env: TAP_REPOSITORY: ${{ vars.HOMEBREW_TAP_REPOSITORY || 'OMT-Global/homebrew-apw' }} diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 025b354..5d9519d 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -7,9 +7,7 @@ permissions: jobs: test: - # Hosted fallback: the Synology shell-only pool does not provide a C toolchain, - # and apt-based provisioning is blocked by container permissions. - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] steps: - name: Setup repo diff --git a/AGENTS.md b/AGENTS.md index 8e0530a..e1d5fd9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ - Always work on a feature branch. Hooks block commits to `main` and `master`; enable them with `git config core.hooksPath .githooks`. - Stack baseline: Generic polyglot. - CI baseline: fast PR checks stay cheap and shell-safe; extended validation runs on `main`, nightly, or manual dispatch. -- Self-hosted runner policy: shell-safe jobs may use `[self-hosted, synology, shell-only, public]`; anything needing Docker, service containers, browser infra, or `container:` must stay on GitHub-hosted runners. +- Self-hosted runner policy: shell-safe jobs must use `[self-hosted, linux, shell-only, public]`; anything needing Docker, service containers, browser infra, or `container:` must use a dedicated self-hosted runner pool with the matching capability labels. - Add or update tests for every interactive, branching, or operator-facing behavior change. - Never commit real secrets, runtime auth, or machine-local env files. Use templates and GitHub environments instead. diff --git a/docs/bootstrap/onboarding.md b/docs/bootstrap/onboarding.md index 74594d9..9fd87b7 100644 --- a/docs/bootstrap/onboarding.md +++ b/docs/bootstrap/onboarding.md @@ -37,10 +37,10 @@ ## Runner Policy - Run `apw --json doctor` first on a new local checkout or self-hosted runner to confirm the Rust, Xcode, secret-scan, signing, and runner-environment diagnostics before extended validation. - - Shell-safe jobs may use `[self-hosted, synology, shell-only, public]`. - - Docker, service-container, browser, and `container:` workloads stay on GitHub-hosted runners. + - Shell-safe jobs must use `[self-hosted, linux, shell-only, public]`. + - Docker, service-container, browser, and `container:` workloads require a dedicated self-hosted runner pool with matching capability labels. - Keep PR checks cheap. Add heavy validation to `scripts/ci/run-extended-validation.sh` instead of the PR lane. - - APW extended validation requires both Rust (`cargo`) and the macOS Swift toolchain, so the `extended-checks` job must run on the org macOS self-hosted pool (`[self-hosted, private, macOS, ARM64, xcode]`) rather than the Synology shell-only pool. + - APW extended validation requires both Rust (`cargo`) and the macOS Swift toolchain, so the `extended-checks` job must run on the org macOS self-hosted pool (`[self-hosted, private, macOS, ARM64, xcode]`) rather than the Linux shell-only pool. - Rust builds OpenSSL through the `openssl` crate's vendored feature, so the macOS runner needs source-build tools (`cc`, `make`, and `perl`) but does not require Homebrew, pkg-config, or a system OpenSSL prefix. - Extended validation runs `scripts/ci/run-native-app-preflight.sh`, which exercises the Swift package through `xcodebuild`, builds `APW.app`, verifies codesign, and confirms the associated-domain entitlement is embedded. @@ -87,5 +87,5 @@ - First-party Claude web sessions should use `bash scripts/claude-cloud/setup.sh` in `claude.ai/code`. - Interactive Claude work is prepared through `.devcontainer/devcontainer.json`. -- GitHub-hosted Claude automation lives in `.github/workflows/claude.yml` and is intentionally separate from the required PR checks. +- Claude automation lives in `.github/workflows/claude.yml` on the shared self-hosted Linux pool and is intentionally separate from the required PR checks. - Finish GitHub-side auth by running `/install-github-app` in Claude Code or adding `ANTHROPIC_API_KEY` as a repo secret. From 0dacbd2ddcca3868a5a0b2b0c533cfffa2f39cf9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 31 May 2026 00:28:08 +0100 Subject: [PATCH 2/5] Align doctor runner guidance with Linux pool Update the native app doctor runner-label remediation to reference the new self-hosted Linux shell-only runner contract and add regression coverage so the retired Synology label does not reappear. --- rust/src/native_app.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/rust/src/native_app.rs b/rust/src/native_app.rs index a50ca61..fac885c 100644 --- a/rust/src/native_app.rs +++ b/rust/src/native_app.rs @@ -492,7 +492,7 @@ fn ci_runner_environment_check() -> Value { "WARN", "runner_labels", "Not running in GitHub Actions; runner labels cannot be verified locally", - "In CI, confirm shell-safe jobs use [self-hosted, synology, shell-only, public] and extended macOS validation uses [self-hosted, private, macOS, ARM64, xcode].", + "In CI, confirm shell-safe jobs use [self-hosted, linux, shell-only, public] and extended macOS validation uses [self-hosted, private, macOS, ARM64, xcode].", ) } @@ -2006,12 +2006,31 @@ mod tests { result } + fn with_ci_env(value: Option<&str>, run: F) -> R + where + F: FnOnce() -> R, + { + let previous_value = env::var("CI").ok(); + if let Some(value) = value { + env::set_var("CI", value); + } else { + env::remove_var("CI"); + } + let result = run(); + if let Some(value) = previous_value { + env::set_var("CI", value); + } else { + env::remove_var("CI"); + } + result + } + #[test] #[serial] fn doctor_does_not_create_default_credentials_file_without_demo_gate() { with_temp_home(|| { with_demo_env(None, || { - let payload = native_app_doctor().unwrap(); + let payload = with_ci_env(None, || native_app_doctor().unwrap()); assert_eq!( payload["frameworks"]["authenticationServicesLinked"], json!(true) @@ -2029,6 +2048,19 @@ mod tests { "missing diagnostic {id}: {diagnostics:#?}" ); } + let runner_labels = diagnostics + .iter() + .find(|entry| entry["id"] == json!("runner_labels")) + .expect("runner labels diagnostic"); + let remediation = runner_labels["hint"].as_str().unwrap_or(""); + assert!( + remediation.contains("[self-hosted, linux, shell-only, public]"), + "runner remediation should document the Linux shell-only pool: {remediation}" + ); + assert!( + !remediation.contains("synology"), + "runner remediation should not mention the retired Synology pool: {remediation}" + ); assert!(diagnostics.iter().all(|entry| entry["status"] .as_str() .map(|status| ["OK", "WARN", "FAIL"].contains(&status)) From ff63e5f6c8e14dd28d696c53e2e964aacfb06683 Mon Sep 17 00:00:00 2001 From: Pheidon Date: Mon, 1 Jun 2026 08:00:14 +0000 Subject: [PATCH 3/5] ci: keep compile and secret jobs on private runners --- .github/workflows/claude.yml | 5 ++++- .github/workflows/lint.yml | 4 +++- .github/workflows/rust-ci.yml | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index f6cd94a..594bfbb 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -32,7 +32,9 @@ jobs: contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.review.author_association)) - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + # Claude has write permissions and reads ANTHROPIC_API_KEY, so keep it on a + # trusted private runner instead of the public shell-only fleet. + runs-on: ['self-hosted', 'private', 'macOS', 'ARM64', 'xcode'] timeout-minutes: 30 permissions: contents: write @@ -70,6 +72,7 @@ jobs: Keep required PR status checks aligned with CI Gate. Preserve the split fast and extended validation model. Shell-safe jobs must use `[self-hosted, linux, shell-only, public]`. + Secret-bearing automation must stay on a trusted private runner. Docker, service-container, browser, and `container:` jobs require a dedicated self-hosted pool with matching capability labels. Prefer the smallest safe change and add tests for behavior changes. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 25fb69c..2065555 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,7 +5,9 @@ on: jobs: lint: - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + # Rust build scripts need a C linker; the public shell-only Linux pool is + # intentionally minimal and cannot satisfy that compile-time dependency. + runs-on: ['self-hosted', 'private', 'macOS', 'ARM64', 'xcode'] steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 5d9519d..2c5a28a 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -7,7 +7,9 @@ permissions: jobs: test: - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + # Rust build scripts need a C linker; the public shell-only Linux pool is + # intentionally minimal and cannot satisfy that compile-time dependency. + runs-on: ['self-hosted', 'private', 'macOS', 'ARM64', 'xcode'] steps: - name: Setup repo From f84e0e02c2f1932098efb25993650583cb1b7701 Mon Sep 17 00:00:00 2001 From: Pheidon Date: Mon, 1 Jun 2026 08:07:38 +0000 Subject: [PATCH 4/5] ci: run Rust checks in containerized self-hosted pool --- .github/workflows/lint.yml | 7 ++++--- .github/workflows/rust-ci.yml | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2065555..4cfd00a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,9 +5,10 @@ on: jobs: lint: - # Rust build scripts need a C linker; the public shell-only Linux pool is - # intentionally minimal and cannot satisfy that compile-time dependency. - runs-on: ['self-hosted', 'private', 'macOS', 'ARM64', 'xcode'] + # Rust build scripts need a C linker; run inside the Rust container on the + # Docker-capable self-hosted Linux pool instead of the minimal shell-only host. + runs-on: ['self-hosted', 'linux', 'docker-capable', 'public'] + container: rust:1-bookworm steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 2c5a28a..6bfe833 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -7,9 +7,10 @@ permissions: jobs: test: - # Rust build scripts need a C linker; the public shell-only Linux pool is - # intentionally minimal and cannot satisfy that compile-time dependency. - runs-on: ['self-hosted', 'private', 'macOS', 'ARM64', 'xcode'] + # Rust build scripts need a C linker; run inside the Rust container on the + # Docker-capable self-hosted Linux pool instead of the minimal shell-only host. + runs-on: ['self-hosted', 'linux', 'docker-capable', 'public'] + container: rust:1-bookworm steps: - name: Setup repo From 6370cce298aea13a823946efb57ae7367b05dd99 Mon Sep 17 00:00:00 2001 From: Pheidon Date: Mon, 1 Jun 2026 08:11:28 +0000 Subject: [PATCH 5/5] ci: use hosted fallback for Rust toolchain checks --- .github/workflows/lint.yml | 8 ++++---- .github/workflows/rust-ci.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4cfd00a..79ecd6d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,10 +5,10 @@ on: jobs: lint: - # Rust build scripts need a C linker; run inside the Rust container on the - # Docker-capable self-hosted Linux pool instead of the minimal shell-only host. - runs-on: ['self-hosted', 'linux', 'docker-capable', 'public'] - container: rust:1-bookworm + # Hosted fallback: the self-hosted shell-only Linux pool does not provide a + # C toolchain, and the Docker-capable pool currently has an incompatible + # Docker client for Actions container jobs. + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 6bfe833..6637a8d 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -7,10 +7,10 @@ permissions: jobs: test: - # Rust build scripts need a C linker; run inside the Rust container on the - # Docker-capable self-hosted Linux pool instead of the minimal shell-only host. - runs-on: ['self-hosted', 'linux', 'docker-capable', 'public'] - container: rust:1-bookworm + # Hosted fallback: the self-hosted shell-only Linux pool does not provide a + # C toolchain, and the Docker-capable pool currently has an incompatible + # Docker client for Actions container jobs. + runs-on: ubuntu-latest steps: - name: Setup repo