From aa548076fba4c122de50f8821e880e222fedf5fb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:06:57 +0900 Subject: [PATCH 1/6] test(ci): require explicit hosted runner image --- tests/workflow_runner_contract.rs | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/workflow_runner_contract.rs diff --git a/tests/workflow_runner_contract.rs b/tests/workflow_runner_contract.rs new file mode 100644 index 00000000..f63eee0a --- /dev/null +++ b/tests/workflow_runner_contract.rs @@ -0,0 +1,39 @@ +//! Repository contract for deterministic GitHub-hosted runner selection. +//! +//! Wardnet's required pull-request workflows must not depend on GitHub's floating +//! `ubuntu-latest` alias. A floating image can change independently of the +//! repository and, during hosted-runner transitions, can leave exact-head jobs +//! queued before checkout. Pinning the Ubuntu image makes runner acquisition a +//! reviewed repository change while preserving GitHub-hosted execution. + +use std::fs; +use std::path::Path; + +const PINNED_UBUNTU_RUNNER: &str = "ubuntu-24.04"; +const FLOATING_UBUNTU_RUNNER: &str = "ubuntu-latest"; + +const RUNNER_BACKED_WORKFLOWS: &[&str] = &[ + ".github/workflows/ci.yml", + ".github/workflows/fuzz.yml", + ".github/workflows/scorecard-analysis.yml", +]; + +#[test] +fn runner_backed_workflows_pin_the_hosted_ubuntu_image() { + let repository = Path::new(env!("CARGO_MANIFEST_DIR")); + + for relative in RUNNER_BACKED_WORKFLOWS { + let path = repository.join(relative); + let workflow = fs::read_to_string(&path) + .unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display())); + + assert!( + !workflow.contains(FLOATING_UBUNTU_RUNNER), + "{relative} must not use the floating {FLOATING_UBUNTU_RUNNER} runner alias" + ); + assert!( + workflow.contains(&format!("runs-on: {PINNED_UBUNTU_RUNNER}")), + "{relative} must pin runner-backed jobs to {PINNED_UBUNTU_RUNNER}" + ); + } +} From 2770b57abebacae00b8624ba4213cdc032d9a9c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:07:11 +0900 Subject: [PATCH 2/6] fix(ci): pin hosted Ubuntu runner --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df092755..9409a898 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ permissions: jobs: rust: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable From 33ebae0208382f7e6582bd88188050d42b5ddbcf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:07:28 +0900 Subject: [PATCH 3/6] fix(ci): pin fuzz runner image --- .github/workflows/fuzz.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index ebcb3ce9..b2a5366d 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -23,7 +23,7 @@ concurrency: jobs: fuzz: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: From 2d41c4079f9a4465c3142a0aa2dd5895cb11f793 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 02:07:41 +0900 Subject: [PATCH 4/6] fix(ci): pin scorecard runner image --- .github/workflows/scorecard-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard-analysis.yml b/.github/workflows/scorecard-analysis.yml index dfa64206..2d147bee 100644 --- a/.github/workflows/scorecard-analysis.yml +++ b/.github/workflows/scorecard-analysis.yml @@ -13,7 +13,7 @@ permissions: jobs: analysis: name: Scorecard Analysis - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read security-events: write From 9a159dde773a970166e1c38619fc2094d2273f79 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 04:04:15 +0900 Subject: [PATCH 5/6] test(ci): validate every workflow runner declaration --- tests/workflow_runner_contract.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/workflow_runner_contract.rs b/tests/workflow_runner_contract.rs index f63eee0a..f8a626ee 100644 --- a/tests/workflow_runner_contract.rs +++ b/tests/workflow_runner_contract.rs @@ -31,9 +31,21 @@ fn runner_backed_workflows_pin_the_hosted_ubuntu_image() { !workflow.contains(FLOATING_UBUNTU_RUNNER), "{relative} must not use the floating {FLOATING_UBUNTU_RUNNER} runner alias" ); + + let runners = workflow + .lines() + .filter_map(|line| line.trim().strip_prefix("runs-on:")) + .map(str::trim) + .collect::>(); + assert!( + !runners.is_empty(), + "{relative} must define at least one runs-on value" + ); assert!( - workflow.contains(&format!("runs-on: {PINNED_UBUNTU_RUNNER}")), - "{relative} must pin runner-backed jobs to {PINNED_UBUNTU_RUNNER}" + runners + .iter() + .all(|runner| *runner == PINNED_UBUNTU_RUNNER), + "{relative} must use {PINNED_UBUNTU_RUNNER} for every runs-on value; found {runners:?}" ); } } From b663f9d200e5f385c7dd067d074940a02836c68e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 04:33:50 +0900 Subject: [PATCH 6/6] fix(ci): format runner contract test --- tests/workflow_runner_contract.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/workflow_runner_contract.rs b/tests/workflow_runner_contract.rs index f8a626ee..ff42bad4 100644 --- a/tests/workflow_runner_contract.rs +++ b/tests/workflow_runner_contract.rs @@ -42,9 +42,7 @@ fn runner_backed_workflows_pin_the_hosted_ubuntu_image() { "{relative} must define at least one runs-on value" ); assert!( - runners - .iter() - .all(|runner| *runner == PINNED_UBUNTU_RUNNER), + runners.iter().all(|runner| *runner == PINNED_UBUNTU_RUNNER), "{relative} must use {PINNED_UBUNTU_RUNNER} for every runs-on value; found {runners:?}" ); }