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 writing lm-kubernetesSolution
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.
is a single KV store holding two kinds of value under two prefixes:objects
module bytes by digest (), and solution documents by layer and name (obj).sol
Both are read whole by an exact key, so they are the same kind of storage used
twice. tells binary from JSON by a header it writes itself, so neitherKVStore
prefix needs decoding here.
is SQL, because it holds the part that changes and the part that getscatalog
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 and layersmodules
computed on request, and its digest is the . Storing it would keep arevision
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. publishes its modules, converged publishesclub
its own and declares , and the mapping every consumerextends: ["converged"]
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
hands back a Valkey key, not the object. The bytes areensureObjectCached
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 — and /registry/index.json —/registry/<digest>
are served by the club's ui host, not by this service
(). ptah speaks plain HTTP andclub/core/frontend/landing/src/ptah-registry.ts
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