mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-08-29 04:06:58 +00:00
checkPredecessor returned a bare false on every failure path (DNS failure, dial error, non-200, 5s timeout, unparseable body), indistinguishable from a hold affirmatively answering "I have no successor". isPredecessorHold then cached that false in predecessorCache, which lives for the life of the process and is never reset, so one blip during a single GC run permanently unreferenced that hold's manifests. Those blobs are long past the 7-day grace period that protects recent content, so the next run deleted them outright with nothing to fall back on. checkPredecessor now reports whether its answer is definitive, and only definitive answers are cached. An inconclusive check keeps the hold's manifests referenced and is recorded in predecessorUnresolved, which bounds the cost to one timeout per run rather than one per manifest and is cleared at the start of every analysis so a hold that was down once is re-checked next time instead of written off. This matches the convention the rest of the package already follows: a user whose PDS cannot be reached has their records treated as referenced, never as garbage. An outage must not be the reason content becomes deletable. Non-200 counts as inconclusive on the same reasoning. A reachable service that cannot produce its own captain record is malfunctioning, not answering, and over-protecting an unrelated hold merely leaves some blobs unreclaimed. Splits the fetch-and-parse half into checkPredecessorAt so it can be tested against a local server without depending on DNS. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>