--- topic: the tiers status: proposed date: 2026-09-15 deciders: jochen reconstructed: false extends: 0014-no-npm-workspace.md --- # 75. An artifact store is a provision; a package registry is a different one ## Context Two questions have been circling, and they turn out to be one question asked twice. **"Should gitea be the mesh's registry?"** It serves OCI images and a dozen package ecosystems, it is already needed — genesis clones from one — and the mesh's own registry has neither authentication ([issue 042](../04-ISSUES/042-nothing-gives-a-node-an-account-for-a-registry/00-report.md)) nor a transport a runtime will accept over a network ([issue 048](../04-ISSUES/048-nothing-makes-a-machine-trust-the-mesh-registry/00-report.md)). Gitea has both. **"Where does the SDK come from?"** [ADR 0014](0014-no-npm-workspace.md) already answers it — each module consumes its dependencies, the mesh's own shared library included, *from the private registry* — and nothing installs one, so today it comes from a git URL, which is [issue 053](../04-ISSUES/053-the-sdk-is-pinned-twice-and-the-two-disagree/00-report.md). **The framing that dissolves both:** `artifact-store` is already a provision, and `registry` already provides it. So "should gitea be the registry" is not a question about replacing a component. It is a question about **a second provider of an existing provision** — which this mesh has a mechanism for, and uses for certificate authorities and VPNs already. ## Decision **Two provisions, because they are two jobs.** | provision | is | for | |---|---|---| | `artifact-store` | content-addressed blobs, pinned by digest, no versions, no ranges | what the **mesh** delivers to **machines** | | `package-registry` | an ecosystem's own registry — npm, cargo, PyPI, Go | what **code** resolves when it is compiled | They are not the same store with different clients. One is addressed by digest and immutable by construction; the other is addressed by name and version, and resolves ranges. Conflating them is how a mesh that pins everything ends up rebuilding one commit into two different things. **`registry` remains the provider genesis installs.** Not because it is better, but because of what it is: a directory and one container, no database, no control plane, installable at step 8 of an install where neither exists yet. Gitea needs a store and provisioning, which means a control plane, which means the pivot has already happened — and the pivot needs somewhere to publish to. **Gitea also provides `artifact-store`, and a mesh may choose it.** Two providers of one provision is a thing the mesh understands: it refuses, names both, and choosing is assigning the one you want. **And it does not claim `the-artifact-store`.** That claim is node-scoped, so a module holding it cannot share a machine with another that does — and a machine running gitea for git and packages *alongside* a registry serving artifacts is an ordinary arrangement, not a conflict. They are different ports doing different jobs. The exclusivity that matters is mesh-wide and is already expressed: `provides` at mesh scope means two providers are two answers, and the resolver refuses until one is assigned. Forbidding co-residence adds nothing to that and forbids something reasonable. **Whether `registry` should still hold that claim is left open here** — it may be protecting something about the port or the data directory that is not written down, and removing a claim is not a thing to do from the outside of a manifest. A mesh that assigns gitea gets authentication and TLS for its artifacts — which is to say, **issues 042 and 048 are answered by choosing a provider that already solved them**, rather than by reimplementing accounts and certificates in a registry that has none. **Gitea provides `package-registry`.** That is ADR 0014's private registry, and it is one service rather than one per ecosystem. `verdaccio` may provide it too, for npm alone, and is then a choice somebody makes rather than the answer. **The registry is not removed at the end of installing.** A mesh that never runs gitea still has an artifact store. Retiring it is a migration a mesh performs, not a step an installation ends with. ## Why not simply gitea, from the start Because genesis would need a control plane before the thing that stores the control plane's image, and that is circular rather than merely awkward. It would also make one of the three things the build loop cannot produce for itself into a stateful application with a database — the pivot is the hardest part of this design already. And it puts every artifact in the service that is also the trust anchor for everything the mesh will ever run ([ADR 0071](0071-where-genesis-gets-its-source.md)), which records that forge serving a cryptominer with tampered git operations. Two blast radii are better than one. ## Moving from one provider to the other is a designed act **Not a removal.** Every image a machine runs is pinned to a digest at a named store, the control plane's own included. Changing the provider means: 1. gitea installed, reachable, and holding an account the builder may publish with 2. every artifact mirrored 3. every declaration re-pinned, the control plane's **last**, because it is what performs the others 4. **every machine verified to have converged and to be able to pull from the new store** 5. only then the old provider unassigned, and its volume kept ([ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md)) Step 4 is the one that is easy to skip and the only thing between this and a mesh that cannot restart its own control plane. A machine that reboots mid-migration pulls from a store that no longer exists, and a local image cache hides that until exactly the moment it matters. ## Consequences **The bootstrap is unchanged**, which is the point of keeping the small provider. **042 and 048 gain a second answer.** They can be fixed in the registry, or dissolved by choosing a provider that already has accounts and TLS. The second is less work and more service. **ADR 0014 becomes satisfiable.** There is a provision for the private registry, something that provides it, and a module may depend on it — so the SDK can be published and consumed rather than cloned, and issue 053 has somewhere to go. **A mesh can be minimal or complete, and both are legitimate.** One with the small registry and no gitea builds and runs modules and cannot serve packages. That is a real configuration, not a broken one — the same way a partial host is real. **And the bootstrap still has no package registry.** The first build of the shared base happens before anything has installed one. That is the same pivot as everything else and it is **not solved here**: it is named, so the next person does not discover it. ## How this is checked | Rule | Checked by | |---|---| | Genesis needs no database | The installer raises a mesh of one on a machine with nothing, and the artifact store it installs has no store of its own. | | Two providers are a choice, not a conflict | A mesh holding both is asked to resolve `artifact-store` and refuses, naming both, until one is assigned. | | Providers may share a machine | A node is assigned both gitea and a registry, and both run — only one of them answers `artifact-store`. | | The two stores are not interchangeable | A module depending on `package-registry` is not satisfied by `artifact-store`, and the refusal says why. | | A migration is verified before it is finished | The old provider cannot be unassigned while any machine's declaration still names it. |