ADR 0200: genesis pivots to the controller as a container, and the first push hands it to a process #346

Merged
mesh-admin merged 1 commits from decision/0200-genesis-pivots-to-a-container-and-hands-over into main 2026-10-03 23:41:37 +00:00
3 changed files with 86 additions and 1 deletions
@@ -0,0 +1,78 @@
---
topic: building it
status: accepted
date: 2026-10-04
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0067-genesis-is-a-pivot.md
---
# 200. Genesis pivots to the controller as a container, and the first push hands it to a process
## Context
The controller is Go, compiled to one static binary, and is the last of the mesh's own programs a
machine runs from an image ([issue 213](../04-ISSUES/213-the-controller-is-a-go-program-run-in-a-container/00-report.md)).
[ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)
§1 says a module's own code is bundles, never an image, and §3 that a service bundle is a `process` the
host runs. The handover exists: a process may name the container it `replaces`, and the host removes
that container only after the process has stayed up across two checks; two controllers are safe
together for that moment, the second standing by on the controller's consumers and every plan held by
one lock.
What stands in the way is genesis ([ADR 0067](0067-genesis-is-a-pivot.md)), which
[issue 223](../04-ISSUES/223-a-new-mesh-installs-its-controller-as-a-container/00-report.md) found
assumes an image and a container at every step from its third: it builds the controller's image,
starts a temporary controller from it, publishes it, finds the controller's container in the pivot
declaration, and from then on talks to the controller through it. A process's bundle is fetched from
the artifact store, and genesis raises the artifact store only after the pivot.
## Considered Options
1. **Raise the artifact store before the pivot**, publish the controller's bundle to it, and talk to
the controller from the host's side. Rejected for now: it reorders genesis around a store that is
itself a module the controller deploys, and rewrites the steps that talk to the controller — a
larger change to the one path that is exercised least, to remove a container that exists for
minutes.
2. **Pivot to the controller as a container, as today, and let the first push hand it over to the
process**, through the handover that already exists. Chosen.
3. **Keep the controller a container.** Rejected: it is the exception to ADR 0188 that every other
module's code has now left, and it costs a container runtime on the control machine and a
container recreation in the middle of a plan.
## Decision
**Genesis raises the controller as a container, under the resource the controller's process
`replaces`, and the first declaration the controller composes for its own machine hands it over.**
The container is genesis's own shape, built from the controller's repository, and is recorded on the
control machine exactly as the manifest's `replaces` names it, so the first apply after the pivot
finds a replacement for it and removes it once the process is up. The controller's manifest declares
only the process; the image form exists for genesis alone and is not a second way to run the
controller on a live mesh.
This is the one bounded exception to ADR 0188 §1: a module's own code in an image, for the minutes
between the pivot and the first push, on a mesh being created.
## Consequences
- A new mesh ends where a running one is: the controller a process, no controller container.
- Genesis keeps its steps; what changes is that it no longer reads the controller's container from the
manifest, and that it records the container under the name the handover expects.
- The controller's repository keeps its image build for genesis and the lab.
- The handover is now on genesis's path too: a process that fails to stay up leaves the genesis
container serving, and the apply says so — the same rule as on a live mesh.
## How it is checked
The installer's test raises a mesh whose controller manifest is the process form, and asserts that the
container genesis recorded is exactly what the process `replaces`, so the first apply hands over and
leaves one controller. Live, on the running mesh: after the manifest change is pushed, the control
machine runs the controller as a process and no controller container, and the controller's seat
answers throughout.
## References
- [Issue 213](../04-ISSUES/213-the-controller-is-a-go-program-run-in-a-container/00-report.md),
[issue 223](../04-ISSUES/223-a-new-mesh-installs-its-controller-as-a-container/00-report.md)
- mesh-host#86 (the handover), mesh-controller#252 (two controllers safe together),
mesh-controller#253 (the controller's manifest as a process)
+1
View File
@@ -320,6 +320,7 @@ python3 00-META/checks/index.py fail if stale
- **0111** — [A build source is on the mesh's git seat, or it is an external repository](0111-a-build-source-is-on-the-git-seat-or-external.md)
- **0149** — [The live mesh is the test bed](0149-the-live-mesh-is-the-test-bed.md)
- **0174** — [A node varies a module through settings and kept regions, never through an edit](0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)
- **0200** — [Genesis pivots to the controller as a container, and the first push hands it to a process](0200-genesis-pivots-to-the-controller-as-a-container-and-the-first-push-hands-it-to-a-process.md)
### How it is checked
@@ -1,5 +1,5 @@
---
status: open
status: located
opened: 2026-10-04
located-in:
- mesh-host
@@ -40,3 +40,9 @@ Until it is settled, the change of the controller's manifest (mesh-controller#25
handover itself is built and merged (mesh-host#86); the controller's half (mesh-controller#252) waits
on the operator. **How it is checked:** the installer's test raises a mesh whose controller manifest
is the process form, and the controller answers its seat's verbs at the end.
## Decided (2026-10-04)
Option 2, [ADR 0200](../../02-DECISIONS/0200-genesis-pivots-to-the-controller-as-a-container-and-the-first-push-hands-it-to-a-process.md):
genesis pivots to the controller as a container recorded under the name the process `replaces`, and
the first push hands it over.