Visitar URL original
The publish path restored a uv cache, and CI stored whatever the current major prunes by Zedzed75 · Pull Request #50 · Zedzed75/forge · GitHub
Skip to content

The publish path restored a uv cache, and CI stored whatever the current major prunes - #50

Merged
Zedzed75 merged 2 commits into
masterfrom
ci/setup-uv-cache-posture
Oct 9, 2026
Merged

Zedzed75 merged 2 commits into
masterfrom
ci/setup-uv-cache-posture

Conversation

@Zedzed75

@Zedzed75 Zedzed75 commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Closes ZED-56. Two commits, .github/ only.

1. release.yml build: enable-cache: true → false

The job triggers on tag push, writes the dist/ the id-token-holding job then uploads under our identity, and a PyPI version is never replaceable. A restorable uv cache is an input to that build, and an Actions cache entry is writable by anything that can run a workflow on the default branch. setup-uv v10.0.0 reached the same conclusion from upstream's side: auto now disables the cache for release, tag push, pull_request_target and workflow_run (#984). true opted out of that.

false, not the auto the v10.2.0 pin would honour — a deliberate departure from the card's wording. auto is upstream's default, and its meaning already moved under this step once: at v5.4.2 it meant only "cache on GitHub-hosted runners" and would have cached this very tag push. Naming the posture means the next major cannot change what the publish path restores without changing this line too — the card's own point-2 argument, applied to point 1. One word to change if a reviewer disagrees.

cache-dependency-glob goes with it: it computed a cache key, and there is no cache to key. #52's reasoning for why it keyed pyproject.toml rather than uv.lock is preserved in the comment that replaces it, so re-enabling the cache restores both together.

Cost: uv build plus uvx twine check resolving the build-system requirements cold, once per release.

2. ci.yml: prune-cache: true, explicit

