Commit Graph
2 Commits
Author SHA1 Message Date
claudeandClaude Opus 5 4da6e8d30f docs(cache): write down the producer half of the depended-upon names contract
daniel/gitdan's ci-cache-reclaim.sh hard-codes four dot-prefixed names it
reads to make correctness decisions but never reclaims as leftovers —
.ci-lock-*, .cache-last-used, .gitea-last-used, .ci-keep — none of which
carried a matching note here (gitdan#32, the follow-up shape to #28/#30
that #7/PR #8 covered for the leftover names specifically).

Of the four, this repo actually produces two:

- .ci-lock-<id>, written by scripts/cache-lock.sh (acquire/release) and
  cache-lib.sh's write_cache_lock(). Documents the contract at
  cache-lock.sh's header, the site someone renaming the marker would
  most likely be editing.
- .cache-last-used, stamped every run by cargo-cache/action.yml. Documents
  the contract at the exact line that writes it.

The other two are read by gitdan's script but produced by nothing in this
repo: .gitea-last-used is a legacy naming convention individual repos
used before adopting the shared cargo-cache action (nothing here writes
it today), and .ci-keep is a per-repo, hand-placed opt-out any consuming
repo's own workflow may drop directly into a cache directory, with no
single owner. Both get a paragraph in README.md's new subsection
explaining why no producer-side counterpart exists for them, rather than
inventing an owner this repo doesn't have.

README.md's "Scratch names in a cache root are a cross-repo contract"
section gains a new subsection, "Names this repo doesn't reclaim, but
the arbiter depends on", covering all four and pointing at gitdan's
DEPENDED-UPON NAMES CONTRACT block as the canonical description.

No behaviour change: comments and docs only.

Ref: gitdan#32. Consumer-side counterpart: daniel/gitdan (this branch's
sibling PR).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJHKJFJVUjnvVsemVBnvrd
2026-08-24 17:08:54 -05:00
claudeandClaude Opus 5 248af3061e feat(cargo-cache): hardlink-clone a per-ref Cargo cache from a published snapshot
Replaces the phase-0 resolution probe with the real actions, merging the two
independent per-branch Cargo cache implementations on this forge into the
design neither of them had.

## The merge

- zemyna seeds a PR branch by `cp -al` hardlink clone (near-free: cost scales
  with inode count, not bytes) from the base branch's LIVE target dir — a
  torn read waiting for a second job slot (its own #911).
- emowheel seeds from a PUBLISHED IMMUTABLE SNAPSHOT (no race by
  construction) but with `cp -a`, duplicating ~35 GB per branch.

This ships hardlink-clone FROM a published snapshot: zemyna's cost profile,
emowheel's soundness, and #911 closed structurally rather than by the runner
happening to have one execution slot.

## The bug both implementations have

A build inside a `cp -al` clone DOES mutate the directory it was cloned from.
Cargo replaces real artifacts, but writes its metadata — and build scripts
write their OUT_DIR — with a plain truncating write, straight through the
shared inode. Measured set: `.fingerprint/<unit>/dep-<target>` (under
CARGO_UNSTABLE_CHECKSUM_FRESHNESS), `build/<pkg>/{output,root-output,out/**}`,
`deps/*.d` and `<profile>/*.d`.

The checksum-freshness case is a wrong answer, not a slow build: a PR clone
rewrites the base's dep-info to describe the PR's sources while the base's
cache still holds the artifact built from the base's; once the PR merges, the
base's next run finds the checksums match, reports `Fresh`, and links a binary
built from the pre-merge code. Reproduced end to end.

Fix: hardlink the artifacts (the GB), real-copy the metadata (the MB) — about
3.7% of a 6.9 GB Bevy target dir, against 100% for a full copy.

## Contents

- `cargo-cache/action.yml` — consume: resolve keys, seed from the base's
  snapshot via staging + one atomic rename, strip Cargo lock files, unshare
  the mutable paths, restore mtimes from git history, lock, prune.
- `cargo-cache-publish/action.yml` — publish: record the build watermark,
  atomically republish the snapshot on a protected branch, release the lock
  (`mode: release-lock` for the `if: always()` step).
- `scripts/` — all logic, so it is testable standalone; the YAML is wiring.
- `scripts/*selftest.sh` + `selftest.sh` — five suites, 63 assertions, every
  fix paired with a control that reproduces the bug. All green locally.

Eviction merges emowheel's liveness pass (dead branches pruned
unconditionally, not gated on disk pressure) with LRU-under-pressure, but
inverts the order within the pressure pass: `target-*` before `snapshot-*`,
because a snapshot is hardlinked to everything cloned from it, so evicting one
frees almost no real bytes while costing every future PR its warm start.

restore-mtimes.sh is ported from emowheel (the watermark variant, which closes
the merge hazard zemyna's copy still has) with its provenance de-projectised.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sqh2vscfzisk83VuPVQX9L
2026-08-23 14:18:01 -05:00