Retention and GC
A mirror that only ever grows is a disk that eventually fills, usually at an inconvenient moment. But deleting from a system with two stores is where things go wrong, so collection here reconciles both at once — the record and the bytes cannot drift apart.
One sweep, both stores
Collection is least-recently-used over the catalog, and it removes the stored object as well as the row. Doing only half is how you get the two failure modes this system cares about: a row without its blob is a broken record, and a blob without its row is an orphan that leaks space forever.
Anything you have pinned is protected. So is anything in the image retention class, which the ISO backend puts its images into on arrival — meaning an OS image is protected by WHAT IT IS, not by someone having remembered to pin it. That is deliberate: the artifacts most expensive to lose are the ones least often pulled, and pure least-recently-used would come for them first.
Build artifacts and step caches are separate
Build artifacts have their own retention, and step caches have their own ledger with a keep-last and a keep-days policy. Caches deliberately carry no catalog row, which is exactly why the orphan scan is per-backend-prefix rather than whole-bucket.
What it costs
Stated plainly, because a claim with no stated cost is the one nobody re-checks.
In S3 mode an uncatalogued blob is a permanent leak until it is adopted, because the catalog is the only enumeration. In filesystem mode collection walks the tree by mtime and does collect it — so the same bug is a leak in one mode and only a visibility gap in the other.