From 92e8c74ce45da0c82b61a9d7410a6b2d1c708197 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 26 Aug 2026 00:16:07 +0200 Subject: [PATCH] ADR 0041 and the build handoff for the node host MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building tier 0 forced the question "the one binary installed by hand" had been carrying unexamined. A TypeScript host needs a runtime present before it runs, so the thing installed by hand becomes two — and the second must be installed by the means the host exists to replace. So the host is a statically linked binary that requires nothing present, written in Go. Rejected: a runtime installed first, which breaks the property the tier rests on; and bundling the runtime into the executable, which carries ninety megabytes to preserve a language choice and puts a young feature at the bottom of the stack. The argument that decided it is architectural rather than about taste. 0037 means the host never queries the mesh database and 0039 means it only receives declarations, so the host shares NO code with any other tier — not a client, not a schema, not the SDK. The language boundary falls exactly on a boundary that already exists, and a second language usually costs duplicated logic where here there is none to duplicate. §8 gains a scope: it said "TypeScript throughout" when everything was a service or a surface, and is now scoped to those with tier 0 named. Another sync owed. Playbook 04 steps 2 and 4: repos.md records mesh-host as existing, the design takes code: [mesh-host] and status: in-progress. --- 00-META/how-we-build.md | 4 + 00-META/repos.md | 4 +- .../0041-the-host-depends-on-nothing.md | 78 +++++++++++++++++++ 03-DESIGN/01-to-be/05-the-node-host.md | 10 ++- 4 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 02-DECISIONS/0041-the-host-depends-on-nothing.md diff --git a/00-META/how-we-build.md b/00-META/how-we-build.md index c7ee824..ddeed6b 100644 --- a/00-META/how-we-build.md +++ b/00-META/how-we-build.md @@ -268,6 +268,10 @@ Data access, business logic and the interface layer are separate. ### Types +*Scope: the mesh's services and surfaces. Tier 0 is a statically linked binary that must depend +on nothing installed first, and is written in Go — +[ADR 0041](../02-DECISIONS/0041-the-host-depends-on-nothing.md).* + - TypeScript throughout; no new untyped JavaScript. - Strict, with no implicit `any` and no unchecked index access. - Public functions state their return type. Prefer `unknown` with a guard over `any`. Errors diff --git a/00-META/repos.md b/00-META/repos.md index 7acec68..87e954b 100644 --- a/00-META/repos.md +++ b/00-META/repos.md @@ -21,13 +21,13 @@ and a forge address is an operational detail (see [`README`](../README.md)). ## What the mesh becomes [ADR 0030](../02-DECISIONS/0030-the-repository-structure.md) records the repositories the -monorepo decomposes into. **Only `mesh-lab` exists so far** — it is built first +monorepo decomposes into. **`mesh-lab` and `mesh-host` exist so far** — the lab is built first ([ADR 0029](../02-DECISIONS/0029-the-labs-first-scenario-has-no-pipeline.md)); the rest are the target, not the present. | Repository | Tier | Holds | |---|---|---| -| `mesh-host` | 0 | the node host — the one binary installed by hand | +| `mesh-host` | 0 | **exists.** The node host — one statically linked binary, requiring nothing present ([ADR 0041](../02-DECISIONS/0041-the-host-depends-on-nothing.md)) | | `mesh-substrate` | 1 | the four pinned services, as declarations | | `mesh-control` | 2 | the control plane and its contexts | | `mesh-surfaces` | 3 | tools, web, cli | diff --git a/02-DECISIONS/0041-the-host-depends-on-nothing.md b/02-DECISIONS/0041-the-host-depends-on-nothing.md new file mode 100644 index 0000000..ac27923 --- /dev/null +++ b/02-DECISIONS/0041-the-host-depends-on-nothing.md @@ -0,0 +1,78 @@ +--- +status: accepted +date: 2026-08-26 +deciders: jochen +reconstructed: false +extends: 0037-the-host-applies-it-does-not-decide.md +--- + +# 41. The host depends on nothing that must be installed first + +## Context + +[ADR 0030](0030-the-repository-structure.md) calls tier 0 *"the one binary installed by hand"*, +and [research 006](../01-RESEARCH/006-mesh-from-scratch/00-overview.md) states the property the +whole tier rests on: *"a binary whose whole argument is that it has no dependencies"*. + +Building it forced the question that phrase had been carrying unexamined. Everything else in +the mesh is TypeScript, and `how-we-build` §8 says so. A TypeScript host needs a runtime present +before it can run — so the thing installed by hand becomes **two** things, and the second must +be installed by the means the host exists to replace. + +## Considered options + +1. **TypeScript, with a runtime installed first.** Simplest, and matches every other + repository. Rejected: it breaks the property the tier is built on. A host that cannot run + until something else has been installed by hand is not the bottom of the stack. +2. **TypeScript, bundled as a single executable.** Preserves the language and produces one + file. Rejected on two grounds: it carries roughly ninety megabytes of runtime to preserve a + language choice, and single-executable bundling is a young feature — tier 0 is the worst + place in the system to discover its edges. +3. **A statically linked binary in a language built for it.** Chosen; Go. + +## Decision + +**The host is a single statically linked binary that requires nothing to be present.** Copy it +onto a machine and run it. That is the whole installation. + +**It is written in Go.** The job is system-level — run commands, write files, speak to the +firewall, the overlay, the service manager and the package manager — which is what Go's +ecosystem is for, and it cross-compiles to every architecture the mesh might reach, including +the lighter devices requirement 6 anticipates. + +**The second language costs less here than anywhere else it could appear**, and the reason is +architectural rather than convenient. [ADR 0037](0037-the-host-applies-it-does-not-decide.md) +means the host never queries the mesh database. +[ADR 0039](0039-the-link-is-the-security-boundary.md) means it only ever receives declarations. +So the host shares **no code** with any other tier — not a client, not a schema, not the SDK. +It is joined to the mesh by a message contract and nothing else. + +The language boundary therefore falls exactly on an architectural boundary that already exists. +A second language usually costs duplicated logic; here there is none to duplicate. + +## Consequences + +- **`how-we-build` §8 needs a scope.** It reads *"TypeScript throughout"*, which was true when + everything was a service or a surface. It is now scoped to those, with tier 0 named as the + exception and this record as the reason. That is a constitution change, and the sync it owes + is part of it. +- **Agents must write Go to work on the host.** A real cost, and the one genuine argument + against this. It is bounded by the host being the only thing in tier 0 — nothing else in the + mesh acquires a second language because of this. +- **The dependency-direction lint the design calls for gets easier, not harder.** A Go module + cannot accidentally import a TypeScript control-plane client; the boundary is enforced by + there being no path across it. +- **Cross-compilation replaces per-node builds.** The host is built once per architecture and + copied, rather than built on the machine it runs on — which is what makes *"copy it and run + it"* true rather than nearly true. +- **Two toolchains in the lab.** Scenarios that place a host need a Go build available, and the + lab is TypeScript. The binary is built before the scenario runs, not inside it. +- **This is reversible at a cost that will only grow.** It is being taken at the moment the + first line is written, which is the cheapest point it will ever be taken. + +## References + +- [ADR 0030](0030-the-repository-structure.md) — *the one binary installed by hand*. +- [ADR 0037](0037-the-host-applies-it-does-not-decide.md) — why the host shares no code. +- [ADR 0039](0039-the-link-is-the-security-boundary.md) — why it receives declarations only. +- [`05-the-node-host.md`](../03-DESIGN/01-to-be/05-the-node-host.md) — the design this serves. diff --git a/03-DESIGN/01-to-be/05-the-node-host.md b/03-DESIGN/01-to-be/05-the-node-host.md index f7a98ad..db149f3 100644 --- a/03-DESIGN/01-to-be/05-the-node-host.md +++ b/03-DESIGN/01-to-be/05-the-node-host.md @@ -1,7 +1,7 @@ --- layer: to-be -status: designed -code: [] +status: in-progress +code: [mesh-host] updated: 2026-08-26 decisions: - 02-DECISIONS/0030-the-repository-structure.md @@ -10,6 +10,7 @@ decisions: - 02-DECISIONS/0038-a-node-joins-by-linking-first.md - 02-DECISIONS/0039-the-link-is-the-security-boundary.md - 02-DECISIONS/0008-a-failed-step-fails-the-job.md + - 02-DECISIONS/0041-the-host-depends-on-nothing.md --- # The node host @@ -18,6 +19,11 @@ Tier 0. The one thing ever installed by hand, and the only thing that changes a ## What it is +A **statically linked binary that requires nothing to be present** — copy it onto a machine and +run it, and that is the whole installation +([ADR 0041](../../02-DECISIONS/0041-the-host-depends-on-nothing.md)). Written in Go, because the +job is system-level and because the host shares no code with any other tier. + A single binary with one job: **apply declared state on this machine** ([ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md)). Overlay membership, packet filtering, packages, services, containers and filesystems are not six