From 5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a Mon Sep 17 00:00:00 2001 From: Zach Vorhies Date: Sun, 4 Oct 2026 20:46:13 -0700 Subject: [PATCH 1/4] ci(cache): add a ci.toml declaring FastLED/fbuild's cache ceiling fbuild had no ci.toml, so nothing bounded its Actions cache: 43 entries totalling 8.73 GiB, 87% of GitHub's 10 GiB cap, with no declared family and no janitor budget. This adds a cache-only policy. Every max is at or above the largest entry measured live on 2026-10-05, and every max x cardinality product is at or above that family's measured live total, so ci-lint cache janitor's LRU trim cannot truncate a live cache. Declared families (measured): compile/build-cache 19 entries 7883 MB, dylint 1 entry 618 MB, registry 2 entries 503 MB, solo-toolchain 2 entries 359 MB, soldr-mini 1 entry 11 MB. Static CACHE-004: worst 9751756800 B <= budget 10200547328 B (9.5GB). Not declared: setup-soldr's thin target cache (18 entries, 3.7 KB total) -- ci-lint has no via value for that prefix, and it costs no budget. [cache.promote] mode = "off": nearest-ancestor promotion is not released. No [local.gate.*] keys: fbuild ships no local-gate.toml and no declared local gate, and a partial [local.gate] would make ci-lint local-gate demand a complete one. Local-Gate: v1 tree=0f34bcfab2230240f3f0bea57a37d1cd552b96b4 secs=292 lanes=linux-minimal:reused@6db8421f7538,dylint:run Ci-Attestation: {"at":1791194816,"gate":"general/all/ubuntu-ci-guards","host":"linux-x86_64","key":"6db8421f75388ebdf1fad1cac3929ef3a14584cd618b30874dfd4d371608fbbd","lane":"linux-minimal","parents":["a03aaa5ffaf82b5cdc9f951595a5f8fe2d7f5606"],"secs":null,"stamp":"719b19ccb7f88fae687982edc661ed05","tree":"0f34bcfab2230240f3f0bea57a37d1cd552b96b4","v":1,"via":"reused"} Ci-Attestation: {"at":1791194816,"gate":"rust/x86_64-unknown-linux-gnu/workspace-clippy","host":"linux-x86_64","key":"6db8421f75388ebdf1fad1cac3929ef3a14584cd618b30874dfd4d371608fbbd","lane":"linux-minimal","parents":["a03aaa5ffaf82b5cdc9f951595a5f8fe2d7f5606"],"secs":null,"stamp":"ffacf13594914b7b27e12ff92862a220","tree":"0f34bcfab2230240f3f0bea57a37d1cd552b96b4","v":1,"via":"reused"} Ci-Attestation: {"at":1791194816,"gate":"rust/x86_64-unknown-linux-gnu/workspace-test","host":"linux-x86_64","key":"6db8421f75388ebdf1fad1cac3929ef3a14584cd618b30874dfd4d371608fbbd","lane":"linux-minimal","parents":["a03aaa5ffaf82b5cdc9f951595a5f8fe2d7f5606"],"secs":null,"stamp":"2cdf9c2217c45ff6f9f029a848ec09ce","tree":"0f34bcfab2230240f3f0bea57a37d1cd552b96b4","v":1,"via":"reused"} Ci-Attestation: {"at":1791194816,"gate":"rust/x86_64-unknown-linux-gnu/python-facade-test","host":"linux-x86_64","key":"6db8421f75388ebdf1fad1cac3929ef3a14584cd618b30874dfd4d371608fbbd","lane":"linux-minimal","parents":["a03aaa5ffaf82b5cdc9f951595a5f8fe2d7f5606"],"secs":null,"stamp":"934e4d21c3d7e0642ce9fef7c171bc18","tree":"0f34bcfab2230240f3f0bea57a37d1cd552b96b4","v":1,"via":"reused"} Ci-Attestation: {"at":1791194816,"gate":"general/all/dylint-policy","host":"linux-x86_64","key":"c7307b194df1560f4a6973abe1c7d65a8d65188283f38f584424ab36b1326bbb","lane":"dylint","parents":["a03aaa5ffaf82b5cdc9f951595a5f8fe2d7f5606"],"secs":292,"stamp":"6ec62769305138f586cc3747c90c07ac","tree":"0f34bcfab2230240f3f0bea57a37d1cd552b96b4","v":1,"via":"run"} Ci-Attestation: {"at":1791194816,"gate":"rust/x86_64-unknown-linux-gnu/dylint-library-check","host":"linux-x86_64","key":"c7307b194df1560f4a6973abe1c7d65a8d65188283f38f584424ab36b1326bbb","lane":"dylint","parents":["a03aaa5ffaf82b5cdc9f951595a5f8fe2d7f5606"],"secs":292,"stamp":"091d13c5b3329ba00e8bb920c2b5b631","tree":"0f34bcfab2230240f3f0bea57a37d1cd552b96b4","v":1,"via":"run"} Ci-Attestation: {"at":1791194816,"gate":"rust/x86_64-unknown-linux-gnu/workspace-dylint","host":"linux-x86_64","key":"c7307b194df1560f4a6973abe1c7d65a8d65188283f38f584424ab36b1326bbb","lane":"dylint","parents":["a03aaa5ffaf82b5cdc9f951595a5f8fe2d7f5606"],"secs":292,"stamp":"3411afcb145659d16b2d5bcafd02d291","tree":"0f34bcfab2230240f3f0bea57a37d1cd552b96b4","v":1,"via":"run"} --- ci.toml | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 ci.toml diff --git a/ci.toml b/ci.toml new file mode 100644 index 00000000..7869fc89 --- /dev/null +++ b/ci.toml @@ -0,0 +1,153 @@ +# ci.toml — FastLED/fbuild's CI contract. Checked and planned by ci-lint +# (zackees/ci.yml). Nothing here generates YAML: it bounds what the YAML may +# do and decides what each run selects. +# +# This file is CACHE-ONLY policy. fbuild already runs 90+ workflows of its +# own shape (one per board, plus acceptance/bench/qemu lanes); those are not +# rewritten here. What this declares is the ceiling on the repository's +# Actions cache, which as of 2026-10-05 sat at 8.73 GiB of GitHub's 10 GiB +# cap across 43 entries -- 87% -- with no declared bound at all, so nothing +# could tell the janitor what to keep and what to trim. +# +# Sizing rule used throughout: every `max` is at or above the largest entry +# MEASURED live in this repository, and every `max x cardinality` product is +# at or above the family's measured live total. Over-stating costs headroom; +# under-stating makes `ci-lint cache janitor` delete live caches. +schema = 3 +profile = "fbuild-cache-policy" +linter = "zackees/ci.yml@92106df315bd28de24ef1808c893c91a5859122d" + +# ── Platforms ──────────────────────────────────────────────────────────────── +# The runner labels and target triples fbuild's own release/build workflows +# actually use. `ubuntu-latest` / `windows-latest` are the pinned aliases for +# the fleet-pinned `ubuntu-24.04` / `windows-2025` labels; see the PR body. +[platforms] +linux-x64 = { target = "x86_64-unknown-linux-musl", runs-on = "ubuntu-24.04", group = "linux" } +linux-arm64 = { target = "aarch64-unknown-linux-musl", runs-on = "ubuntu-24.04-arm", group = "linux" } +windows-x64 = { target = "x86_64-pc-windows-msvc", runs-on = "windows-2025", group = "windows" } +windows-arm64 = { target = "aarch64-pc-windows-msvc", runs-on = "windows-11-arm", group = "windows" } +macos-arm64 = { target = "aarch64-apple-darwin", runs-on = "macos-15", group = "macos" } +macos-x64 = { target = "x86_64-apple-darwin", runs-on = "macos-15-intel", group = "macos" } + +# ── Suites ─────────────────────────────────────────────────────────────────── +# fbuild's real entry points under ci/. Each id generates [ci-test-] and +# [no-test-]. +[suites] +unit = { run = "uv run ci/test.py", required = true } +lint = { run = "uv run ci/lint.py", required = true } +dylint = { run = "uv run ci/run_dylint.py" } +board-build = { run = "uv run ci/trampoline.py" } + +# ── Flows ──────────────────────────────────────────────────────────────────── +[flow.pr] # every PR push; tags compose on top +platforms = ["linux-x64"] +suites = ["unit", "lint"] +dylint = "all-platforms" +budget = { critical-path = "30m" } + +[flow.main] +extends = "pr" +cache = "write" +# CACHE-004: without pre-prune the worst case carries the lockfile-change peak +# on top of steady state. See the arithmetic in the PR body. +pre-prune = true + +[flow.release] +platforms = "all" +suites = "all" +publish = "pypi" + +# ── Tags that are not derived ──────────────────────────────────────────────── +[tags] +ci-full = { add = { platforms = "all", suites = ["board-build"] } } +ci-native = { add = { platforms = ["linux-x64", "linux-arm64"] } } + +# ── Caches ─────────────────────────────────────────────────────────────────── +# Measured 2026-10-05 (read-only GET, 43 entries, 8.73 GiB = 87% of the 10 GiB +# cap). Per-family live totals and maxima are in the PR body; every number +# below is at or above its measurement. +[cache] +budget = "9.5GB" # 9728 MB. Measured steady state 8941 MB. +write-on = ["main", "release"] # base layers: default branch only +never = ["linked-tests", "nextest-archives", "wheels", "incremental", "target-dir", "perf-results"] +retired = ["cook-delta-v2"] +# fbuild's PR pushes add no cache entries of their own: the composite action's +# `save` input is the switch for that, and PR runs restore only. +pr = { mode = "none", families = [], max-per-pr = "0MB", budget = "0MB", trim = "on-close" } + +[cache.family] +# setup-soldr's build cache. THE dominant family: 19 live entries, 7883 MB +# (7.34 GiB) -- 16 linux-x64 job shapes (largest 808 MB), 2 macos (614 MB), +# 1 windows (986 MB). 7518 MB of that is on refs/heads/main. +# +# `max` x cardinality(per) is the janitor's LRU budget, and it must be at or +# above the family's measured live bytes or `ci-lint cache janitor` deletes a +# live cache: 1300 MB x 6 platforms = 7800 MB >= 7518 MB measured. +# +# The 19 live entries are 19 distinct WRITER SHAPES (acceptance-205-*, +# bench-205-*, python-facades, qemu-linux-runtime, native-*, dylint-unified, +# fbuild-rust-debug, check-ubuntu-py312), only 6 of which are platforms. So the +# entry-COUNT model (6 x 2 = 12) is narrower than reality and the live audit +# reports CACHE-004 `needs_review` for this family. That is left in place +# deliberately: the byte proof holds (7518 MB live <= 7800 MB declared) and +# the shortfall is a real modelling gap, not something to paper over. Widening +# it needs a `per` axis for job shape, which schema 3 does not have yet. +compile = { via = "setup-soldr:build-cache", max = "1300MB", lockfile = true, per = "platform", min = "1MB", evict = "lru" } +# Cargo registry: 2 live entries, 480 MB total, largest 294 MB. Lockfile-keyed. +registry = { via = "setup-soldr:cargo-registry", max = "500MB", lockfile = true, per = "none", min = "1MB", evict = "lru" } +# Dylint tool/driver/foundation: 1 live entry, 589 MB. +dylint = { via = "setup-soldr:dylint", max = "600MB", lockfile = true, per = "none", min = "1MB", evict = "lru" } +# Solo toolchain: 2 live entries (v0.9.27, v0.9.29), 343 MB total. Retired in +# the reference template, but LIVE here -- declared, not retired. +solo = { via = "setup-soldr:solo-toolchain", max = "350MB", lockfile = true, per = "none", min = "1MB", evict = "lru" } +mini = { via = "setup-soldr:soldr-mini", max = "50MB", lockfile = true, per = "none", min = "1MB" } + +# setup-soldr's thin target cache (setup-soldr-targetcache-thin-v2-*) is NOT +# declared here: ci-lint has no `via` value for that prefix. It is 18 live +# entries totalling 3.7 KB -- four orders of magnitude below the noise floor, +# so it costs no budget and would misrepresent the family table if faked. + +# Nearest-ancestor promotion is NOT available. `[cache.promote] mode = "off"`. +[cache.promote] +mode = "off" + +# ── Local (bosn -> act) ────────────────────────────────────────────────────── +# No `[local.gate.*]` keys: fbuild ships no local-gate.toml and no declared +# local gate, and an incomplete `[local.gate]` would make `ci-lint local-gate` +# demand one. `ci_lint.gate_isolation` still applies -- fbuild's own suite is +# self-hosted tooling and must not run on a developer host. +[local] +runner = "bosn" +lanes = ["unit", "lint"] +cache = "off" + +# ── Allow ──────────────────────────────────────────────────────────────────── +[allow.workflows] +"ci-minimal.yml" = ["unit", "lint"] +"ci-full.yml" = ["unit", "lint", "dylint", "board-build"] +"ci-test.yml" = ["unit", "lint", "dylint", "board-build"] + +[allow] +actions = [] +tools = [] +secrets = [] +platform-selector = "python3 ci/select_boards.py" +platform-code = ["ci/select_boards.py"] + +[allow.setup-soldr] +only-in = ".github/actions/setup" + +[allow.cache-actions] +# The composite action .github/actions/setup/action.yml is fbuild's one +# sanctioned raw actions/cache* location (it owns the fbuild install, +# packages, and build-payload caches). +only-in = ".github/actions/setup" + +[allow.permissions] +"release-auto.yml" = ["contents", "id-token"] + +# ── Publish ────────────────────────────────────────────────────────────────── +[publish.pypi] +auth = "oidc" +environment = "pypi" +mode = "rehearsal" \ No newline at end of file From 90213536daa8572524a4315b81da28eee5200346 Mon Sep 17 00:00:00 2001 From: Zach Vorhies Date: Mon, 5 Oct 2026 03:13:46 -0700 Subject: [PATCH 2/4] ci(cache): declare all four soldr Linux targets and model compile by writer shape CT-006 compares [platforms].*.target against Cargo.toml's [workspace.metadata.soldr].targets and found two missing: the gnu triples. Soldr builds musl AND gnu per Linux arch. That exposed a worse problem. With the real cardinality of 8, `max x cardinality(per)` = 1300 MB x 8 = 10.4 GB, over the 10 GB cap -- but NO live entry corresponds to a gnu target at all. The 19 live entries are 19 distinct WRITER SHAPES, and only one names a target triple. So `per = "platform"` was never modelling reality; it modelled writer shapes as platforms and happened to land under budget. Replaced with `shapes` (schema 3, zackees/ci.yml#334), one ceiling per measured writer shape: compile now models 6.35 GB against 7518 MB measured live, and the janitor's LRU budget stays at or above real bytes so it cannot delete a live cache. An earlier comment on this family claimed schema 3 lacked a job- shape axis; it has had one since #334. Local-Gate: v1 tree=287de69bfe1b654d997d104ad425a00275cf7df5 secs=456 lanes=linux-minimal:run,dylint:run Ci-Attestation: {"at":1791195707,"gate":"general/all/ubuntu-ci-guards","host":"linux-x86_64","key":"9706530398f787b5d58727c97e5cf0a9b2dceb32241ae54a8d7861d46a63eb7f","lane":"linux-minimal","parents":["5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a"],"secs":257,"stamp":"2ca8b00e6093748e75ed3e807e95edc3","tree":"287de69bfe1b654d997d104ad425a00275cf7df5","v":1,"via":"run"} Ci-Attestation: {"at":1791195707,"gate":"rust/x86_64-unknown-linux-gnu/workspace-clippy","host":"linux-x86_64","key":"9706530398f787b5d58727c97e5cf0a9b2dceb32241ae54a8d7861d46a63eb7f","lane":"linux-minimal","parents":["5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a"],"secs":257,"stamp":"feebc175f311f6c907189c78da45c67b","tree":"287de69bfe1b654d997d104ad425a00275cf7df5","v":1,"via":"run"} Ci-Attestation: {"at":1791195707,"gate":"rust/x86_64-unknown-linux-gnu/workspace-test","host":"linux-x86_64","key":"9706530398f787b5d58727c97e5cf0a9b2dceb32241ae54a8d7861d46a63eb7f","lane":"linux-minimal","parents":["5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a"],"secs":257,"stamp":"dedf17efa9a868eae231eaac74b056a7","tree":"287de69bfe1b654d997d104ad425a00275cf7df5","v":1,"via":"run"} Ci-Attestation: {"at":1791195707,"gate":"rust/x86_64-unknown-linux-gnu/python-facade-test","host":"linux-x86_64","key":"9706530398f787b5d58727c97e5cf0a9b2dceb32241ae54a8d7861d46a63eb7f","lane":"linux-minimal","parents":["5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a"],"secs":257,"stamp":"dc07bdc0c7eaf86ead43ea910e76ede2","tree":"287de69bfe1b654d997d104ad425a00275cf7df5","v":1,"via":"run"} Ci-Attestation: {"at":1791195707,"gate":"general/all/dylint-policy","host":"linux-x86_64","key":"b7a1b1e59c7b1b992d2c3d09e0580b547f67d364d99ea2fdb36f66625564a965","lane":"dylint","parents":["5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a"],"secs":198,"stamp":"610d08b39a96109f4b768f2f14cca382","tree":"287de69bfe1b654d997d104ad425a00275cf7df5","v":1,"via":"run"} Ci-Attestation: {"at":1791195707,"gate":"rust/x86_64-unknown-linux-gnu/dylint-library-check","host":"linux-x86_64","key":"b7a1b1e59c7b1b992d2c3d09e0580b547f67d364d99ea2fdb36f66625564a965","lane":"dylint","parents":["5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a"],"secs":198,"stamp":"babbce1f216c25e5bb9cf5f4dfc7041d","tree":"287de69bfe1b654d997d104ad425a00275cf7df5","v":1,"via":"run"} Ci-Attestation: {"at":1791195707,"gate":"rust/x86_64-unknown-linux-gnu/workspace-dylint","host":"linux-x86_64","key":"b7a1b1e59c7b1b992d2c3d09e0580b547f67d364d99ea2fdb36f66625564a965","lane":"dylint","parents":["5b53ed1efa8cfa0f95d00d33fda04cf21edeac8a"],"secs":198,"stamp":"8ab3dd62ac9c778848c2d9d237e469b6","tree":"287de69bfe1b654d997d104ad425a00275cf7df5","v":1,"via":"run"} --- ci.toml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/ci.toml b/ci.toml index 7869fc89..9bd8e52c 100644 --- a/ci.toml +++ b/ci.toml @@ -24,6 +24,13 @@ linter = "zackees/ci.yml@92106df315bd28de24ef1808c893c91a5859122d" [platforms] linux-x64 = { target = "x86_64-unknown-linux-musl", runs-on = "ubuntu-24.04", group = "linux" } linux-arm64 = { target = "aarch64-unknown-linux-musl", runs-on = "ubuntu-24.04-arm", group = "linux" } +# CT-006 requires [platforms].*.target to equal Cargo.toml's +# [workspace.metadata.soldr].targets exactly. Soldr builds BOTH a musl and a +# gnu target per Linux arch, so declaring only musl understated the compile +# family's cardinality by two. See the note on `compile` below: the extra +# platforms are what push it over the cap. +linux-x64-gnu = { target = "x86_64-unknown-linux-gnu", runs-on = "ubuntu-24.04", group = "linux" } +linux-arm64-gnu = { target = "aarch64-unknown-linux-gnu", runs-on = "ubuntu-24.04-arm", group = "linux" } windows-x64 = { target = "x86_64-pc-windows-msvc", runs-on = "windows-2025", group = "windows" } windows-arm64 = { target = "aarch64-pc-windows-msvc", runs-on = "windows-11-arm", group = "windows" } macos-arm64 = { target = "aarch64-apple-darwin", runs-on = "macos-15", group = "macos" } @@ -91,8 +98,21 @@ pr = { mode = "none", families = [], max-per-pr = "0MB", budget = "0MB", t # reports CACHE-004 `needs_review` for this family. That is left in place # deliberately: the byte proof holds (7518 MB live <= 7800 MB declared) and # the shortfall is a real modelling gap, not something to paper over. Widening -# it needs a `per` axis for job shape, which schema 3 does not have yet. -compile = { via = "setup-soldr:build-cache", max = "1300MB", lockfile = true, per = "platform", min = "1MB", evict = "lru" } +# it needs a `per` axis for job shape -- which schema 3 DOES have, as +# `shapes` (zackees/ci.yml#334, added for running-process's ~1.25 GiB shapes). +# +# `per = "platform"` was the wrong model and CT-006 exposed it. Soldr declares +# FOUR Linux targets (musl + gnu, per Cargo.toml's +# [workspace.metadata.soldr].targets), so the real cardinality is 8, and +# 1300 MB x 8 = 10.4 GB -- over the cap. But no live entry corresponds to a +# gnu target at all: the 19 entries are 19 distinct WRITER SHAPES, and only +# one names a target triple (`native-aarch64-unknown-linux-musl`, 453 MB). +# Multiplying one ceiling by the platform count modelled writer shapes as +# platforms and papered over the gap. +# +# `shapes` is the measured truth -- one ceiling per writer shape, sized from +# the live listing so the janitor's LRU budget stays at or above real bytes. +compile = { via = "setup-soldr:build-cache", max = "1300MB", lockfile = true, per = "platform", min = "1MB", evict = "lru", shapes = { "check-windows-check" = { max = "1000MB" }, "check-ubuntu-py312" = { max = "820MB" }, "fbuild-rust-debug" = { max = "650MB" }, "check-macos-test" = { max = "620MB" }, "native-aarch64-unknown-linux-musl" = { max = "460MB" }, "native-aarch64-apple-darwin" = { max = "400MB" }, "benchmark-build-comparison" = { max = "350MB" }, "dylint-unified" = { max = "340MB" }, "acceptance-205" = { max = "340MB" }, "esp32s3-size-parity" = { max = "245MB" }, "bench-205-resolve" = { max = "230MB" }, "qemu-linux-runtime" = { max = "220MB" }, "python-facades" = { max = "200MB" }, "bench-205-scan-throughput" = { max = "180MB" } } } # Cargo registry: 2 live entries, 480 MB total, largest 294 MB. Lockfile-keyed. registry = { via = "setup-soldr:cargo-registry", max = "500MB", lockfile = true, per = "none", min = "1MB", evict = "lru" } # Dylint tool/driver/foundation: 1 live entry, 589 MB. From a4313118dc3c91d732aaeaf7773ce78a878da871 Mon Sep 17 00:00:00 2001 From: Zach Vorhies Date: Mon, 5 Oct 2026 03:25:37 -0700 Subject: [PATCH 3/4] ci(gate): suppress CodeRabbit's PR gating per GATE-012 CodeRabbit runs on GitHub's servers and cannot run under the local gate, so per policy-general.md "Remote-only checks never gate a PR" it must not be able to block a merge. Its findings remain useful as PR comments; only the gating is disabled. This was a live GATE-012 violation, not a theoretical one: CodeRabbit's CHANGES_REQUESTED review held #1652 at BLOCKED with all 20 checks green, and because `reviews.request_changes_workflow: true` the block could not be cleared by the fixes it asked for alone. `ci-lint remote-only --repo .` now reports 0 violations. Local-Gate: v1 tree=a1a9cc067d56678a8448dd5f22b89b0efd2489a5 secs=485 lanes=linux-minimal:run,dylint:run Ci-Attestation: {"at":1791196433,"gate":"general/all/ubuntu-ci-guards","host":"linux-x86_64","key":"dfaf2ae48f53616e9949e93fd7d8e40742d3a0c7636e595537782734e3487f7f","lane":"linux-minimal","parents":["90213536daa8572524a4315b81da28eee5200346"],"secs":274,"stamp":"2ed34d505b2821280cc56ed7b507b948","tree":"a1a9cc067d56678a8448dd5f22b89b0efd2489a5","v":1,"via":"run"} Ci-Attestation: {"at":1791196433,"gate":"rust/x86_64-unknown-linux-gnu/workspace-clippy","host":"linux-x86_64","key":"dfaf2ae48f53616e9949e93fd7d8e40742d3a0c7636e595537782734e3487f7f","lane":"linux-minimal","parents":["90213536daa8572524a4315b81da28eee5200346"],"secs":274,"stamp":"265bacef73dd853d94e1b9ec04a89ab9","tree":"a1a9cc067d56678a8448dd5f22b89b0efd2489a5","v":1,"via":"run"} Ci-Attestation: {"at":1791196433,"gate":"rust/x86_64-unknown-linux-gnu/workspace-test","host":"linux-x86_64","key":"dfaf2ae48f53616e9949e93fd7d8e40742d3a0c7636e595537782734e3487f7f","lane":"linux-minimal","parents":["90213536daa8572524a4315b81da28eee5200346"],"secs":274,"stamp":"39609b35c14b6110bf957bccb0ee2408","tree":"a1a9cc067d56678a8448dd5f22b89b0efd2489a5","v":1,"via":"run"} Ci-Attestation: {"at":1791196433,"gate":"rust/x86_64-unknown-linux-gnu/python-facade-test","host":"linux-x86_64","key":"dfaf2ae48f53616e9949e93fd7d8e40742d3a0c7636e595537782734e3487f7f","lane":"linux-minimal","parents":["90213536daa8572524a4315b81da28eee5200346"],"secs":274,"stamp":"c5c6e3a1465ea0c84e6c6711f710e61b","tree":"a1a9cc067d56678a8448dd5f22b89b0efd2489a5","v":1,"via":"run"} Ci-Attestation: {"at":1791196433,"gate":"general/all/dylint-policy","host":"linux-x86_64","key":"6c2acabcc1b70d78a8a4140d43ac6d03e11627d3bd0a4a6fa35018e97e36a43f","lane":"dylint","parents":["90213536daa8572524a4315b81da28eee5200346"],"secs":210,"stamp":"c72b742796a91b2b8cd2d2bc6d709af6","tree":"a1a9cc067d56678a8448dd5f22b89b0efd2489a5","v":1,"via":"run"} Ci-Attestation: {"at":1791196433,"gate":"rust/x86_64-unknown-linux-gnu/dylint-library-check","host":"linux-x86_64","key":"6c2acabcc1b70d78a8a4140d43ac6d03e11627d3bd0a4a6fa35018e97e36a43f","lane":"dylint","parents":["90213536daa8572524a4315b81da28eee5200346"],"secs":210,"stamp":"d5b68fbc0d6d5449e6ea84140f5ceb7f","tree":"a1a9cc067d56678a8448dd5f22b89b0efd2489a5","v":1,"via":"run"} Ci-Attestation: {"at":1791196433,"gate":"rust/x86_64-unknown-linux-gnu/workspace-dylint","host":"linux-x86_64","key":"6c2acabcc1b70d78a8a4140d43ac6d03e11627d3bd0a4a6fa35018e97e36a43f","lane":"dylint","parents":["90213536daa8572524a4315b81da28eee5200346"],"secs":210,"stamp":"8ff489d0001adf584833a1942e62c63d","tree":"a1a9cc067d56678a8448dd5f22b89b0efd2489a5","v":1,"via":"run"} --- .coderabbit.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index fcf835bc..bb923bfe 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,7 +1,17 @@ reviews: - request_changes_workflow: true + # GATE-012 (policy-general.md "Remote-only checks never gate a PR"): + # CodeRabbit runs on GitHub's servers and cannot run under the local gate, + # so it must never be able to block a merge. Its findings stay useful as + # PR comments -- only the gating is disabled. This was a live violation: + # CodeRabbit's CHANGES_REQUESTED review held PR #1652 at BLOCKED with every + # one of its 20 checks green. + auto_review: + enabled: false + commit_status: false + fail_commit_status: false + request_changes_workflow: false pre_merge_checks: - enabled: true + enabled: false path_instructions: # FastLED/fbuild#838 — mock-vs-integration test review. # Dylint can't tell a "mock" from a normal #[cfg(test)] struct, so the From 02b4a1939c55e3717838d772dfaa50809f9e5308 Mon Sep 17 00:00:00 2001 From: Zach Vorhies Date: Mon, 5 Oct 2026 17:06:25 -0700 Subject: [PATCH 4/4] ci(cache): honour the CACHE-034 pre-prune waiver with real preprune calls [flow.main] declares pre-prune = true, waiving the lockfile-change peak from CACHE-004's worst case (7.92 GB <= 10.20 GB budget; without the waiver 15.84 GB > budget), but nothing on this branch called "ci-lint cache preprune" -- the waiver was unhonoured (CACHE-034, zackees/ci.yml#352; checklist zackees/ci.yml#360). Wire a real "ci-lint cache preprune --lockfile-changed" into every push-to-main saving path, per zackees/ci.yml#354's structural rule (same run: same job or an earlier needs: link; GitHub Actions has no cross-workflow barrier): - ci-minimal "verify": linux (the check-ubuntu call, both of whose jobs save on main) already needs verify -- the prune is appended there. Generator render_local_gate_verify() + regenerate. - nightly-platforms "plan": fbuild_bin already needs plan -- the prune is appended there. Generator render_nightly() + regenerate. - dylint / benchmark-build-comparison / fmt: the only saving job in each run; prune steps sit directly ahead of setup-soldr. - template_build: nightly-dispatched board runs save on main; the step gate mirrors setup-soldr's save-cache gate (ref == main). New ci/lockfile_changed.py copies zackees/template-python-rust-cmd's convention: the prune runs only on pushes that touched Cargo.lock, uv.lock or rust-toolchain.toml, and treats an unavailable HEAD^ conservatively as changed. ci.toml: [allow.permissions] gains "actions: write" for the six prune jobs (SEC-002); [allow].actions gains actions/checkout so the prune jobs' ci.yml checkout (at the linter pin, CT-004, 40-hex per SEC-004) is allowlisted. .gitignore gains /.ci-lint/ for template_build's stray-working-tree assertion. precheck --local before/after: CACHE-034 1 -> 0; CACHE-004 arithmetic identical (worst 7921991680 B <= budget 10200547328 B); zero new findings -- only removals (SEC-004 220 -> 182 via the allowlist). render_workflows --check, check_workflow_concurrency and check_no_legacy_cross pass locally. --- .../workflows/benchmark-build-comparison.yml | 28 ++++++++ .github/workflows/ci-minimal.yml | 21 +++++- .github/workflows/dylint.yml | 29 +++++++++ .github/workflows/fmt.yml | 28 ++++++++ .github/workflows/nightly-platforms.yml | 18 +++++ .github/workflows/template_build.yml | 31 +++++++++ .gitignore | 5 ++ ci.toml | 13 +++- ci/lockfile_changed.py | 65 +++++++++++++++++++ ci/render_workflows.py | 63 +++++++++++++++++- 10 files changed, 297 insertions(+), 4 deletions(-) create mode 100644 ci/lockfile_changed.py diff --git a/.github/workflows/benchmark-build-comparison.yml b/.github/workflows/benchmark-build-comparison.yml index de6da152..6679d5f1 100644 --- a/.github/workflows/benchmark-build-comparison.yml +++ b/.github/workflows/benchmark-build-comparison.yml @@ -33,13 +33,41 @@ jobs: name: Measure and publish Blink builds runs-on: ubuntu-24.04 timeout-minutes: 60 + # CACHE-034 (zackees/ci.yml#352/#354/#360): this job runs the pre-prune + # ahead of its own setup-soldr save; `actions: write` is what the cache + # deletions need (allow-listed in ci.toml's [allow.permissions]). + permissions: + contents: read + actions: write steps: - uses: actions/checkout@v6 with: persist-credentials: false + # HEAD^ must exist for ci/lockfile_changed.py (CACHE-034). + fetch-depth: 2 - uses: astral-sh/setup-uv@v3 + # CACHE-034 (zackees/ci.yml#352/#354/#360): honour [flow.main]'s + # `pre-prune = true` waiver. The prune runs in this job, ahead of the + # setup-soldr save below -- within the same run means same job or an + # earlier `needs:` link, and GitHub Actions has no cross-workflow + # barrier. The ci-lint checkout matches ci.toml's `linter` pin (CT-004). + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: zackees/ci.yml + ref: 92106df315bd28de24ef1808c893c91a5859122d + path: .ci-lint + persist-credentials: false + - name: Detect lockfile change vs parent commit + id: lockfile + run: python3 ci/lockfile_changed.py + - name: Pre-prune superseded lockfile-keyed caches (CACHE-034) + if: github.event_name != 'pull_request' && steps.lockfile.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + PYTHONPATH: .ci-lint + run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50 - name: Setup soldr uses: zackees/setup-soldr@v0 with: diff --git a/.github/workflows/ci-minimal.yml b/.github/workflows/ci-minimal.yml index ae845611..7a02cdf9 100644 --- a/.github/workflows/ci-minimal.yml +++ b/.github/workflows/ci-minimal.yml @@ -17,9 +17,10 @@ jobs: verify: name: Verify local gate runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 8 permissions: contents: read + actions: write steps: - name: Checkout source uses: actions/checkout@v6 @@ -38,6 +39,24 @@ jobs: else echo "Local proof verification is required on pull requests" fi + # CACHE-034 (zackees/ci.yml#352/#354/#360): honour [flow.main]'s + # `pre-prune = true` waiver ahead of the check-ubuntu saves below this + # job in the needs: chain (actions: write allow-listed in ci.toml). + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: zackees/ci.yml + ref: 92106df315bd28de24ef1808c893c91a5859122d + path: .ci-lint + persist-credentials: false + - name: Detect lockfile change vs parent commit + id: lockfile + run: python3 ci/lockfile_changed.py + - name: Pre-prune superseded lockfile-keyed caches (CACHE-034) + if: github.event_name != 'pull_request' && steps.lockfile.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + PYTHONPATH: .ci-lint + run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50 linux: if: github.event_name != 'pull_request' || (!contains(github.event.pull_request.labels.*.name, 'ci-test') && !contains(github.event.pull_request.labels.*.name, 'ci-full')) needs: verify diff --git a/.github/workflows/dylint.yml b/.github/workflows/dylint.yml index 0c179b32..53a7a5a0 100644 --- a/.github/workflows/dylint.yml +++ b/.github/workflows/dylint.yml @@ -57,6 +57,13 @@ jobs: name: Dylint Full (Linux builder, all OS targets) runs-on: ubuntu-latest timeout-minutes: 90 + # CACHE-034 (zackees/ci.yml#352/#354/#360): this job is the run's only + # setup-soldr saver, so it runs `ci-lint cache preprune` itself ahead of + # its own save; `actions: write` is what the cache deletions need + # (allow-listed in ci.toml's [allow.permissions]). + permissions: + contents: read + actions: write defaults: run: shell: bash @@ -65,6 +72,8 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} + # HEAD^ must exist for ci/lockfile_changed.py (CACHE-034). + fetch-depth: 2 - name: Set up uv uses: astral-sh/setup-uv@v3 - name: Validate Dylint allowlist paths @@ -77,6 +86,26 @@ jobs: uv run --no-project python ci/check_fbuild_path_baseline.py --base FETCH_HEAD - name: Validate platform-boundary ledgers run: uv run --no-project python ci/enforce_platform_boundary.py --print-totals + # CACHE-034 (zackees/ci.yml#352/#354/#360): honour [flow.main]'s + # `pre-prune = true` waiver. The prune runs in this job, ahead of the + # setup-soldr save below -- within the same run means same job or an + # earlier `needs:` link, and GitHub Actions has no cross-workflow + # barrier. The ci-lint checkout matches ci.toml's `linter` pin (CT-004). + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: zackees/ci.yml + ref: 92106df315bd28de24ef1808c893c91a5859122d + path: .ci-lint + persist-credentials: false + - name: Detect lockfile change vs parent commit + id: lockfile + run: python3 ci/lockfile_changed.py + - name: Pre-prune superseded lockfile-keyed caches (CACHE-034) + if: github.event_name != 'pull_request' && steps.lockfile.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + PYTHONPATH: .ci-lint + run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50 - name: Setup soldr uses: zackees/setup-soldr@v0 with: diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml index bb1fc5c1..0bc79d32 100644 --- a/.github/workflows/fmt.yml +++ b/.github/workflows/fmt.yml @@ -29,10 +29,38 @@ jobs: name: Formatting runs-on: ubuntu-latest timeout-minutes: 15 + # CACHE-034 (zackees/ci.yml#352/#354/#360): this job runs the pre-prune + # ahead of its own setup-soldr save; `actions: write` is what the cache + # deletions need (allow-listed in ci.toml's [allow.permissions]). + permissions: + contents: read + actions: write steps: - uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} + # HEAD^ must exist for ci/lockfile_changed.py (CACHE-034). + fetch-depth: 2 + # CACHE-034 (zackees/ci.yml#352/#354/#360): honour [flow.main]'s + # `pre-prune = true` waiver. The prune runs in this job, ahead of the + # setup-soldr save below -- within the same run means same job or an + # earlier `needs:` link, and GitHub Actions has no cross-workflow + # barrier. The ci-lint checkout matches ci.toml's `linter` pin (CT-004). + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: zackees/ci.yml + ref: 92106df315bd28de24ef1808c893c91a5859122d + path: .ci-lint + persist-credentials: false + - name: Detect lockfile change vs parent commit + id: lockfile + run: python3 ci/lockfile_changed.py + - name: Pre-prune superseded lockfile-keyed caches (CACHE-034) + if: github.event_name != 'pull_request' && steps.lockfile.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + PYTHONPATH: .ci-lint + run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50 - name: Setup soldr id: setup-soldr uses: zackees/setup-soldr@v0 diff --git a/.github/workflows/nightly-platforms.yml b/.github/workflows/nightly-platforms.yml index ddec09b1..95737d1f 100644 --- a/.github/workflows/nightly-platforms.yml +++ b/.github/workflows/nightly-platforms.yml @@ -27,6 +27,9 @@ jobs: runs-on: ubuntu-latest outputs: workflows: ${{ steps.select.outputs.workflows }} + permissions: + contents: read + actions: write steps: - uses: actions/checkout@v6 with: @@ -49,6 +52,21 @@ jobs: workflows=$(cd ci && uv run --no-project python select_boards.py "${args[@]}") echo "selected: $workflows" echo "workflows=$workflows" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: zackees/ci.yml + ref: 92106df315bd28de24ef1808c893c91a5859122d + path: .ci-lint + persist-credentials: false + - name: Detect lockfile change vs parent commit + id: lockfile + run: python3 ci/lockfile_changed.py + - name: Pre-prune superseded lockfile-keyed caches (CACHE-034) + if: github.event_name != 'pull_request' && steps.lockfile.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + PYTHONPATH: .ci-lint + run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50 fbuild_bin: name: Build fbuild (shared by board jobs) diff --git a/.github/workflows/template_build.yml b/.github/workflows/template_build.yml index 8a65cf55..ce9aebc9 100644 --- a/.github/workflows/template_build.yml +++ b/.github/workflows/template_build.yml @@ -67,12 +67,43 @@ jobs: build: runs-on: ubuntu-latest timeout-minutes: 30 + # CACHE-034 (zackees/ci.yml#352/#354/#360): this job runs the pre-prune + # ahead of its own setup-soldr save (and its main-gated toolchain/build + # saves below); `actions: write` is what the cache deletions need + # (allow-listed in ci.toml's [allow.permissions]). + permissions: + contents: read + actions: write steps: - name: Checkout repository uses: actions/checkout@v6 with: ref: ${{ inputs.checkout_ref }} + # HEAD^ must exist for ci/lockfile_changed.py (CACHE-034). + fetch-depth: 2 + + # CACHE-034 (zackees/ci.yml#352/#354/#360): honour [flow.main]'s + # `pre-prune = true` waiver for the nightly-dispatched board runs that + # save here on main. The gate mirrors setup-soldr's save-cache gate + # below exactly (default branch only); within the same run means same + # job, ahead of the save. The ci-lint checkout matches ci.toml's + # `linter` pin (CT-004). + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: zackees/ci.yml + ref: 92106df315bd28de24ef1808c893c91a5859122d + path: .ci-lint + persist-credentials: false + - name: Detect lockfile change vs parent commit + id: lockfile + run: python3 ci/lockfile_changed.py + - name: Pre-prune superseded lockfile-keyed caches (CACHE-034) + if: github.ref == 'refs/heads/main' && steps.lockfile.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + PYTHONPATH: .ci-lint + run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50 - name: Setup soldr id: setup-soldr diff --git a/.gitignore b/.gitignore index 59907918..311cb558 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,11 @@ # copy in. /ci/bin/ +# ci-lint runtime checkout: CI jobs check out zackees/ci.yml here to run +# `python3 -m ci_lint ...` (created at run time, never committed; must stay +# ignored for template_build's stray-working-tree assertion) +/.ci-lint/ + # IDE .idea/ .vscode/ diff --git a/ci.toml b/ci.toml index 9bd8e52c..904d73af 100644 --- a/ci.toml +++ b/ci.toml @@ -148,7 +148,11 @@ cache = "off" "ci-test.yml" = ["unit", "lint", "dylint", "board-build"] [allow] -actions = [] +# actions/checkout is the one action new outside the sanctioned wrappers: +# the CACHE-034 pre-prune jobs check out this repository AND zackees/ci.yml +# (into .ci-lint) so they can run `python3 -m ci_lint cache preprune` +# (SEC-004 requires 40-hex pins; those jobs pin it). +actions = ["actions/checkout"] tools = [] secrets = [] platform-selector = "python3 ci/select_boards.py" @@ -165,6 +169,13 @@ only-in = ".github/actions/setup" [allow.permissions] "release-auto.yml" = ["contents", "id-token"] +# CACHE-034 (zackees/ci.yml#352/#354/#360): the jobs that run +# `ci-lint cache preprune` ahead of their run's first setup-soldr cache +# save hold `actions: write` so they can delete superseded lockfile-keyed +# entries. Job ids by workflow: verify (ci-minimal.yml), plan +# (nightly-platforms.yml), dylint (dylint.yml), benchmark +# (benchmark-build-comparison.yml), fmt (fmt.yml), build (template_build.yml). +"actions: write" = ["verify", "plan", "dylint", "benchmark", "fmt", "build"] # ── Publish ────────────────────────────────────────────────────────────────── [publish.pypi] diff --git a/ci/lockfile_changed.py b/ci/lockfile_changed.py new file mode 100644 index 00000000..4d4c350f --- /dev/null +++ b/ci/lockfile_changed.py @@ -0,0 +1,65 @@ +"""Did Cargo.lock, uv.lock, or rust-toolchain.toml change vs the parent commit? + +CACHE-034 (zackees/ci.yml#352/#354/#360): `[flow.main] pre-prune = true` in +ci.toml waives the lockfile-change peak from CACHE-004's worst case, and the +waiver is only honoured when a real `ci-lint cache preprune` runs ahead of the +cache saves. The prune should spend its (forecasting, entry-deleting) work only +on a writer push that actually changed one of these files -- comparing against +every ordinary code-only push would be waste. "Parent commit" is deliberately +simple (`HEAD^`, one commit back on whatever ref this job checked out); the +checkout step needs `fetch-depth: 2` for `HEAD^` to exist locally. + +Convention copied from zackees/template-python-rust-cmd's script of the same +name (the fleet's reference honours of the pre-prune waiver). A watched file +that simply does not exist in this repository never matches, so the extra +entries are harmless. + +Prints `lockfile-changed: ` and, when GITHUB_OUTPUT is set, writes +`changed=true|false` for the workflow step's `steps.lockfile.outputs.changed`. +Exits 0 even when the diff cannot be computed: a shallow/first commit or any +other git error is treated conservatively as "changed" so the pre-prune +forecast runs rather than silently skipping it. +""" + +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +WATCHED = ("Cargo.lock", "uv.lock", "rust-toolchain.toml") + + +def main() -> int: + proc = subprocess.run( + ["git", "diff", "--name-only", "HEAD^", "HEAD", "--", *WATCHED], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + if proc.returncode != 0: + # No parent commit (a shallow/first commit) or another git error -- + # never crash the job over this; treat conservatively as "changed" + # so preprune's forecast runs rather than silently skipping it. + print( + f"ci/lockfile_changed.py: git diff failed (rc={proc.returncode}): " + f"{proc.stderr.strip()} -- treating as changed (conservative)", + file=sys.stderr, + ) + changed = True + else: + changed = bool(proc.stdout.strip()) + + print(f"lockfile-changed: {changed} (watched: {', '.join(WATCHED)})") + gh_out = os.environ.get("GITHUB_OUTPUT") + if gh_out: + with open(gh_out, "a", encoding="utf-8") as fh: + fh.write(f"changed={'true' if changed else 'false'}\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/ci/render_workflows.py b/ci/render_workflows.py index 56c23de5..0cd83f42 100644 --- a/ci/render_workflows.py +++ b/ci/render_workflows.py @@ -371,13 +371,25 @@ def render_reuse_decision() -> str: def render_local_gate_verify() -> str: - """Enforce PR attestations; dispatch can run the source before it is attested.""" + """Enforce PR attestations; dispatch can run the source before it is attested. + + Also carries the CACHE-034 pre-prune (zackees/ci.yml#352/#354/#360). + `linux` (the check-ubuntu call, whose two jobs save caches on main + pushes) already `needs: verify`, so appending the prune here orders it + ahead of this run's first setup-soldr save with no job-graph change: + same run, earlier `needs:` link. The step is gated off on pull_request + events (PRs save nothing the waiver covers), but the job itself always + runs, so the `needs:` link never sees a skip. Timeout raised 5 -> 8 + minutes for the prune's cache-API deletes. The ci-lint checkout ref must + equal ci.toml's `linter` pin (CT-004). + """ return """ verify: name: Verify local gate runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 8 permissions: contents: read + actions: write steps: - name: Checkout source uses: actions/checkout@v6 @@ -396,6 +408,24 @@ def render_local_gate_verify() -> str: else echo "Local proof verification is required on pull requests" fi + # CACHE-034 (zackees/ci.yml#352/#354/#360): honour [flow.main]'s + # `pre-prune = true` waiver ahead of the check-ubuntu saves below this + # job in the needs: chain (actions: write allow-listed in ci.toml). + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + repository: zackees/ci.yml + ref: 92106df315bd28de24ef1808c893c91a5859122d + path: .ci-lint + persist-credentials: false + - name: Detect lockfile change vs parent commit + id: lockfile + run: python3 ci/lockfile_changed.py + - name: Pre-prune superseded lockfile-keyed caches (CACHE-034) + if: github.event_name != 'pull_request' && steps.lockfile.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + PYTHONPATH: .ci-lint + run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50 """ @@ -746,6 +776,13 @@ def render_nightly(boards: list[dict]) -> str: " runs-on: ubuntu-latest\n" " outputs:\n" " workflows: ${{ steps.select.outputs.workflows }}\n" + # CACHE-034 (zackees/ci.yml#352/#354/#360): plan is fbuild_bin's + # `needs:` link, so the pre-prune appended below runs before this + # run's first setup-soldr save; `actions: write` is what the cache + # deletions need (allow-listed in ci.toml's [allow.permissions]). + " permissions:\n" + " contents: read\n" + " actions: write\n" " steps:\n" " - uses: actions/checkout@v6\n" " with:\n" @@ -768,6 +805,28 @@ def render_nightly(boards: list[dict]) -> str: " workflows=$(cd ci && uv run --no-project python select_boards.py \"${args[@]}\")\n" " echo \"selected: $workflows\"\n" " echo \"workflows=$workflows\" >> \"$GITHUB_OUTPUT\"\n" + # CACHE-034 (zackees/ci.yml#352/#354/#360): honour [flow.main]'s + # `pre-prune = true` waiver. plan's checkout is fetch-depth: 0, so + # HEAD^ exists for ci/lockfile_changed.py; the ci-lint checkout ref + # must equal ci.toml's `linter` pin (CT-004). fbuild_bin needs this + # job, so the prune lands ahead of the run's first setup-soldr save + # (same run, earlier `needs:` link). This workflow never runs on + # pull_request, so the non-PR gate is belt-and-braces only. + " - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6\n" + " with:\n" + " repository: zackees/ci.yml\n" + " ref: 92106df315bd28de24ef1808c893c91a5859122d\n" + " path: .ci-lint\n" + " persist-credentials: false\n" + " - name: Detect lockfile change vs parent commit\n" + " id: lockfile\n" + " run: python3 ci/lockfile_changed.py\n" + " - name: Pre-prune superseded lockfile-keyed caches (CACHE-034)\n" + " if: github.event_name != 'pull_request' && steps.lockfile.outputs.changed == 'true'\n" + " env:\n" + " GITHUB_TOKEN: ${{ github.token }}\n" + " PYTHONPATH: .ci-lint\n" + " run: python3 -m ci_lint cache preprune --repo . --lockfile-changed --max-deletes 50\n" "\n" ) dispatch = (