Merge pull request 'The catalogue owns the module graph, and genesis builds rather than carries' (#34) from feat/the-catalogue-owns-the-module-graph into main

This commit was merged in pull request #34.
This commit is contained in:
2026-09-12 22:09:38 +02:00
4 changed files with 150 additions and 17 deletions
@@ -0,0 +1,107 @@
---
topic: the tiers
status: accepted
date: 2026-09-12
deciders: jochen
reconstructed: false
extends: 0067-genesis-is-a-pivot.md
---
# 70. The catalogue owns the module graph, and genesis builds rather than carries
## Context
**The module graph has no owner.** What modules exist, what each requires and provides, what each
claims, what each is made of — all of it lives inside the control plane because that is where it
was first written, not because anything decided it belonged there.
**The control plane's own test says it does not belong there.**
[`06-the-control-plane`](../03-DESIGN/01-to-be/06-the-control-plane.md) defines the tier as
*everything that needs to know about more than one node*, and states the corollary plainly:
anything a single machine could answer alone is not the control plane's. What a module is, and
what it needs, requires no knowledge of any node whatsoever.
**And nothing can query it.** The graph is the thing that answers *what must be rebuilt when this
changes*, *what would break if this were removed*, and *what can be installed here* — and today it
is reachable only as control-plane internals. [ADR 0009](0009-modules-and-the-graph.md) already
decided that a build edge is derived, that an artifact is stale when anything it was built against
moved, and that the rebuild set is therefore computable. None of that has anywhere to live.
**Genesis currently carries an image, and cannot produce a builder at all.**
[ADR 0067](0067-genesis-is-a-pivot.md) has the installer carry the control plane's image. That
works, but it leaves the builder with no route onto a fresh mesh — it cannot be fetched from the
public internet, because the mesh builds it, and the installer carries one image only. So a raised
mesh cannot build anything, including the modules it is made of.
## Decision
**The catalogue is a core module, beside the control plane and the builder, and it owns the module
graph.** Modules, what they require and provide, what they claim, their dependencies and their
build edges, assignments and configuration — the catalogue holds them and serves tools over them:
install a module, query what is available and what it needs, query the graph, query and update
settings.
**It is one per mesh**, expressed the way the control plane already expresses it — a claim scoped
to the mesh, not a new mechanism.
**The control plane consumes it.** Resolving what a module requires into an actual binding, and
composing what a machine should be, both need to know what modules are. So the dependency runs
from the control plane to the catalogue, which is the opposite of what the tiers suggest and is
therefore written down here rather than left to be inferred.
**Genesis builds the core modules rather than carrying them.** The installer ships an *init
builder* — the one thing carried — which is started, clones the source, and builds the control
plane, the catalogue and the builder. The installer then raises a temporary control plane, which
installs the catalogue, registers the permanent control plane and the builder, and assigns all
three to the first machine. The temporary control plane stops; the installer verifies that the
permanent one answers and can query the graph. The builder then sees a catalogue in its initial
state and builds the core modules into it.
**So exactly one thing is carried, and it is a builder rather than a result.** That is the
difference from [ADR 0067](0067-genesis-is-a-pivot.md), which carried the control plane's image:
carrying a builder produces every core module on the machine, including the builder itself, so
there is no component left without a route.
## Consequences
The catalogue joins the small set of things that cannot arrive through the ordinary path, because
it cannot be installed by something that needs it in order to install anything. It arrives the same
way everything else does under this record — built by the init builder before the mesh can install
anything — so the set is answered by one mechanism rather than three special cases.
The rebuild fan-out gains a home. *What was this built against* and *what must rebuild now* are
questions about the graph, and the graph now has an owner to hold the edges and answer them.
The catalogue holds state, so it owns a store in the substrate's database, the same way the control
plane's contexts do. That is the mesh's own store and not the `postgres` module, which is a
provider other modules consume.
A mesh without a catalogue cannot resolve anything, where previously it merely lacked an interface.
That is the cost of ownership over surfacing, and it is deliberate: one owner beats two copies.
## Open, and to be settled before this is built
**Where the init builder clones from.** [ADR 0067](0067-genesis-is-a-pivot.md) rejected building
from source at genesis partly because the source lives in a forge that runs on the mesh, so a
total rebuild would need the mesh it is rebuilding. Carrying a builder answers the toolchain half
of that objection and not this half. Genesis must therefore name a source that exists before the
mesh does.
**What the init builder publishes into.** Building produces artifacts that must be pinned by a
digest a registry assigned, and today the registry is installed after the machine has joined. If
the core modules are built first, the registry has to exist first, so the substrate's order needs
restating rather than assumed.
**Where the line falls between the catalogue and the control plane.** Claims and assignments need
to know about every node, which by the control plane's own test is its work. Whether the catalogue
holds them and asks, or the control plane holds them and the catalogue surfaces them, is not
settled here.
## How this is checked
| Rule | Checked by |
|---|---|
| The catalogue owns the graph | The control plane answers *what does this module require* by asking the catalogue, and a mesh whose catalogue is stopped cannot resolve — observed, not assumed. |
| One per mesh | A second catalogue assigned anywhere in the mesh is refused by the claim, and the refusal is exercised. |
| Genesis builds rather than carries | The installer carries exactly one artifact, and after installing, every core module is pinned to a digest the mesh's own registry assigned. |
| The builder has a route | A mesh raised by the installer, with no hand-placed image, can build a module. |
+1
View File
@@ -102,6 +102,7 @@ python3 00-META/checks/index.py fail if stale
- **0067** — [Genesis is a pivot: a temporary control plane installs the registry that makes it permanent](0067-genesis-is-a-pivot.md)
- **0068** — [The lab takes requests, one at a time, and runs each from its own copy](0068-the-lab-takes-requests.md) *(proposed)*
- **0069** — [A module is a repository and a path within it](0069-a-module-is-a-repository-and-a-path.md)
- **0070** — [The catalogue owns the module graph, and genesis builds rather than carries](0070-the-catalogue-owns-the-module-graph.md)
### What runs on them, and how it gets there
+26 -14
View File
@@ -360,10 +360,12 @@ machine across the private network, because a mesh-scoped provision that only an
not one. A container that is running is not a registry that replies, and this project has paid for
that distinction once already.*
## The three the loop cannot build, and there are only three
## The four the loop cannot build
*2026-09-12. Written down because it keeps being rediscovered as if it were new, once per
component. It is one rule, it has three instances, and the list is closed.*
component. It is one rule; the instances are listed below. Revised the same day, when the catalogue
gained an owner and turned out to be a fourth — which is the argument for stating the rule rather
than the list.*
**Anything the build loop needs in order to run cannot be delivered by the build loop.** It arrives
from outside exactly once, and from then on it is an ordinary module, upgraded like one. What
@@ -374,20 +376,30 @@ have a route and one does not:
|---|---|---|
| The control plane | It is what installs modules. Nothing can install it before it runs. | **Carried inside the installer** and published once there is a registry ([ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md)) |
| The registry | It *is* where artifacts are delivered from. A store cannot be delivered through itself. | **Pulled from the public internet** — its image is an ordinary public one, never built ([`04-ISSUES/029`](../../04-ISSUES/029-the-artifact-store-cannot-be-delivered-by-the-artifact-store/00-report.md)) |
| The builder | It is what builds. Nothing builds it before it runs. | **Nothing yet.** Not carried, not public. See below. |
| The builder | It is what builds. Nothing builds it before it runs. | Built at genesis by the init builder ([ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md)) |
| The catalogue | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder ([ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md)) |
**The builder has no route, and this is the open one.** It is built from the control plane's
repository, so it cannot be pulled from the public internet like the registry; and the installer
carries one image only, the control plane's. So a mesh raised by the installer today has no builder
and no way to obtain one, which means it cannot build the catalogue, which means every module
waiting on a digest keeps waiting. Whatever answers this — the installer carrying a second image,
the control plane's own build producing both, or the first builder being fetched some other way —
is the last thing between a raised mesh and a self-upgrading one.
**How they arrive is settled and not yet built.** The installer carries an init builder, which
clones the source and builds the control plane, the catalogue and the builder before a mesh exists
to install anything. Two things about that are open and named in
[ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md): where the init builder
clones from, given the forge normally runs on the mesh it would be rebuilding, and what it
publishes into, given the registry is installed later in the order today.
**There is no fourth.** Everything else the mesh runs is either upstream — a third-party image
pulled by digest — or built by the builder from a repository and published to the registry. So the
question "how does *this* one get here first?" has an answer for every module without asking it
again: if it is not one of the three above, it comes through the loop.
**There is a fourth, and the list was closed too early.** The catalogue owns the module graph
([ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md)), so it cannot be
installed by something that needs it in order to install anything — the same shape as the other
three. This section previously said the list was closed at three, which was written before the
catalogue had an owner and is corrected here rather than left to be reasoned from.
**And the answer for all four is now one mechanism, not four special cases.** Genesis carries an
*init builder* and builds the core modules on the machine — control plane, catalogue and builder —
rather than carrying a finished image of any of them. So the question is no longer "how does this
one get here first?" asked once per component; it is answered once, by the thing that is carried
being a builder rather than a result.
Everything outside those four is either upstream — a third-party image pulled by digest — or built
by the builder from a repository and a path, and published to the registry.
**A carried artifact is not a differently-pinned artifact.** Once published it is named by a digest
the mesh's registry assigned, exactly like everything the builder produces. A reader cannot tell
+16 -3
View File
@@ -36,6 +36,19 @@ Confusing the two is what produced a procedure that only ever worked in a fixtur
raises four machines the same way has not tested genesis at all — it has tested joining, four
times, with the first one hand-fed.
## Genesis is decided to change, and has not yet
*2026-09-12.* [ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md) settles
that the installer carries an **init builder** rather than the control plane's image, and that the
core modules — control plane, catalogue, builder — are **built on the machine** before a mesh exists
to install anything. One thing is carried, and it is a builder rather than a result, which is what
gives the builder and the catalogue a route they did not have.
**The section below describes what the installer does today**, which is to carry the control plane's
image and publish it once there is a registry. It is kept as written because it is true of the
program that exists, and replacing it with the intention would leave nothing describing the thing
anybody actually runs. The order changes when the init builder is built; the pivot does not.
## Genesis
The installer is a single program carrying the control plane's image inside it. That is what makes
@@ -136,9 +149,9 @@ needs a toolchain and a working tree, which is most of the burden the installer
**A mesh cannot say how it was raised.** Nothing afterwards can contradict a claim that a machine
was brought up the supported way, so the rule that it must be is, today, unenforced.
**The builder has no way to arrive.** It cannot be pulled from the public internet, because the
mesh builds it; and the installer carries one image only. So the paragraphs above describing the
core modules being built are, today, describing something that cannot start.
**The init builder is not built.** Until it is, the installer carries the control plane's image and
nothing gives a fresh mesh a builder or a catalogue, so the paragraphs above describing the core
modules being built describe something that cannot yet start.
**A machine has no account for a registry that asks for one.** The mesh grants a consumer a
credential for a database; it does not yet do so for the store its own images live in. Genesis