Change language

rp-registry

Purpose

The registry of modules and solutions: what exists, what each solution is made of,
and the bytes themselves. It answers two readers that share nothing else. Ptah
asks for an index and then for objects by digest, never learning a name and
verifying every byte it receives. The portal and the chat ask the opposite
question — what solutions exist, what would this one bring, what does it need
first.

Both live on one service because a catalogue naming modules the object store does
not hold is the failure this exists to prevent, and it can only be checked where
both halves are visible.

Responsibility boundary

rp-registry never touches a cluster. It publishes a catalogue and serves bytes;
turning a selection into a running platform is lm-kubernetes writing Solution
custom resources, and reconciling them is ptah's. Keeping that line means one
registry can serve many clusters without holding credentials to any of them.

Storage

Two stores, deliberately not more.

objects is a single KV store holding two kinds of value under two prefixes:
module bytes by digest (obj), and solution documents by layer and name (sol).
Both are read whole by an exact key, so they are the same kind of storage used
twice. KVStore tells binary from JSON by a header it writes itself, so neither
prefix needs decoding here.

catalog is SQL, because it holds the part that changes and the part that gets
queried: which artifact currently resolves to which digest, in which layer. Bytes
are immutable and addressed by content, so they need no table; names move with
every publish and are asked for by layer, by kind, and across layers.

The published mapping is not stored. It is a fold over layers and modules
computed on request, and its digest is the revision. Storing it would keep a
second copy of the truth that is stale exactly when a publish has just happened
and a cluster is asking why it cannot see it.

Layers

A layer is one build's output. converged publishes its modules, club publishes
its own and declares extends: ["converged"], and the mapping every consumer
reads is composed from all of them — a product layer landing after the base it
extends, so a module it deliberately replaces wins. Publishing order stops
mattering, and no build can erase names it did not create.

Publishing replaces a layer wholesale rather than merging: a publish is that
layer's entire output, so a name the new build no longer produces has to
disappear. Merging would leave it pointing at bytes nothing builds any more.

Serving the bytes

ensureObjectCached hands back a Valkey key, not the object. The bytes are
materialised in the scoped cache and read from there by the ui, which is the same
handoff the gallery uses for images and for the same reason: a module is up to
2 MiB, and answering with the payload would push it through storage → ms → ui on
every cache miss, over a channel meant for requests.

The two routes a cluster uses — /registry/index.json and /registry/<digest> —
are served by the club's ui host, not by this service
(club/core/frontend/landing/src/ptah-registry.ts). ptah speaks plain HTTP and
nothing else, so something with an HTTP surface has to answer for it, and that is
the ui; the base image stays out of it, because a product without a registry has
nothing to serve. The routes are anonymous, because authenticity rests on the
digest: ptah verifies every byte it receives against the digest it asked for, so a
shared secret would add something to distribute and revoke without adding a
guarantee.

A digest no layer names is not served even when the bytes are stored. Objects with
no name are either garbage from an interrupted publish or something a caller
guessed, and serving them would make this an open blob store.

Limits

An object is capped at 2 MiB. Real modules are tens of kilobytes — the whole
converged backend set is about 0.44 MiB compressed — and ptah refuses anything
over 64 MiB, so this sits far below both. It is not an optimisation: the registry
is meant to hold community modules, and an upload the size of a container image
means somebody published the wrong artifact.

Objects are stored exactly as they arrive. The registry neither compresses nor
decompresses: module bundles are brotli, workflow sources are raw because
centimanus executes them itself, and the digest covers the stored bytes in both
cases. Any re-encoding would break content addressing at the one hop it exists to
protect.

Source

modules/repositories/rp-registry