diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml deleted file mode 100644 index 62b8101..0000000 --- a/.github/actionlint.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Self-hosted runner labels (lab ARC scalesets, see lab ADR-0001). -# actionlint (run by CodeRabbit and available locally) treats unknown -# runs-on labels as errors without this registry. -self-hosted-runner: - labels: - - cachekit-lean - - cachekit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d11f2e0..1d6464d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,27 +9,57 @@ on: permissions: contents: read -env: - # Rustup's temp dir must be on the same filesystem as RUSTUP_HOME to avoid - # cross-device link errors on container overlay filesystems. - RUSTUP_HOME: /tmp/rustup - CARGO_HOME: /tmp/cargo - # The cachekit-lean ARC runner has a hard 6Gi memory cgroup (lab ADR-0001). - # cargo defaults -j to the visible core count (~24 via the pod's CPU limit), - # and a cold `cargo test` build of this 246-crate async/TLS/crypto graph with - # full test-profile debuginfo peaks above 6Gi at that fan-out — the kernel - # OOM-kills the linker and the runner "loses communication" (issue #25, - # confirmed by a 6Gi-cgroup repro). Cap parallel compile/link jobs so peak - # RSS stays well under the cap on the cache-less lean pod. - CARGO_BUILD_JOBS: "4" - jobs: + guard-hosted-runners: + name: guard — no self-hosted runner labels + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Fail if any workflow still targets a self-hosted runner + # Drift protection for maintainers only, NOT a fork-PR control — a + # fork PR runs its own copy of this workflow file, so this check + # cannot stop an attacker's fork from restoring `runs-on: cachekit`. + # The control that actually blocks that lives in repository and org + # runner settings, outside this file. This job only stops a + # maintainer from accidentally merging the drift back in. + # + # Parsed with yq, not a line-oriented grep: `runs-on:` (and + # matrix.os/matrix.runner) are equally valid as a scalar, a + # flow-list (`[self-hosted, cachekit]`), or a block-list (`- cachekit` + # on its own line) — a grep anchored on the `runs-on:` line only + # catches the first form and silently misses the other two. + run: | + bad=0 + for f in .github/workflows/*.yml; do + # yq's own parse failure must not be swallowed by the grep on the + # next line: GitHub Actions runs bash steps with `-e` but not + # `-o pipefail`, so `yq ... | grep ... || true` would mask a + # broken/unparseable workflow as "no hits found" instead of + # erroring. Capture yq's output and exit status separately. + if ! values=$(yq eval '(.jobs[].runs-on, .jobs[].strategy.matrix.os[]?, .jobs[].strategy.matrix.runner[]?)' "$f"); then + echo "::error::failed to parse $f with yq" + bad=1 + continue + fi + hits=$(echo "$values" | grep -oE '\b(cachekit(-lean)?|self-hosted)\b' || true) + if [ -n "$hits" ]; then + echo "::error::$f still targets a self-hosted runner label: $(echo "$hits" | tr '\n' ' ')" + bad=1 + fi + done + if [ "$bad" -eq 1 ]; then + exit 1 + fi + test: name: ${{ matrix.rust }} - runs-on: cachekit-lean - # Backstop: a wedged/killed runner otherwise hangs ~10min until GitHub's - # heartbeat reckoning (issue #25). Fail fast — a healthy cold -j4 build of - # this tree finishes well inside this window. + runs-on: ubuntu-latest + # Fail fast rather than waiting out a hung build — a healthy cold build + # of this tree finishes well inside this window on a hosted runner. timeout-minutes: 20 continue-on-error: ${{ matrix.rust == 'beta' }} strategy: @@ -44,6 +74,10 @@ jobs: rustup toolchain install ${{ matrix.rust }} --profile minimal --component rustfmt,clippy rustup default ${{ matrix.rust }} + - uses: Swatinem/rust-cache@63fed3e2fecf6f7b51dc6f043341b79ef82a9ae7 # v2.9.2 + with: + key: ${{ matrix.rust }} + - name: Check formatting if: matrix.rust != '1.85' run: cargo fmt --all -- --check @@ -57,13 +91,12 @@ jobs: redis-lock: name: redis lock integration - # `services:` containers require Docker. cachekit-lean is a no-dind - # scaleset (lab ADR-0001), so a redis service can only run on the - # with-dind `cachekit` scaleset. Without a live redis the lock - # acquire/release path has zero automated coverage — the + # GitHub-hosted runners have Docker preinstalled, so `services:` works + # unchanged here with no extra Docker setup. Without a live redis the + # lock acquire/release path has zero automated coverage — the # redis_lock_live_semantics test skips when CACHEKIT_TEST_REDIS_URL is # unset (LAB-426: the AC requires locking exercised against a container). - runs-on: cachekit + runs-on: ubuntu-latest timeout-minutes: 20 services: redis: @@ -80,8 +113,8 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - # This job never pushes; don't leave the token on a self-hosted - # runner workspace (zizmor: artipacked). + # This job never pushes; don't leave the token in the git config + # (zizmor: artipacked). persist-credentials: false - name: Install Rust toolchain @@ -89,12 +122,13 @@ jobs: rustup toolchain install stable --profile minimal rustup default stable + - uses: Swatinem/rust-cache@63fed3e2fecf6f7b51dc6f043341b79ef82a9ae7 # v2.9.2 + - name: Run redis lock integration test env: - # The job runs directly on the runner, which shares the pod network - # namespace with the dind sidecar, so the mapped service port is - # reachable on localhost. redis-only, no default features — leanest - # build for the memory-tight (5Gi) with-dind pod. + # The job container shares a network namespace with the redis + # service container, so the mapped port is reachable on localhost. + # redis-only, no default features — leanest build. CACHEKIT_TEST_REDIS_URL: redis://localhost:6379 run: | cargo test --no-default-features --features redis --test redis_tests -- --nocapture 2>&1 | tee redis-test.log @@ -108,12 +142,12 @@ jobs: memcached: name: memcached integration - # Same reasoning as redis-lock: `services:` needs Docker, so this runs on - # the with-dind `cachekit` scaleset. Without a live memcached the + # Same reasoning as redis-lock: hosted runners have Docker built in, so + # `services:` needs no extra Docker setup. Without a live memcached the # get/set/delete/ttl path has zero automated coverage — the # memcached_live_semantics test skips when CACHEKIT_TEST_MEMCACHED_URL is # unset (LAB-429: the AC requires memcached exercised against a container). - runs-on: cachekit + runs-on: ubuntu-latest timeout-minutes: 20 services: memcached: @@ -130,8 +164,8 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - # This job never pushes; don't leave the token on a self-hosted - # runner workspace (zizmor: artipacked). + # This job never pushes; don't leave the token in the git config + # (zizmor: artipacked). persist-credentials: false - name: Install Rust toolchain @@ -139,12 +173,13 @@ jobs: rustup toolchain install stable --profile minimal rustup default stable + - uses: Swatinem/rust-cache@63fed3e2fecf6f7b51dc6f043341b79ef82a9ae7 # v2.9.2 + - name: Run memcached integration test env: - # The job runs directly on the runner, which shares the pod network - # namespace with the dind sidecar, so the mapped service port is - # reachable on localhost. memcached-only, no default features — - # leanest build for the memory-tight (5Gi) with-dind pod. + # The job container shares a network namespace with the memcached + # service container, so the mapped port is reachable on localhost. + # memcached-only, no default features — leanest build. CACHEKIT_TEST_MEMCACHED_URL: tcp://localhost:11211 run: | cargo test --no-default-features --features memcached --test memcached_tests -- --nocapture 2>&1 | tee memcached-test.log @@ -158,7 +193,7 @@ jobs: wasm: name: wasm32 check + runtime tests - runs-on: cachekit-lean + runs-on: ubuntu-latest timeout-minutes: 15 env: # Must equal the wasm-bindgen version in Cargo.lock — the test runner @@ -176,6 +211,8 @@ jobs: rustup default stable rustup target add wasm32-unknown-unknown + - uses: Swatinem/rust-cache@63fed3e2fecf6f7b51dc6f043341b79ef82a9ae7 # v2.9.2 + - name: Check wasm32 build run: cargo check -p cachekit-rs --target wasm32-unknown-unknown --features workers,encryption --no-default-features diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1321ec..d49aace 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,15 +77,13 @@ jobs: # stops a feature branch from driving a publish) — the required tag input # alone decides what content ships. if: ${{ needs.release-please.outputs.release_created == 'true' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') }} - runs-on: cachekit-lean + runs-on: ubuntu-latest timeout-minutes: 30 - env: - RUSTUP_HOME: /tmp/rustup - CARGO_HOME: /tmp/cargo - # Same 6Gi cgroup OOM constraint as CI (issue #25): cap parallel jobs so - # the cold pre-publish build doesn't OOM-kill the linker on cachekit-lean. - CARGO_BUILD_JOBS: "4" steps: + # Deliberately no Swatinem/rust-cache here, unlike ci.yml — this job + # holds CARGO_REGISTRY_TOKEN and only runs on a release (a handful of + # times a year), so a third-party action buys negligible build-time + # savings against a real increase in that secret's trust surface. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: # On dispatch, build and publish the tag's content — never main HEAD. diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index da24f7e..cafb344 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -26,12 +26,6 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: - # Same rationale as ci.yml: rustup's temp dir must share a filesystem with - # RUSTUP_HOME, and -j must be capped or cargo reads the node's core count - # instead of the 6Gi pod cgroup and OOM-kills the linker (lab ADR-0001). - RUSTUP_HOME: /tmp/rustup - CARGO_HOME: /tmp/cargo - CARGO_BUILD_JOBS: "4" # Pinned so the gate cannot change meaning without a diff. Renovate bumps # these like any other pin; the cache key below is derived from them. CARGO_DENY_VERSION: "0.19.4" @@ -42,10 +36,7 @@ env: jobs: supply-chain: - # cachekit-lean: neither tool needs Docker — cargo-deny resolves `cargo - # metadata`, cargo-audit reads Cargo.lock — so there is no reason to occupy - # the scarcer with-dind scaleset. - runs-on: cachekit-lean + runs-on: ubuntu-latest timeout-minutes: 20 steps: # Checkout is deliberately LAST. cargo honours a `.cargo/config.toml` @@ -90,8 +81,8 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - # This job never pushes; don't leave the token on a self-hosted - # runner workspace (zizmor: artipacked). + # This job never pushes; don't leave the token in the git config + # (zizmor: artipacked). persist-credentials: false - name: cargo deny (advisories + bans + licenses + sources)