Skip to content

ci(cache): declare bosn's cache families, budget and LRU eviction - #513

Closed
zackees wants to merge 1 commit into
mainfrom
ci/cache-policy
Closed

zackees wants to merge 1 commit into
mainfrom
ci/cache-policy

Conversation

@zackees

@zackees zackees commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Draft: adding ci.toml unlocks 461 pre-existing policy findings across bosn (baseline without a ci.toml is 1: "not found at repo root"). None are caused by this file, but they only become visible once one exists, so this needs review before it lands.

Why

bosn's Actions cache is at 221 entries / 9.92 GiB — 99% of GitHub's 10 GiB per-repo cap. Once at the cap, GitHub refuses new saves, so builds go cold. bosn has no ci.toml, so ci-lint cache janitor had no family boundaries and nothing to evict against.

Diagnosis

family entries bytes largest
build-cache 16 9,858,848,811 1003.6 MB
solo-toolchain 4 775,424,833 207.7 MB
soldr-mini 2 21,770,955 10.5 MB
uv 3 616,911 0.3 MB
undeclared att1-* 196 85,704 470 B

Shape 1 — does the prefix encode a per-run content hash? Yes, and this is the whole problem. setup-soldr-buildcache-v2-<os>-<arch>-<digest>-<job-suffix>-<lockhash>: the trailing hash is a per-toolchain/per-lockfile hash, not a per-commit one (setup-soldr#237 deliberately dropped the commit SHA so the cache stays warm across commits). Every distinct lockfile/toolchain combination appends an entry, and nothing ever retires the old one — exactly the CACHE-029 shape. bosn has 13 live Linux build-cache entries under 4 different toolchain digests.

Shape 2 — does it decompose into the documented via families? Yes, all of it, with no residue:

  • setup-soldr-buildcache-v2- → setup-soldr:build-cache (16)
  • solo-toolchain-v3- → setup-soldr:solo-toolchain (4)
  • soldr-mini-v2- → setup-soldr:soldr-mini (2)
  • setup-uv-1- → setup-uv (3, legacy prefix)
  • att1-* (196, ~440 B each) — ci-lint's own attestation caches, not a setup-soldr family

Shape 3 — platforms. Four runner/target pairs, read from .github/workflows/: linux-x64 (ubuntu-latest), windows-x64 (windows-latest), and the two Darwin targets, which cross-compile on ubuntu-latest and smoke on macos-15-intel / macos-15. Only the first two produce build-cache entries today.

Local gate — confirmed present, and deliberately untouched. bosn declares its gate in local-gate.toml ([gate], plus [gate.isolation], [gate.lanes.*], [gate.trust], [gate.replay]). ci-lint reads ci.toml's [local.gate] only when present, so no [local.gate] key is added here and local-gate.toml stays authoritative. Verified directly: load_gate_config() returns the local-gate.toml config with zero findings after this change.

Arithmetic — it fits

Every max is at or above the largest entry actually observed. This is the direction that matters: ci-lint cache janitor LRU-evicts an evict = "lru" family down to max x cardinality, so an under-stated max would delete caches the next run genuinely restores. An over-stated max only costs budget headroom.

build-cache      1050MB x 4 (platform) = 4,404,019,200 B
solo-toolchain    210MB x 4 (platform) =   880,803,840 B
soldr-mini         12MB x 4 (platform) =    50,331,648 B
uv                  1MB x 4 (platform) =     4,194,304 B
steady total                          = 5,339,348,992 B
+ lockfile-change peak                =             0 B
+ [cache.pr].budget                   =             0 B
= worst case                          = 5,339,348,992 B   (52% of 9.5 GB)

CACHE-004, CACHE-029 and CACHE-030 are all clean, validated against ci.yml at 527cc179 (the ref bosn already pins).

per = "platform" throughout: none of these families can be counted more tightly, because each key already embeds a per-toolchain/per-lockfile hash — the same over-count CACHE-029 documents for setup-uv.

What it reclaims

ci-lint cache janitor --dry-run against the live repository:

family          before(n)   before(B)    after(n)     after(B)
build-cache         16   9858848811          3   2073450482
solo-toolchain      4     775424833          4     775424833
soldr-mini          2      21770955          2      21770955
undeclared        196         85704          0            0
uv                  3        616911          3        616911

~7.8 GB reclaimed on the build cache alone (9.86 GB -> 2.07 GB), via CACHE-006 superseded-entry cleanup. Nothing was deleted — this PR only adds the declaration.

Two deviations worth your review

  1. profile = "cache-policy-only". bosn ships a Rust-backed PyPI wheel but has not adopted the rust-pypi-app suite/publish contract. Claiming that profile would assert things bosn has not adopted (its enforced requirements: [python], [rust], required unit+smoke suites, OIDC publish). Only rust-pypi-app has enforced requirements, so this value loads and is not cross-checked.

  2. [allow].setup-soldr.only-in / [allow].cache-actions.only-in = .github/workflows. bosn has no .github/actions/ wrapper — it calls setup-soldr and actions/cache directly from its workflows. These keys record bosn's real layout rather than inventing a wrapper directory it does not have. This is a repo-specific deviation from the fleet's wrapper convention, recorded rather than silently assumed. Without them, CACHE-001 and CACHE-009 fire 15 times against a wrapper bosn does not have.

Also note the empty-but-present [suites] / [flow] / [tags] / [local] / [allow] / [publish] tables: schema 3 requires those keys to load, and declaring them empty is the honest "nothing declared yet" — filling them in is a separate piece of work.

Not done here

ci-lint cache janitor (the actual reclaim) and cache trim — this PR only adds the declaration.

bosn's GitHub Actions cache sat at 221 entries / 9.92 GiB -- 99% of
GitHub's 10 GiB per-repository cap -- with no ci.toml, so ci-lint had
no family boundaries and 'cache janitor' had nothing to evict against.

Measured live 2026-10-05 (221 entries, 10,656,747,214 B):

  build-cache       16 entries   9,858,848,811 B   largest 1,003.6 MB
  solo-toolchain     4 entries     775,424,833 B   largest   207.7 MB
  soldr-mini         2 entries      21,770,955 B   largest    10.5 MB
  uv                 3 entries         616,911 B   largest     0.3 MB
  (undeclared att1) 196 entries        85,704 B   largest     470 B

Every max is at or above the largest entry observed, the direction that
matters: janitor LRU-evicts an evict="lru" family down to max x
cardinality, so an under-stated max would delete caches the next run
genuinely restores. CACHE-004 static arithmetic fits with wide headroom:

  worst case 5,339,348,992 B / 9.5 GB budget (52%)

CACHE-004, CACHE-029 and CACHE-030 are all clean against ci.yml at
527cc179 (the ref bosn already pins). A cache janitor --dry-run against
the live repository plans 9,858,848,811 -> 2,073,450,482 B on
build-cache, reclaiming ~7.8 GB.

No [local.gate] key is added: bosn declares its gate in local-gate.toml,
and ci-lint reads ci.toml's [local.gate] only when present, so the gate
stays authoritative there and GATE-001 does not fire.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zackees added a commit to zackees/ci.yml that referenced this pull request Oct 5, 2026
…t blocks it (#344)

**Merged:** zackees/reld#218 (ac71f0b), worst case 5.92 GiB against a 9.5 GB
budget. 7 entries stay undeclared -- no via exists for msys2-pkgs,
llvm-ld-coff, linkers-Linux or setup-soldr-targetcache-thin-v2.

**Open drafts:**
- zackees/bosn#513 -- draft because adding a ci.toml surfaces **461
  pre-existing findings** (LAYOUT-001 x208, SEC-004 x148) that were
  invisible while there was no ci.toml. The agent also corrected my
  premise: bosn is NOT 221 distinct builds. It is 13 live build-cache
  entries under 4 toolchain digests -- setup-soldr#237 deliberately
  dropped the per-commit SHA so caches survive across commits -- plus 196
  tiny ci-lint attestation caches. A janitor dry-run plans 9.86 -> 2.07 GB.
- zackees/mimalloc-pprof#603 -- draft because a local gate exists.
  Declared families fit at 8.02 GiB vs 9.5 GB, but 3.58 GiB (42%) is
  UNDECLARABLE, see below.
- FastLED/fbuild#1652 -- fits at 9.75 GB against 9.5 GB, 448 MB headroom.

**Structural:** FastLED/FastLED#4703 applies `save: 'false'` on PR board
jobs so they restore without writing. It does NOT apply
`cache-mode: 'split'` -- verified available (it is on fbuild main and
FastLED pins @main, a floating ref) but genuinely broken: setup runs
before the compile step synthesises platformio.ini, and fbuild install
hard-fails on apollo3_red, clone and kitchensink. Shipping it would turn
board jobs red. Stops the leak at ~120 GiB; does not reach 10 GiB.

**A bug in my own #334**, found independently by the reld, fbuild and
mimalloc-pprof agents: `shapes` made CACHE-004 sum the shape maxima
while `_lru_evictions` still used `max x cardinality(per)`. Where they
diverge the janitor is stricter, so it evicted live entries precheck had
approved. Fixed in #343; both paths now call `family_footprint`.

**Next blocker, now the top ci.yml item:** `ALLOWED_VIA_VALUES` has no
entry for shapes setup-soldr actually emits, so they can never be
declared and are reported undeclared forever --
setup-soldr-targetcache-thin-v2-, setup-soldr-prepare-* without its v3
segment, setup-soldr-tarball-*, setup-soldr-cargo-registry-v2-*,
cook-base-v1-*. That is 42% of mimalloc-pprof's cache and blocks reld
and fbuild too.

Refs #153, #334, #343

Local-Gate: v1 tree=d3697bb3c8052b2e0a9ad339e012e0772ca4c929 secs=73
@zackees

zackees commented Oct 5, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #512, which merged a ci.toml to main while this was in draft. Closing rather than merging two competing cache declarations for the same repository.

The declaration in #512 is the same shape and reaches the same conclusion this PR did: compile is the large family (3.28 GiB on linux-x64), evict = "lru" on the growing ones, and the janitor dry-run plans 9.86 -> 2.07 GB. Both agree that the 196 att1-* entries are ci-lint's own attestation caches (~440 B each), not a setup-soldr family.

Nothing from this PR's analysis is lost: the agent's findings are recorded in zackees/ci.yml#344 and the setup-soldr-targetcache-* via gap it identified is fixed in zackees/ci.yml#345.

@zackees zackees closed this Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant