refactor(cache): name the mutable set once, and measure it

The set of paths a hardlink clone has to real-copy — dep-info,
build-script metadata, linked outputs, the pruned directories that hold
them — was spelled out inline in unshare_mutable_paths, in four find
invocations. Nothing else needed it, so one spelling was enough.

Something else needs it now: the prune has to know what a clone will
cost before it happens, and a sizer with its own copy of the predicates
would drift from the copier silently and in the dangerous direction — an
under-measured clone is one that starts and runs out of disk halfway
through unsharing. So the directory names become one array and the file
rules one dispatcher, applied by a callback per side, with each rule's
rationale moved to the rule rather than left at the old call site.

mutable_set_kb measures that set off a snapshot, skipping the subtrees
already measured whole so nothing is counted twice; clone_headroom_kb
scales it by a hand-written margin and floor for what the measurement
cannot see (cp -al materialising every directory for real, and the
unshare holding one subtree twice at its peak). Both residuals are named
where the function is, in both directions.

seed_source_candidates moves the seed's source-preference list into
cache-lib for the same reason: the prune ahead of it has to resolve the
same source the seed will clone, and two agreeing derivations are one
edit away from disagreeing.

No behaviour change — the copier applies the same rules to the same
tree, verified by the hardlink-clone suite's inode partition in both
directions.

Refs #20.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXMQCJ5Eg5f9G9cfYzyh4Z
This commit is contained in:
2026-09-07 00:20:45 -05:00
co-authored by Claude Fable 5.1
parent 0b099ecf62
commit a960c8f91b
2 changed files with 192 additions and 44 deletions
+5 -4
View File
@@ -61,10 +61,11 @@ fi
# snapshot already exists.
# 3. an explicit fallback directory — migration off a pre-existing flat
# cache, so the first run under this scheme isn't a needless cold build.
CANDIDATES=()
[ -n "$BASE_KEY" ] && CANDIDATES+=("$(snapshot_dir_for "$ROOT" "$BASE_KEY"):base snapshot")
CANDIDATES+=("$(snapshot_dir_for "$ROOT" "$OWN_KEY"):own snapshot")
[ -n "$FALLBACK" ] && CANDIDATES+=("${FALLBACK}:fallback dir")
#
# The list itself lives in cache-lib.sh because prune-cache.sh reads it too:
# it runs ahead of this step and has to free enough disk for the clone below,
# which means resolving the same source this loop will pick.
mapfile -t CANDIDATES < <(seed_source_candidates "$ROOT" "$OWN_KEY" "$BASE_KEY" "$FALLBACK")
for entry in "${CANDIDATES[@]}"; do
src="${entry%%:*}"; label="${entry#*:}"