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