From 0118c28f01fafeb234f1b9d129a9820380e8a0f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 12:56:18 -0500 Subject: [PATCH] docs(cache): state the .publish-* gap as a ticket, not a present state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contract block asserted that neither side reclaims .publish-new- 'today'. True when written and about to stop being true: gitdan#30 tracks adding both .publish-* prefixes to the arbiter's enumeration, and a sibling track is landing it this round. A comment that dates itself against a merge in flight is worse than no comment. Rewords all four sites (cache-lib.sh, publish-snapshot.sh, and README's table row and prose) to reference gitdan#30 and keep the mechanism that made the shape worth catching — .publish-new- is tagged per job per run exactly as .stage- is — rather than the arbiter's momentary contents. The rule itself is unchanged; it is the durable part, and it is what found this. Adds the counting check while there: the five names here and the prefixes ci-cache-reclaim.sh enumerates are meant to be the same length, so a mismatch is the cheapest signal that one side gained a shape without telling the other. --- README.md | 13 ++++++++++--- scripts/cache-lib.sh | 17 +++++++++++------ scripts/publish-snapshot.sh | 13 +++++++------ 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f5bdd5f..76c725e 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ block is the canonical description; this side owns the names. | name | produced by | if the job dies holding it | |---|---|---| | `.stage-` | `cache-lib.sh`, `hardlink_clone_into()` | stranded; only the arbiter reclaims it | -| `.publish-new-` | `publish-snapshot.sh` | stranded, and not in the arbiter's list | +| `.publish-new-` | `publish-snapshot.sh` | stranded; see `daniel/gitdan#30` | | `.publish-old--` | `publish-snapshot.sh` | swept by the next publish of that key | | `.evicting--` | `prune-cache.sh`, `evict_dir()` | swept at the start of the next prune pass | | `.reading--` | `cache-lib.sh`, `reader_lock_acquire()` | not garbage — see below | @@ -263,8 +263,15 @@ renaming one. That script enumerates by explicit prefix rather than by dotglob stream alongside the trees they protect — so a name it has not been told about is not handled conservatively, it is invisible, and an unreclaimed staging tree is a full clone of a multi-GB target dir on the one volume whose entire problem -is disk. `.publish-new-` is the standing example: it strands exactly as -`.stage-` does and is in neither side's sweep. +is disk. `.publish-new-` is the worked example: the two `.publish-*` names were +outside the contract when it was written, and `.publish-new-` strands exactly +as `.stage-` does, so it is the one that needed catching. Bringing both under +the arbiter's enumeration is tracked as `daniel/gitdan#30`. + +The two lists are meant to be the same length — the five names above, and the +prefixes `ci-cache-reclaim.sh` enumerates. A mismatch means one side gained a +shape without telling the other, which is the drift the rule exists to catch +and the cheapest thing to check. **The staleness constants are part of the same contract, and that half has a direction.** `CACHE_READ_STALE_SECONDS` (`cache-lib.sh`) and diff --git a/scripts/cache-lib.sh b/scripts/cache-lib.sh index 6927e96..bf50169 100755 --- a/scripts/cache-lib.sh +++ b/scripts/cache-lib.sh @@ -55,12 +55,17 @@ # is invisible: an unreclaimed staging tree is a full clone of a multi-GB # target dir on the one volume whose entire problem is disk. # -# Of the five above, the arbiter enumerates `.stage-`, `.evicting-` and -# `.reading-`. The two `.publish-*` names predate the contract and are not in -# it, which is why `.publish-new-` — tagged per job per run exactly as -# `.stage-` is — is a shape neither side reclaims today. Closing that is a -# change over there, not here; the rule above is what stops the list growing -# another one. +# The two `.publish-*` names predate the contract and were outside it when this +# block was written — the rule catching an uncovered shape on its first +# application. `.publish-new-` is the one that mattered: it is tagged per job +# per run exactly as `.stage-` is, so a publisher killed before the swap +# strands a tree under a name no later run of that script matches, which is +# precisely the shape only the arbiter can reach. Bringing both in is tracked +# as daniel/gitdan#30. +# +# The two lists are meant to be the same length. A shape here without a prefix +# there is one side having changed without telling the other, and it is +# cheapest to notice by counting. # # The two staleness constants the arbiter mirrors are part of the same # contract, and that half has a direction to it — see CACHE_READ_STALE_SECONDS diff --git a/scripts/publish-snapshot.sh b/scripts/publish-snapshot.sh index d040864..0d8d2d5 100755 --- a/scripts/publish-snapshot.sh +++ b/scripts/publish-snapshot.sh @@ -55,12 +55,13 @@ DST=$(snapshot_dir_for "$ROOT" "$OWN_KEY") # `.publish-old-` and `.publish-new-` are dot-prefixed names under a cache # root, so the cross-repo contract at the top of cache-lib.sh covers them: # renaming either, or adding a third, requires a matching prefix in -# daniel/gitdan's ci-cache-reclaim.sh. Neither is in its list today, and -# `.publish-new-` is the shape that most needs to be — it is tagged per job per -# run, so a publisher killed between staging its snapshot and the swap below -# strands a full hardlink clone that no later run of this script will ever -# match. `.publish-old-` is milder: the sweep further down reclaims it on the -# next publish of the same key. +# daniel/gitdan's ci-cache-reclaim.sh. Both were outside its list when that +# contract was written, and `.publish-new-` is why it mattered: it is tagged +# per job per run, so a publisher killed between staging its snapshot and the +# swap below strands a full hardlink clone that no later run of this script +# will ever match. `.publish-old-` is milder — the sweep further down reclaims +# it on the next publish of the same key. Bringing both under the arbiter's +# enumeration is tracked as daniel/gitdan#30. OLD="${ROOT}/.publish-old-${OWN_KEY}-${TAG}" SNAP_NAME=$(basename "$DST") GRACE="${CACHE_READ_GRACE_SECONDS}"