diff --git a/02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md b/02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md new file mode 100644 index 0000000..f0fcc37 --- /dev/null +++ b/02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md @@ -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. | diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index eaf273f..fc67ee1 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -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 diff --git a/03-DESIGN/01-to-be/12-a-module-repository.md b/03-DESIGN/01-to-be/12-a-module-repository.md index e5ca4a8..b717803 100644 --- a/03-DESIGN/01-to-be/12-a-module-repository.md +++ b/03-DESIGN/01-to-be/12-a-module-repository.md @@ -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 diff --git a/03-DESIGN/01-to-be/17-raising-a-mesh.md b/03-DESIGN/01-to-be/17-raising-a-mesh.md index b999bab..c3d3680 100644 --- a/03-DESIGN/01-to-be/17-raising-a-mesh.md +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -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