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
108 lines
6.3 KiB
Markdown
108 lines
6.3 KiB
Markdown
---
|
|
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-controller.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. |
|