|
|
|
@@ -0,0 +1,158 @@
|
|
|
|
|
---
|
|
|
|
|
topic: the mesh
|
|
|
|
|
status: accepted
|
|
|
|
|
date: 2026-09-29
|
|
|
|
|
deciders: jochen
|
|
|
|
|
reconstructed: false
|
|
|
|
|
extends: 02-DECISIONS/0141-the-host-delivers-its-own-successor.md
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 142. The mesh delivers its own components as binaries, not as container images
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
Measured on the control-node, 2026-09-29:
|
|
|
|
|
|
|
|
|
|
| what | how it runs | publishes |
|
|
|
|
|
|---|---|---|
|
|
|
|
|
| host | a binary on the machine | — |
|
|
|
|
|
| controller, catalogue, builder, vault | containers | nothing |
|
|
|
|
|
| store, registry, broker | containers | ports |
|
|
|
|
|
|
|
|
|
|
**The mesh's own software is delivered two ways, and the difference is not a property of the
|
|
|
|
|
software.** The host and the controller are both written in the same language, both the mesh's own,
|
|
|
|
|
both doing the mesh's own work. One is an image fetched from a registry. The other is a file somebody
|
|
|
|
|
copied to four machines, owned by no package, built by nothing
|
|
|
|
|
([issue 142](../04-ISSUES/142-the-host-is-the-one-thing-the-mesh-does-not-deliver/00-report.md)).
|
|
|
|
|
|
|
|
|
|
**The reason is not a judgement about either, it is that images are the only delivery that works.**
|
|
|
|
|
There is no way to put a binary on a machine. The host is hand-copied because of that, and the
|
|
|
|
|
controller is an image because of that. Neither was chosen on its merits.
|
|
|
|
|
|
|
|
|
|
What it costs, all of it measured rather than argued:
|
|
|
|
|
|
|
|
|
|
- **Genesis must raise a container runtime before the control plane can exist.** The bundle carries
|
|
|
|
|
three images and one of them is the controller, *"in the bundle for the same reason they are: there
|
|
|
|
|
is nothing to fetch it with yet"*
|
|
|
|
|
([design 07](../03-DESIGN/01-to-be/07-the-foundation.md)). So the hardest moment in the mesh's life
|
|
|
|
|
has a prerequisite that the thing being started does not need.
|
|
|
|
|
- **Updating the control plane depends on the control plane.** Its image is fetched from the registry,
|
|
|
|
|
which is a container the controller manages.
|
|
|
|
|
- **A change to the host cannot be rolled out at all.** Every machine here runs a byte-identical
|
|
|
|
|
hand-copied binary. A change merged yesterday reached none of them.
|
|
|
|
|
- **Compiling the language the mesh is written in is not a capability of the builder.** The bundle
|
|
|
|
|
toolchains are typescript — real, with a registered base module — and python, which is named in the
|
|
|
|
|
list and absent from the catalogue. The controller is built as an image from a Dockerfile, which is
|
|
|
|
|
the per-repository incantation the bundle toolchain exists to abolish
|
|
|
|
|
([design 18](../03-DESIGN/01-to-be/18-building-a-module.md)).
|
|
|
|
|
|
|
|
|
|
The half that *receives* a binary safely is already built and tested
|
|
|
|
|
([ADR 0141](0141-the-host-delivers-its-own-successor.md)): versions side by side in directories named
|
|
|
|
|
for them, the newest run, the running one standing aside between reconciles, retirement keeping the
|
|
|
|
|
predecessor, and a rollback that chooses a directory. What is missing is everything that puts one
|
|
|
|
|
there.
|
|
|
|
|
|
|
|
|
|
## Considered Options
|
|
|
|
|
|
|
|
|
|
1. **Leave it as it is.** Rejected: it is not a design, it is the reach of one mechanism. And it is
|
|
|
|
|
what makes a host change undeliverable.
|
|
|
|
|
2. **Containerise the host too**, so everything is delivered one way. Rejected: the host is what
|
|
|
|
|
starts the container runtime and what applies containers. A host in a container is the bootstrap
|
|
|
|
|
problem made total, and the machine would have no way back from a bad one.
|
|
|
|
|
3. **Deliver the mesh's components as operating-system packages.** Rejected for the reason
|
|
|
|
|
[ADR 0141](0141-the-host-delivers-its-own-successor.md) rejected it for the host: a package and a
|
|
|
|
|
trusted repository per operating system, three of each, and the `package` resource asserts presence
|
|
|
|
|
and deliberately never a version.
|
|
|
|
|
4. **Binaries for the mesh's own components, containers for third-party software.** Adopted.
|
|
|
|
|
|
|
|
|
|
## Decision
|
|
|
|
|
|
|
|
|
|
**The mesh's own components are delivered as binaries on the machine.** The host, the controller, the
|
|
|
|
|
catalogue, the builder, the vault — the software this project writes. They are delivered by the
|
|
|
|
|
mechanism [ADR 0141](0141-the-host-delivers-its-own-successor.md) built: an archive, fetched by
|
|
|
|
|
digest, unpacked into a directory named for its version, with the running one standing aside between
|
|
|
|
|
reconciles and a rollback that chooses the predecessor.
|
|
|
|
|
|
|
|
|
|
**Third-party software stays a container.** The store, the registry, the broker. They are somebody
|
|
|
|
|
else's build, they are already adopted as modules
|
|
|
|
|
([ADR 0078](0078-the-store-and-broker-are-modules.md)), and an image is the right way to carry
|
|
|
|
|
somebody else's software. **The container runtime remains required** — modules use it — so this
|
|
|
|
|
removes a dependency from the control plane, not from the machine.
|
|
|
|
|
|
|
|
|
|
**The builder compiles the languages the mesh is written in.** A toolchain for Go, with a base module
|
|
|
|
|
providing the compiler, exactly as typescript has. The obligation the toolchain list warns about — an
|
|
|
|
|
SDK carrying the broker client, the envelope and tool serving — attaches to a *module* written in a
|
|
|
|
|
language, not to the language being compilable. None of these components is a module in that sense;
|
|
|
|
|
the host is what applies modules.
|
|
|
|
|
|
|
|
|
|
**An artifact says what it targets.** A compiled binary is per operating system, pinned at link time
|
|
|
|
|
([ADR 0005](0005-the-node-host.md)), and a toolchain deliberately takes nothing from the module,
|
|
|
|
|
because anything a module could override there it would be writing a Dockerfile to override. So the
|
|
|
|
|
target is a property of the artifact rather than of the recipe, and one artifact declared per target
|
|
|
|
|
is one build each.
|
|
|
|
|
|
|
|
|
|
**A component's version comes from where it sits, not from its linker.** It is unpacked into a
|
|
|
|
|
directory named for its version, so it can read its own version from its path. The stamp goes, and
|
|
|
|
|
with it the need for a build to know what it will be called.
|
|
|
|
|
|
|
|
|
|
**Genesis carries a binary reference where it carried an image reference.** The principle does not
|
|
|
|
|
change — the bundle names a thing by digest and the host fetches it, pinned because nothing can
|
|
|
|
|
resolve a version when no mesh exists — and the container runtime stops being a prerequisite for the
|
|
|
|
|
control plane. It stays a prerequisite for the store and the broker, which is where it belongs.
|
|
|
|
|
|
|
|
|
|
**The order is staged, and each step stands alone.** Compiling Go; an artifact naming its target;
|
|
|
|
|
delivering a binary; the host as the first component delivered; the controller, catalogue, builder and
|
|
|
|
|
vault out of their containers; genesis last. Genesis is last for the reason it is always last: it
|
|
|
|
|
matters for a machine nobody has yet, and every earlier step is provable on a mesh that exists.
|
|
|
|
|
|
|
|
|
|
## Consequences
|
|
|
|
|
|
|
|
|
|
- **One delivery for the mesh's own software**, so a change to the host ships the way a change to the
|
|
|
|
|
controller does, and neither is copied by hand.
|
|
|
|
|
- **The control plane stops depending on a container runtime and on its own registry.** Both remain on
|
|
|
|
|
the machine for other reasons; neither gates the control plane's own life any more.
|
|
|
|
|
- **`Replaced()`, the known-good record and the launcher's rollback stop being dead code.** They were
|
|
|
|
|
written for this and have been called by nothing but their tests.
|
|
|
|
|
- **Four more components gain a rollback they do not have.** Today a bad controller image is recovered
|
|
|
|
|
by an operator; under this it is recovered the way a bad host is.
|
|
|
|
|
- **Two versions of each component occupy disk.** Around nine megabytes each. The predecessor is what a
|
|
|
|
|
rollback needs.
|
|
|
|
|
- **Genesis gets smaller, not larger.** One fewer image to carry and one fewer runtime to raise before
|
|
|
|
|
the control plane.
|
|
|
|
|
- **This does not make the components smaller or simpler.** They are the same programs; what changes is
|
|
|
|
|
how they arrive. A reader expecting the containers to have been hiding complexity will not find any.
|
|
|
|
|
- **What got harder:** the builder gains a language, artifacts gain a target, and the mesh gains a
|
|
|
|
|
second kind of thing it must deliver correctly — one where getting it wrong takes the control plane
|
|
|
|
|
down rather than a module. That is why the host is first: it is the component whose recovery is
|
|
|
|
|
already built and tested.
|
|
|
|
|
|
|
|
|
|
## How it is checked
|
|
|
|
|
|
|
|
|
|
- **A component is delivered and runs, with nothing copied by hand.** A bed builds the host from its
|
|
|
|
|
repository, delivers it to a machine running an older one, and the machine reports the new version.
|
|
|
|
|
This fails today at the first step, because nothing builds it.
|
|
|
|
|
- **Each target is built once and only the matching one is delivered.** Asserted by declaring an
|
|
|
|
|
artifact per operating system and checking that a machine is offered the one it can run — a host
|
|
|
|
|
built for another is what ADR 0005's link-time pin exists to refuse.
|
|
|
|
|
- **A component reads its version from its path**, asserted by unpacking the same bytes into two
|
|
|
|
|
differently named directories and seeing each report its own.
|
|
|
|
|
- **A bad component is rolled back without an operator**, for the host first: a version that will not
|
|
|
|
|
start is replaced by its predecessor once, and the second failure halts naming the machine.
|
|
|
|
|
- **The control plane comes up with no registry reachable**, which is the dependency this removes —
|
|
|
|
|
asserted by raising it with the registry stopped.
|
|
|
|
|
- **Genesis raises a control plane with no container runtime running**, and raises the store and the
|
|
|
|
|
broker afterwards. Last, and on a machine with nothing on it.
|
|
|
|
|
- **A published port count that does not change.** The mesh's own components publish nothing today, so
|
|
|
|
|
moving them out of containers must not open anything — asserted on the machine's reachable set before
|
|
|
|
|
and after, which the converge preview already reads.
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
|
|
- [ADR 0141](0141-the-host-delivers-its-own-successor.md) — the receiving half, already built
|
|
|
|
|
- [ADR 0005](0005-the-node-host.md) — the host, its supervision, and one binary per operating system
|
|
|
|
|
- [ADR 0078](0078-the-store-and-broker-are-modules.md) — why third-party software stays a container
|
|
|
|
|
- [ADR 0006](0006-the-substrate-and-the-control-plane.md) — what genesis must raise, and in what order
|
|
|
|
|
- [issue 142](../04-ISSUES/142-the-host-is-the-one-thing-the-mesh-does-not-deliver/00-report.md) — the
|
|
|
|
|
measurement that started this
|
|
|
|
|
- [design 07](../03-DESIGN/01-to-be/07-the-foundation.md) — the bundle's three images, one of them the
|
|
|
|
|
controller
|