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)
|
- **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)*
|
- **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)
|
- **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
|
### 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
|
not one. A container that is running is not a registry that replies, and this project has paid for
|
||||||
that distinction once already.*
|
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
|
*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
|
**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
|
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 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 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
|
**How they arrive is settled and not yet built.** The installer carries an init builder, which
|
||||||
repository, so it cannot be pulled from the public internet like the registry; and the installer
|
clones the source and builds the control plane, the catalogue and the builder before a mesh exists
|
||||||
carries one image only, the control plane's. So a mesh raised by the installer today has no builder
|
to install anything. Two things about that are open and named in
|
||||||
and no way to obtain one, which means it cannot build the catalogue, which means every module
|
[ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md): where the init builder
|
||||||
waiting on a digest keeps waiting. Whatever answers this — the installer carrying a second image,
|
clones from, given the forge normally runs on the mesh it would be rebuilding, and what it
|
||||||
the control plane's own build producing both, or the first builder being fetched some other way —
|
publishes into, given the registry is installed later in the order today.
|
||||||
is the last thing between a raised mesh and a self-upgrading one.
|
|
||||||
|
|
||||||
**There is no fourth.** Everything else the mesh runs is either upstream — a third-party image
|
**There is a fourth, and the list was closed too early.** The catalogue owns the module graph
|
||||||
pulled by digest — or built by the builder from a repository and published to the registry. So the
|
([ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md)), so it cannot be
|
||||||
question "how does *this* one get here first?" has an answer for every module without asking it
|
installed by something that needs it in order to install anything — the same shape as the other
|
||||||
again: if it is not one of the three above, it comes through the loop.
|
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
|
**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
|
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
|
raises four machines the same way has not tested genesis at all — it has tested joining, four
|
||||||
times, with the first one hand-fed.
|
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
|
## Genesis
|
||||||
|
|
||||||
The installer is a single program carrying the control plane's image inside it. That is what makes
|
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
|
**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.
|
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
|
**The init builder is not built.** Until it is, the installer carries the control plane's image and
|
||||||
mesh builds it; and the installer carries one image only. So the paragraphs above describing the
|
nothing gives a fresh mesh a builder or a catalogue, so the paragraphs above describing the core
|
||||||
core modules being built are, today, describing something that cannot start.
|
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
|
**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
|
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