Settles the design repository now that the self-upgrade build is on main: - Records the two decisions that shipped without a record — ADR 0077 (the controller/foundation/node vocabulary) and ADR 0078 (the store and broker are ordinary modules); accepts ADR 0075 and 0076, which shipped work rests on. - Fills issue 051's amended-design and wires ADR 0078 into 07-the-foundation. - Sweeps the repo rename (mesh-control -> mesh-controller) into the mutable docs now that the forge repo is renamed; updates the glossary note and repos.md. - Fixes the six broken links from the design-doc renames, indexes the glossary, regenerates the decisions reading order. Both checks (records.py, index.py) are green. Statuses stay honest: the build is on main and lab-proven but not deployed as the production mesh, so the to-be docs remain in-progress and the as-is layer (the hal mesh) is unchanged — graduation to implemented + as-is belongs to deployment, not merge. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
130 lines
7.5 KiB
Markdown
130 lines
7.5 KiB
Markdown
---
|
|
topic: the tiers
|
|
status: accepted
|
|
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. |
|