The catalogue owns the module graph, and genesis builds rather than carries #34
@@ -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. |
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user