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
9 changes: 6 additions & 3 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ubuntu-latest
# 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
Expand Down Expand Up @@ -69,8 +71,9 @@ 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]`.
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.

MANUAL TASK: ${{ github.event.inputs.prompt }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/extended-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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' }}
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ 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.
# 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:
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pr-fast-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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: >-
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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' }}
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ 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.
# 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:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
8 changes: 4 additions & 4 deletions docs/bootstrap/onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]`.
Comment thread
jmcte marked this conversation as resolved.
- 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.

Expand Down Expand Up @@ -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.
36 changes: 34 additions & 2 deletions rust/src/native_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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].",
)
}

Expand Down Expand Up @@ -2006,12 +2006,31 @@ mod tests {
result
}

fn with_ci_env<F, R>(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)
Expand All @@ -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))
Expand Down
Loading