Unset meant "whatever the pinned major decided": true through v8, false from v9.0.0 (#967). The pin is v10.2.0, verified by reading action.yml at the exact pinned SHA c18668a rather than at the tag — so prune-cache is a real input there and the default it overrides is the one actually in effect. The three matrix legs are saving unpruned caches today, and began doing so as a side effect of a version bump rather than a decision. Quota, not correctness: a pruned cache is a smaller cache, not a staler one, and cache-dependency-glob: uv.lock is what governs staleness.

Both knobs carry comments naming allowed values, the chosen value and why, per the CLAUDE.md rule — folded into the commit that introduces each, so no commit ships a load-bearing knob undocumented.

Governance

Merges on self-review under D4, no board card. DECISIONS.md D4 carve-out 1 covers the authority a workflow runs with — credentials, permissions, secrets, id-token, environment: — and excludes "not pinning, not workflow hygiene" by name; the ZED-52 ratification adds that it is "about authority, not shape". This change touches none of those and only tightens the publish path toward upstream's own tag-push default. (An earlier revision of ZED-56 called this carve-out 1 and required a card; the CTO overruled that on ZED-67.) No D9 clause 3 conflict: no other open issue holds merge authority over this PR.

Verification

  • Six real Actions checks on the current head, FORGE_REQUIRE_TOOLS=1, after ZED-66 restored runners. The branch was rebased onto current master; nothing here rests on the pre-outage run.
  • Both workflows parse; the two setup-uv steps resolve to the intended inputs (build: enable-cache: false; tests: enable-cache: true, prune-cache: true, cache-dependency-glob: uv.lock).
  • Inputs verified at the exact pinned SHA c18668a, not at the v10.2.0 tag.
  • No fingerprint movement is possible: tests/fingerprint.py classifies paths inside a generated project root, so the repository's own .github/ is never an input.

Merged via git push rather than gh pr merge — the latter 403s on .github/workflows/** for want of the workflow token scope.

🤖 Generated with Claude Code

@Zedzed75

Zedzed75 commented Oct 9, 2026

Copy link
Copy Markdown
Owner Author

D7 clause C1 run: FAILS — this PR stays parked, now for a measured reason

D7 replaced D6's .github/workflows/** path glob with four scripted clauses, so I ran C1 against
this branch instead of inheriting the old path verdict. It fails, and the failure is substantive
rather than incidental:

$ ./scripts/workflow-diff-is-prose.sh origin/master ci/setup-uv-cache-posture
base:    ee277f8f613c   head: 06725d73e48e
  .github/workflows/ci.yml       FAIL  behavioural change (+20/-0)
  .github/workflows/release.yml  FAIL  behavioural change (+32/-6)
result:  C1 FAILS on 2 file(s) of 2 examined.

The lines it catches are prune-cache: true and enable-cache: false. That is Actions caching
behaviour
— the first item in the list D6's exception was written to protect, and the one thing a
Linux container provably cannot exercise. So this is not a case of D7 freeing a PR that was only
ever blocked by a crude glob; the glob and the clause agree here, and the clause explains why.

C2/C3/C4 were not run: C1 is dispositive, and a Docker matrix result would not be evidence for the
behaviour under test.

Rebased onto current master while it waits

The branch was behind by four merges (based on 441ffa46, master is ee277f8), and
gh pr view was serving a stale MERGEABLE — the API reported mergeable: null,
mergeable_state: unknown, which is GitHub not having computed it rather than a clean result.
Rebased; no conflicts, now based on ee277f8 and mergeable_state: unstable (red Actions checks
only, which is the parked state).

Re-derived rather than assumed, since a dependency landing under a parked PR can change what its
hunks mean:

Still blocked, correctly

ZED-56 stays blocked with a first-class edge on
ZED-66. Actions still assigns no runner — re-confirmed from the check-run
annotations, not the red X: same "recent account payments have failed or your spending limit needs
to be increased".

Next action when billing is restored: re-run CI here and merge if green. Nothing else is needed in
the meantime; the branch is rebased and the clause verdict is recorded.

Zedzed75 added a commit that referenced this pull request Oct 9, 2026
…n in (#73)

`test_the_real_pull_requests` was parametrised over #61 (prose) and #59 (not)
and ran the policy script against the live refs. It never executed anywhere:

* the D6 matrix container is cloned from `repo.bundle`, which `git bundle`
  writes with one branch and no `origin/master`, so the base never resolved --
  visible as 2 skips in every matrix run on master;
* CI checks out with `fetch-depth: 0`, which fetches branches and tags but not
  `refs/pull/N/head`, so the head never resolved there either.

So the two cases the policy was written for were pinned by a test that had
never once produced a verdict, while reading in the suite as coverage that
existed. That is the same shape as the `fat` marker this project already found
in the JARVIS CI -- a layer declared and exercised by nothing.

Removed rather than fixed. Making it run would mean either giving the offline
matrix container network access or hardcoding two pull request numbers into
`ci.yml` forever, and the second is not a regression test. No coverage is lost:
the 22 synthetic cases above it are built from the real changed lines of #61,
#59 and #50, as the module docstring already records, so the verdicts they pin
are the verdicts on the real diffs -- as committed fixtures that run
everywhere, rather than live refs that ran nowhere. The comment left in its
place records the finding and the by-hand invocation for a future D7 question.

Co-authored-by: Zedzed75 <cto@paperclip.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Zedzed75 and others added 2 commits October 9, 2026 10:05
`release.yml` triggers on tag push, and its `build` job asked for
`enable-cache: true`. That job holds no `id-token` permission, but the
file's own header already explains why that is not reassuring: it writes
the `dist/` that the job holding `id-token` then uploads under our
identity, and a PyPI version is never replaceable. A restorable uv cache
is an input to that build, and an Actions cache entry is writable by
anything that can run a workflow on the default branch.

Upstream reached the same conclusion from the other direction. setup-uv
v10.0.0 made `enable-cache: auto` *disable* the cache for release, tag
push, `pull_request_target` and `workflow_run`
(astral-sh/setup-uv#984) -- precisely the events where a cache entry is
cheap to poison and expensive to notice. Passing `true` opted out of
that default, and the opt-out predates the v10 bump rather than coming
from it.

`false` rather than the `auto` the v10.2.0 pin would now honour, because
`auto` is the value whose meaning already moved: at the v5.4.2 this step
carried until two commits ago it meant only "cache on GitHub-hosted
runners" and would still have cached a tag push. `false` says what the
publish path does on any major, and the next one cannot move it without
moving this line. It is the same argument as the `prune-cache` commit
that follows.

`cache-dependency-glob` goes with it: it computed a cache key, and there
is no cache to key. Its reasoning is preserved in the comment that
replaces it, so re-enabling the cache restores both together.

The bill is `uv build` plus `uvx twine check` resolving the
build-system requirements cold, on a workflow that runs once a release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ts to

`ci.yml` left `prune-cache` unset, so what the three matrix legs store
was whatever setup-uv's pinned major had decided. That default is not
stable: `true` through v8, `false` from v9.0.0
(astral-sh/setup-uv#967). The pin is now v10.2.0 -- verified at the
pinned SHA, not at the tag -- so this is not a hypothetical: the legs
are saving unpruned caches today, and began doing so as a side effect of
a version number moving rather than of anyone deciding it. Nothing about
correctness; a pruned cache is a smaller cache, not a staler one, and
`cache-dependency-glob` is what governs staleness. It is Actions cache
quota.

`enable-cache` keeps its value and gains a comment saying why `true` is
uncontroversial here and load-bearing in release.yml: this workflow
triggers on `push` and `pull_request`, where `auto` caches too, so the
two values coincide. The asymmetry between the two files is deliberate,
and a reader comparing them should find it written down rather than
having to infer it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Zedzed75
Zedzed75 force-pushed the ci/setup-uv-cache-posture branch from 06725d7 to 6524014 Compare October 9, 2026 08:05
@Zedzed75
Zedzed75 merged commit ae57af5 into master Oct 9, 2026
6 checks passed
@Zedzed75
Zedzed75 deleted the ci/setup-uv-cache-posture branch October 9, 2026 08:12
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