From 3a9ed9d3fd21b63002e9e585c4f50cff0e9269a5 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 13 Sep 2026 01:49:02 +0200 Subject: [PATCH 1/7] Two findings from building the catalogue on a live mesh The runtime every module compiles against cannot be built by the mesh, so the one rule that would catch it moving can never fire. And a container keeps the values it was created with, so two good applies can leave it running on neither. --- .../00-report.md | 71 +++++++++++++++++++ .../00-report.md | 60 ++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md create mode 100644 04-ISSUES/045-a-container-keeps-the-values-it-started-with/00-report.md diff --git a/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md b/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md new file mode 100644 index 0000000..a70aa91 --- /dev/null +++ b/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md @@ -0,0 +1,71 @@ +--- +status: open +opened: 2026-09-13 +located-in: [] +fixed-by: +amended-design: +--- + +# 044 — The runtime every module builds on cannot be built by the mesh + +## Symptom + +Every module written in the mesh's own toolchain is compiled on top of one shared base image — the +tool runtime. The mesh can build the modules. It cannot build the base. + +Two things stop it, and each is enough on its own: + +- The base's container definition copies a compiled output directory that is not in the repository. + A clean clone has the sources and no compiled output, so the definition refers to something that + is not there. +- The base's dependency on the mesh's own software development kit resolves to a **sibling checkout + on disk**, not to anything a clone can fetch: + + ``` + "node_modules/@novox/mesh-sdk" -> { "resolved": "../mesh-sdk", "link": true } + ``` + +Both mean the same thing: the base can only be produced on a workstation that happens to have the +neighbouring repositories laid out beside it, and has run a compile step by hand first. + +Observed while building four modules through the mesh from a repository and a path. All four +succeeded, and all four recorded that they were built on top of the same base — an image reference +that no module in the mesh produced, because no module could have. + +## Why this matters + +**This is the one artifact the whole build chain rests on, and it is the one artifact outside the +chain.** Three separate consequences, all visible today: + +- *The base is pinned by hand.* Every module's container definition names it as a literal digest + written by a person. Nothing checks that digest against anything. +- *Nothing can tell when it moves.* A build records what it was built on top of, so an artifact is + stale when anything beneath it moved. That rule cannot fire for the base: the graph has edges + pointing at it, and no version on the other end, because no build ever registered one. The mesh + is therefore unable to answer "what must be rebuilt" for the change that would reach *every* + module at once. +- *Nobody can check what is in it.* The published base and the definition in the repository have + already drifted: the definition names one operating system base, and the image actually serving + every module is built on a different one. This was found by running the image, not by reading + anything, and it went unnoticed for exactly as long as nobody could rebuild it. + +A rule the design states — an artifact is stale when anything it was built against moved — is +unenforceable for the artifact it matters most for. That is the shape of a wrong rule, not a +missing feature: everything downstream reports "nothing to rebuild" and is believed. + +## How it would be checked + +The check is the fix's own test: clone the repository alone, into an empty directory, and build it. +Nothing else present, no neighbouring checkouts, no compile run first. If that produces the base, +the mesh can produce it too, because that is all the mesh does. + +## Open questions + +- Where should the software development kit come from, for a build that has only one repository? + The mesh already knows how to run a package registry as a module, and it already publishes + container images to one of its own — so this may be the same answer twice rather than a new one. +- Is the base a module like any other, or is it one of the few things a new mesh must arrive + carrying? It behaves like both: everything is built on top of it, and it is built out of the same + sources as everything else. +- If it becomes an ordinary module, what stops the circularity — the base is built by the builder, + and the builder is built on the base? diff --git a/04-ISSUES/045-a-container-keeps-the-values-it-started-with/00-report.md b/04-ISSUES/045-a-container-keeps-the-values-it-started-with/00-report.md new file mode 100644 index 0000000..dbf387e --- /dev/null +++ b/04-ISSUES/045-a-container-keeps-the-values-it-started-with/00-report.md @@ -0,0 +1,60 @@ +--- +status: open +opened: 2026-09-13 +located-in: [] +fixed-by: +amended-design: +--- + +# 045 — A container keeps the values it started with + +## Symptom + +A module reads its database credentials from a file the mesh composes. The file was written, then +rewritten with a different value once the mesh had more to say — the provider's address was not +known the first time and was the second. + +The container went on running with the first version, indefinitely, and reported nothing. Its own +view: + +``` +DATABASE_URL=postgresql://…@:20000/… what the container holds +DATABASE_URL=postgresql://…@:20000/… what the file on disk says +``` + +A module may declare that it restarts when one of its files changes, and this one now does. That +did not help: the restart fires when the file changes *during an apply*, and the change had already +happened before the declaration naming it arrived. Applying again is a no-op, because nothing +changed that time either. The only thing that recovered it was removing the module from the machine +and putting it back. + +## Why this matters + +**Reconciliation compares intentions, not what is actually running.** Two intentions in a row can +both be applied successfully and still leave a container holding values from neither, because a +container captures its environment once, when it is created, and nothing afterwards re-reads it. + +The failure is silent in the worst way: the container is up, the machine reports success, the mesh +reports every module current, and the thing inside is using a credential the mesh no longer +believes in. Nothing in the system is in a position to notice — the host knows what it wrote, and +the module knows what it read, and no one compares the two. + +It is not specific to credentials. Any composed value delivered through a file a container reads at +start has the same shape, which is most of what the mesh delivers. + +## How it would be checked + +A machine reporting a module as running should be able to say *what it is running with* — not what +was last written for it. The check is then a comparison the mesh can make on every heartbeat rather +than a property of one apply: for each container, does what it holds match what the files it was +built from now say. A mismatch is a drift the mesh can act on, and today it is not expressible at +all. + +## Open questions + +- Should the host recreate such a container on its own, or report the drift and let the mesh decide? + Recreating is the obvious repair and is also an unannounced restart of a running service. +- Is "the files a container was created from" something the host already knows, or does a + declaration have to say it? A restart-on list is close to this, but it is written by the module + author and is therefore exactly as complete as they remembered to make it. +- Does the same gap exist for values the mesh delivers by other means than a file? From 15ac7fd8dcc8106eedb370b3d6d0261bb28ed111 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 13 Sep 2026 02:25:07 +0200 Subject: [PATCH 2/7] =?UTF-8?q?The=20base=20has=20no=20circularity=20?= =?UTF-8?q?=E2=80=94=20the=20builder=20does=20not=20stand=20on=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Written as an open question; it has an answer, and leaving it open would have made the fix look harder than it is. --- .../00-report.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md b/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md index a70aa91..d4d773b 100644 --- a/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md +++ b/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md @@ -67,5 +67,7 @@ the mesh can produce it too, because that is all the mesh does. - Is the base a module like any other, or is it one of the few things a new mesh must arrive carrying? It behaves like both: everything is built on top of it, and it is built out of the same sources as everything else. -- If it becomes an ordinary module, what stops the circularity — the base is built by the builder, - and the builder is built on the base? +- If it becomes an ordinary module, is there a circularity? **No** — checked rather than assumed: + the builder is a compiled program built from its own sources and a language toolchain, and does + not stand on this base at all. Only modules written in the mesh's scripted toolchain do. So the + thing that would build the base is not one of the things built on it, and the chain has an end. From 3983188b8ae3a1daeba1760e33aea1c7a915745e Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 13 Sep 2026 02:51:08 +0200 Subject: [PATCH 3/7] =?UTF-8?q?Issue=20044=20resolved=20=E2=80=94=20the=20?= =?UTF-8?q?mesh=20builds=20its=20own=20floor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloned alone, built by the mesh, every module moved onto it, and the base then changed for real: all three went stale naming what moved. A comment-only change correctly makes nothing stale, which found a second bug — staleness compared commits where it should compare artifacts. --- .../00-report.md | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md b/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md index d4d773b..f5ca865 100644 --- a/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md +++ b/04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md @@ -1,8 +1,8 @@ --- -status: open +status: resolved opened: 2026-09-13 -located-in: [] -fixed-by: +located-in: [mesh-tools, mesh-sdk] +fixed-by: mesh-tools feat/run-once-entry, mesh-sdk feat/adr-ref-resync amended-design: --- @@ -71,3 +71,42 @@ the mesh can produce it too, because that is all the mesh does. the builder is a compiled program built from its own sources and a language toolchain, and does not stand on this base at all. Only modules written in the mesh's scripted toolchain do. So the thing that would build the base is not one of the things built on it, and the chain has an end. + +## Resolved, 2026-09-13 + +The base is a module now, built from its own repository and nothing else. + +Three things were in the way, and the third was not visible until the first two were gone: + +- **The toolkit could not be fetched.** It is named by a pinned commit of its own repository rather + than by a folder on somebody's disk. Both repositories turned out to be readable without a + credential, so no package registry was needed after all — which is why this was smaller than it + looked. +- **The toolkit shipped no compiled output.** Every one of its entry points pointed into a directory + that is not in source control. It declares the hook that is meant to compile it on install, and + the package manager in use does not run that hook — so the build compiles it explicitly instead. + Relying on a hook firing would have failed the same invisible way this issue is about. +- **The base is also the compile environment.** Every module's recipe starts from this image and + invokes the compiler out of it. A first attempt dropped the build tools to make a smaller image, + which broke every module built on it. They stay, and the recipe now says why. + +The drift this issue reported is also closed: the recipe claimed one operating system while serving +another, and now states what is actually in service. Changing that was deliberately *not* folded in +— a module already builds against it with that system's package manager, and moving the ground +under every module at the same moment as making it buildable would be two changes wearing one +commit. + +**Checked the way the report said it should be.** The repository was cloned alone, with nothing +beside it, and built by the mesh. Then every module was moved onto the result, and the base changed +for real: all three went stale, each naming the base and the commits it moved between, each listed +once. Changing only a comment in the base — a new commit, a byte-identical image — correctly makes +nothing stale, which is a second bug this exercise found and fixed: staleness was comparing commits +when it should compare artifacts, so the mesh would have rebuilt itself entirely to arrive back +exactly where it started. + +## What is still true + +A module names its base as a literal fingerprint in its own recipe. So the mesh can now *say* that +three modules must be rebuilt, and rebuilding them still produces the old base until somebody edits +that fingerprint. Noticing is solved; acting on it is the build chain driving itself, which is +separate work. From 9f5ac38662b19a177ab45a8b8dd0fc882bdccf87 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 13 Sep 2026 03:20:07 +0200 Subject: [PATCH 4/7] Settle how the builder arrives, and what it publishes into MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two questions the design record named as the one gap stopping a fresh mesh from producing anything. They cannot be answered apart: a builder with nowhere to publish has made a file on a disk. The registry's role did not change — the answer to 'must it precede the control plane' did, because the control plane's image is now produced rather than carried, and a produced image must be put somewhere before it can be fetched. --- .../0073-the-installer-carries-a-builder.md | 92 +++++++++++++++++++ 02-DECISIONS/README.md | 1 + 2 files changed, 93 insertions(+) create mode 100644 02-DECISIONS/0073-the-installer-carries-a-builder.md diff --git a/02-DECISIONS/0073-the-installer-carries-a-builder.md b/02-DECISIONS/0073-the-installer-carries-a-builder.md new file mode 100644 index 0000000..c61b369 --- /dev/null +++ b/02-DECISIONS/0073-the-installer-carries-a-builder.md @@ -0,0 +1,92 @@ +--- +topic: the tiers +status: accepted +date: 2026-09-13 +deciders: jochen +reconstructed: false +extends: 0070-the-catalogue-owns-the-module-graph.md +--- + +# 73. The installer carries a builder, and the registry precedes the control plane + +## Context + +[ADR 0070](0070-the-catalogue-owns-the-module-graph.md) decided that genesis builds rather than +carries, and [ADR 0071](0071-where-genesis-gets-its-source.md) settled where it clones from. Two +questions were left open, and the design record names them as the one gap that stops a fresh mesh +from being able to produce anything at all: **how the builder arrives**, and **what it publishes +into**. + +They cannot be answered apart. A builder that arrives with nowhere to publish has produced a file +on a disk, and a place to publish with nothing to produce is an empty shelf. + +Today the installer carries the control plane's image inside itself. That works, and it is why +genesis needs no registry: nothing is ever pulled, because the one image that matters is already +present. The cost is that the mesh which results holds an artifact it did not make, cannot rebuild, +and knows nothing about — no version, no source, no edges. That is the same shape as the fault +[issue 044](../04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md) +recorded for the shared runtime, and fixing it there while shipping it here on every new mesh would +be a strange place to stop. + +## Decision + +**The installer carries a builder, and nothing else.** One artifact, not a growing set. It clones +the source at a named commit, checks what it got ([ADR 0071](0071-where-genesis-gets-its-source.md)), +and produces the control plane from the same repository and path that any later rebuild of it would +use. What raises the mesh is therefore the same thing that will maintain it, and there is no second +mechanism kept in step with the first. + +**The registry joins the substrate, and precedes the control plane.** Not because it became more +fundamental, but because the answer to a stated question changed: + +| | is it substrate? | must it precede the control plane? | +|---|---|---| +| the store | yes | yes — there is nowhere else to put the control plane's state | +| the broker | yes | yes — the control plane reaches a machine only over it | +| the image registry | yes — it cannot grant itself a repository | **yes, now** — the control plane's image is produced here, and a produced image has to be put somewhere before anything can fetch it | + +[ADR 0033](0033-the-substrate-is-a-store-and-a-broker.md) answered that last cell *no*, and was +right at the time, for the reason it gave: the first machine fetched the control plane from +upstream, so nothing needed a registry until there was already a control plane to install one. That +premise is what this decision removes. The registry's role never changed; what changed is whether +anything needs it before the control plane exists. + +**The substrate bundle therefore carries three services and no control plane**, where it carried +two services and a control plane. It does not grow: an image comes out as one goes in. + +## Consequences + +**Genesis gains a step and loses one.** The registry is raised with the substrate rather than +installed as the first act of a temporary control plane, and a build step appears before the +control plane is raised at all. The pivot described in +[ADR 0067](0067-genesis-is-a-pivot.md) survives unchanged in shape — a temporary control plane is +still what installs the permanent one — but what it installs is now something this mesh built. + +**A fresh mesh can produce from the moment it exists.** The builder is present before the control +plane is, so the core modules, the catalogue and the builder's own module can be built in the +ordinary way rather than waiting for somebody to carry them in. The paragraphs in +[`17-raising-a-mesh`](../03-DESIGN/01-to-be/17-raising-a-mesh.md) that describe this were describing +something that could not start; they can start now. + +**Genesis needs more of the outside world.** Carrying an image needed nothing but the installer. +Building one needs the source, and whatever the build itself reaches for. This is a real cost and +is not waved away: it makes genesis fail in more ways, all of them at a step that says what it was +doing. It is accepted because the alternative is a mesh that cannot rebuild its own control plane, +which fails in exactly one way, silently, later, and for ever. + +**A pre-built bundle remains possible and is not this.** Nothing here forbids delivering artifacts +rather than building them; it fixes where they may come from. A bundle of pre-built core modules is +an **export of a mesh that built them**, carrying what the catalogue knows about each alongside the +artifact itself — so that loading one leaves the graph in the state building would have left it. A +bundle that carries images without that is the thing this decision rejects, whoever ships it. + +## What this does not decide + +**Whether the builder's own module is carried or built.** It builds everything else; what installs +*it* as an ordinary module afterwards, so that it too can be upgraded, is the same closed-list +question [`12-a-module-repository`](../03-DESIGN/01-to-be/12-a-module-repository.md) already holds, +and is unchanged by this. + +**How a machine authenticates to a registry that asks it to.** Genesis raises its own and reaches it +over the loopback, so this remains a joining problem +([issue 042](../04-ISSUES/042-nothing-gives-a-node-an-account-for-a-registry/00-report.md)). diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 758393a..b9b864a 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -105,6 +105,7 @@ python3 00-META/checks/index.py fail if stale - **0070** — [The catalogue owns the module graph, and genesis builds rather than carries](0070-the-catalogue-owns-the-module-graph.md) - **0071** — [Genesis clones from a mesh, and checks what it got](0071-where-genesis-gets-its-source.md) - **0072** — [Two graphs, and a build chain that orders itself](0072-two-graphs-and-the-build-chain.md) +- **0073** — [The installer carries a builder, and the registry precedes the control plane](0073-the-installer-carries-a-builder.md) ### What runs on them, and how it gets there From aabaaf2bb4af48fbc2373ab60bc8fcb193eb622f Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 13 Sep 2026 03:22:05 +0200 Subject: [PATCH 5/7] Rewrite 0073: the registry does not move, and the argument fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Written an hour ago claiming a produced image must be published before anything can fetch it, so the registry had to precede the control plane. The premise is false: the machine that builds the image is the machine that runs it, and the temporary control plane names a built image exactly as it names a carried one — by the digest of its own configuration, which requires nothing to have served it. Building changes where the bytes came from, not where they are. Rewritten rather than superseded because nothing has been built on it and nobody has read it: a record that contradicts itself is a draft, not a decision. The argument is kept, because it was asked for and a negative answer is the result. --- .../0073-the-installer-carries-a-builder.md | 44 ++++++++++--------- 02-DECISIONS/README.md | 2 +- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/02-DECISIONS/0073-the-installer-carries-a-builder.md b/02-DECISIONS/0073-the-installer-carries-a-builder.md index c61b369..47fbc8a 100644 --- a/02-DECISIONS/0073-the-installer-carries-a-builder.md +++ b/02-DECISIONS/0073-the-installer-carries-a-builder.md @@ -7,7 +7,7 @@ reconstructed: false extends: 0070-the-catalogue-owns-the-module-graph.md --- -# 73. The installer carries a builder, and the registry precedes the control plane +# 73. The installer carries a builder, and the registry stays where it is ## Context @@ -17,11 +17,8 @@ questions were left open, and the design record names them as the one gap that s from being able to produce anything at all: **how the builder arrives**, and **what it publishes into**. -They cannot be answered apart. A builder that arrives with nowhere to publish has produced a file -on a disk, and a place to publish with nothing to produce is an empty shelf. - Today the installer carries the control plane's image inside itself. That works, and it is why -genesis needs no registry: nothing is ever pulled, because the one image that matters is already +genesis needs no registry: nothing is ever fetched, because the one image that matters is already present. The cost is that the mesh which results holds an artifact it did not make, cannot rebuild, and knows nothing about — no version, no source, no edges. That is the same shape as the fault [issue 044](../04-ISSUES/044-the-runtime-every-module-builds-on-cannot-be-built-by-the-mesh/00-report.md) @@ -36,31 +33,36 @@ and produces the control plane from the same repository and path that any later use. What raises the mesh is therefore the same thing that will maintain it, and there is no second mechanism kept in step with the first. -**The registry joins the substrate, and precedes the control plane.** Not because it became more -fundamental, but because the answer to a stated question changed: +**The registry does not move, and the argument for moving it does not survive being made.** + +It was put this way: a produced image has to be put somewhere before anything can fetch it, so the +registry must now precede the control plane, and +[ADR 0033](0033-the-substrate-is-a-store-and-a-broker.md)'s answer to that question has to flip. + +It does not, because the premise is false. **The thing that builds the image and the machine that +runs it are the same machine.** A built image is already in that machine's container runtime, and +the temporary control plane names it exactly as it names a carried one — by the digest of its own +configuration, a local identity that requires nothing to have served it. Building changes where the +bytes came from. It does not change where they are. | | is it substrate? | must it precede the control plane? | |---|---|---| | the store | yes | yes — there is nowhere else to put the control plane's state | | the broker | yes | yes — the control plane reaches a machine only over it | -| the image registry | yes — it cannot grant itself a repository | **yes, now** — the control plane's image is produced here, and a produced image has to be put somewhere before anything can fetch it | +| the image registry | yes — it cannot grant itself a repository | **still no** — the first machine neither fetches the control plane nor needs to, whether the image was carried in or made here | -[ADR 0033](0033-the-substrate-is-a-store-and-a-broker.md) answered that last cell *no*, and was -right at the time, for the reason it gave: the first machine fetched the control plane from -upstream, so nothing needed a registry until there was already a control plane to install one. That -premise is what this decision removes. The registry's role never changed; what changed is whether -anything needs it before the control plane exists. - -**The substrate bundle therefore carries three services and no control plane**, where it carried -two services and a control plane. It does not grow: an image comes out as one goes in. +So the registry stays where [ADR 0033](0033-the-substrate-is-a-store-and-a-broker.md) put it: +substrate by role, ordinary by delivery, installed by the temporary control plane as its first act. +The bundle carries two services and a control plane, as it did. **What publishes into the registry +is unchanged too** — the existing step that pushes the control plane's image into it, which is the +moment that image first receives a digest assigned by something other than itself. It now pushes +something this mesh built rather than something it was handed. ## Consequences -**Genesis gains a step and loses one.** The registry is raised with the substrate rather than -installed as the first act of a temporary control plane, and a build step appears before the -control plane is raised at all. The pivot described in -[ADR 0067](0067-genesis-is-a-pivot.md) survives unchanged in shape — a temporary control plane is -still what installs the permanent one — but what it installs is now something this mesh built. +**Genesis gains one step and changes no others.** A build happens before the image is loaded. The +pivot described in [ADR 0067](0067-genesis-is-a-pivot.md) survives exactly as written, because the +step it pivots on never cared where the image came from. **A fresh mesh can produce from the moment it exists.** The builder is present before the control plane is, so the core modules, the catalogue and the builder's own module can be built in the diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index b9b864a..31bc462 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -105,7 +105,7 @@ python3 00-META/checks/index.py fail if stale - **0070** — [The catalogue owns the module graph, and genesis builds rather than carries](0070-the-catalogue-owns-the-module-graph.md) - **0071** — [Genesis clones from a mesh, and checks what it got](0071-where-genesis-gets-its-source.md) - **0072** — [Two graphs, and a build chain that orders itself](0072-two-graphs-and-the-build-chain.md) -- **0073** — [The installer carries a builder, and the registry precedes the control plane](0073-the-installer-carries-a-builder.md) +- **0073** — [The installer carries a builder, and the registry stays where it is](0073-the-installer-carries-a-builder.md) ### What runs on them, and how it gets there From fdc61054e38be811e3c71492444637e041219b3f Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 13 Sep 2026 04:26:01 +0200 Subject: [PATCH 6/7] Genesis carries a builder, and the document says so The section saying the change was decided and had not happened now contradicted the section below it. It also records the argument that failed, because a reader will otherwise ask the same question and reach the same wrong answer. --- 03-DESIGN/01-to-be/17-raising-a-mesh.md | 46 +++++++++++++++---------- 1 file changed, 28 insertions(+), 18 deletions(-) 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 c3d3680..a6b579c 100644 --- a/03-DESIGN/01-to-be/17-raising-a-mesh.md +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -36,34 +36,44 @@ 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 +## What changed, and what did not -*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. +*2026-09-13.* The installer carries a builder now, and builds the control plane it raises. Three +records settle it: [ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md) that +genesis builds rather than carries, [ADR 0071](../../02-DECISIONS/0071-where-genesis-gets-its-source.md) +where it clones from, and [ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md) how +the builder arrives — which also records an argument that failed. It was put that a produced image +must be published before anything can fetch it, so the registry would have to come up before the +control plane. It does not: the machine that builds the image is the machine that runs it, and a +local image is named by the digest of its own configuration exactly as a carried one is. **Building +changes where the bytes came from, not where they are.** -**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. +So the pivot is unchanged, the registry is where it was, and one step was added before the bundle is +written. What follows describes the program that exists. ## Genesis -The installer is a single program carrying the control plane's image inside it. That is what makes -genesis possible without a network to fetch from and without a registry to name: the image is -present because the installer is present. +The installer is a single program carrying **the builder** inside it — not the control plane +([ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md)). What cannot be fetched is +the thing that does the fetching, so that is what is carried; everything else is made here. It proceeds in one direction, and every step is safe to run again. **First it refuses to start if the machine is not ready.** A container runtime, the ability to -write where it must write, the host binary where it expects it. A machine that is not ready is told -what is missing rather than half-changed. +write where it must write, the host binary where it expects it — and a repository and a commit to +build from, because an installer told nothing would raise a store and a broker and then have +nothing to raise a control plane from. A machine that is not ready is told what is missing rather +than half-changed. -**Then it loads the carried image and describes what the machine will become.** The image is named -by the digest of its own configuration — content-addressed and unforgeable, and requiring nothing -to have served it. This is legal precisely where nothing could have served one, and nowhere else. +**Then it loads the carried builder and builds the control plane with it**, from a repository on a +mesh that already exists and a named commit ([ADR 0071](../../02-DECISIONS/0071-where-genesis-gets-its-source.md)). +This is the same repository and path every later rebuild of the control plane will use, so what +raises the mesh is the same thing that will maintain it. + +**Then it describes what the machine will become.** The image it just made is named by the digest +of its own configuration — content-addressed and unforgeable, and requiring nothing to have served +it. That is legal precisely where nothing could have served one, and it is why building here needs +no registry: the machine that made the image is the machine that will run it. **Then it raises the substrate and a temporary control plane, and waits for that control plane to answer.** At this point the machine is a mesh of one node with nothing joined to it. From 8e7fac8bf161d05e50420047986bcfb8142f5ff7 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 13 Sep 2026 06:09:31 +0200 Subject: [PATCH 7/7] Record what genesis now does, and how each rule is checked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The builder's arrival was the one rule the document said nothing checked. It is checked now, by both genesis beds — and so is the thing that distinguishes a built control plane from a carried one, which every earlier assertion accepted either way. --- 03-DESIGN/01-to-be/17-raising-a-mesh.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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 a6b579c..069f4de 100644 --- a/03-DESIGN/01-to-be/17-raising-a-mesh.md +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -159,9 +159,16 @@ 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 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. +**The builder's own module is not installed by genesis.** The installer carries a builder and uses +it, so a fresh mesh is raised on something it built; but nothing afterwards installs that builder as +an ordinary module on the machine, so the mesh cannot yet be asked to build anything else. The +paragraphs above describing the core modules being built can start now — a builder exists and has +somewhere to publish — and nothing yet starts them. + +**A module's declaration still has to be copied onto the machine by hand.** The installer reads the +registry's and the control plane's manifests from a checkout somebody put there. The control plane's +now lives in the control plane's own repository, which the installer clones anyway, so this is a +thing that can be removed rather than a thing that must be designed. **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 @@ -177,6 +184,7 @@ pulling problem, not a genesis one. | Every step may be run again | The installer is re-run against a raised machine and must change nothing and report why. | | An image is named exactly | A machine refuses a bundle naming an image by tag. The refusal is exercised, not assumed. | | The installer is what installed this | **Nothing.** See above. | -| The builder can arrive on a fresh mesh | **Nothing.** There is no route for it yet. | +| The builder can arrive on a fresh mesh | The installer carries it, and the genesis bed raises a machine by running the installer. A bed that raises one any other way fails its own acceptance check. | +| The control plane a mesh runs is one it built | The genesis bed asserts the running control plane is pinned to a digest this mesh's own registry serves, for an image built from a named repository and commit — not one the installer carried. | | A core module is built rather than only carried | The control plane is rebuilt from its own repository and path, and the running mesh is upgraded to the result — the same path any module takes. | | Installing produced a mesh that can produce | After installing, a module with source of its own is asked for and comes back pinned to a digest this mesh's registry assigned, not to a placeholder. |