LibWorld
← All features

A catalog you can query

Storing bytes is the easy half. The question you will actually be asked is "what do we have, and what did this release depend on?" — and a plain cache cannot answer it. LibWorld writes down what it stores, so you can query it later.

The bytes and the facts live apart, on purpose

An artifact is a big opaque blob; what you want to ask about it is small and structured. So LibWorld keeps the two separately: artifacts go to any S3-compatible object store, or to local disk if you have not configured one, while the record of what they are lives in Postgres where you can query it.

Each row carries the backend, package, version, size, hashes, storage key, when it arrived, and when it was last pulled. That one table is what world ls, the dashboard, and the phone app are all reading — there is no second source of truth to drift.

The S3 side is written directly against the API — SigV4 signing over the libcurl that was already linked, rather than a vendored AWS SDK. It is the reason the whole server has no cloud-vendor dependency to update.

Finding the blobs the catalog lost

A cache can quietly develop a blind spot. Every normal operation walks catalog → blobs, so a stored artifact whose database row went missing becomes invisible: it is absent from world ls and from the dashboard, while still happily answering pulls, because a backend that finds the bytes on disk just serves them.

In S3 mode that is a permanent leak, since the garbage collector enumerates the catalog — the exact place the object is missing from. So GET /gc/orphans walks the other way, blobs → catalog, and ?adopt=true writes a row for each thing it finds. The first scan of our own dev bucket turned up eight, and one of them had already cost an hour of debugging a test failure that looked like a new bug.

It scans each backend's own prefix rather than the whole bucket, because that bucket also holds build artifacts and step caches which carry no catalog row by design — a whole-bucket scan would call every one of them an orphan and offer to put it under retention.

Adopting is the default rather than deleting, because throwing away unrecognised bytes is the wrong instinct for a system whose job is to still have them. The adopted row is dated from the object's own mtime, so an ancient orphan becomes collectable immediately instead of being handed a fresh retention window for having just been noticed.

What it costs

Stated plainly, because a claim with no stated cost is the one nobody re-checks.

What cannot be recovered is left blank rather than guessed. A storage key gives you a filename and nothing more, so an adopted orphan has no source_url — and inventing a plausible one would make a later repull confidently fetch the wrong file.