Files
hq/03-DESIGN/00-as-is/05-runtime-and-installation.md
jschoubben 333356cff3 Order the records the way the system is learned
Jochen asked whether the order made sense. It did not -- it followed when
things happened to be decided, which after consolidation is fictional anyway
since record 5 alone folds decisions taken across a week.

Concretely wrong before: the domain statement sat at 8, after five engineering
rules; the constitution was scattered across 5, 12 and 17; the tiers landed at
15, 16, 21 and 22 with process records in between.

Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what
runs on them and how it gets there (9-10), how it is built (11-16), how it is
checked (17-18), how we work (19-23).

Two things made this safe rather than free. It is a permutation, not a
compaction, so the renames go through temporary names -- otherwise two files
want one slot and one is lost. And the reference rewrite is a single
simultaneous pass, because almost every number moved into a slot another number
was vacating; replacing one at a time would have cascaded and pointed things at
the wrong record while still resolving.

Verified: 284 [ADR NNNN](path) links across the repository, all with matching
text and target.

The ordering principle is now stated in 19 rather than left implicit -- the
repository already said "the numbering is the flow" about its folders, and
there was no reason for the records to be the exception.
2026-08-28 23:30:42 +02:00

108 lines
5.0 KiB
Markdown

---
layer: as-is
status: implemented
code: [hal]
updated: 2026-08-23
decisions:
- 02-DECISIONS/0009-modules-and-the-graph.md
- 02-DECISIONS/0012-the-mesh-creates-no-symlinks.md
---
# The node runtime, and how a node comes into being
Every node runs the same runtime. What differs is its assignment.
## Two modes, coexisting
The runtime runs in two modes at once on any node that needs both.
**Daemon** mode is a headless consumer: it connects to the broker, consumes the node's request
queue, routes each request to a local capability, and emits the node's lifecycle events. This
is what makes a node a participant — it is reachable whether or not anyone is logged in.
**Interactive** mode exposes the node's capabilities to a session on that machine over a local
protocol. Its capability surface is larger, because it includes stand-ins for every capability
discovered on peers.
The two are the same code with the same catalogue. A capability is written once and is
available to both.
## Anatomy naming, and what it obscures
The runtime's components are named after brain anatomy: an entry point that bootstraps, a
headless listener, an interactive surface, an installer daemon, and a provisioner daemon.
This is the mesh's most-cited naming problem and it belongs in the as-is layer because it is
what a reader will actually encounter. The names are evocative and describe nothing: the most
suggestive word in the system names the node runtime, and the component whose manifest says
"mesh messaging" is documented elsewhere as the interactive runtime. Anatomy makes attractive
names and poor boundaries.
[ADR 0001](../../02-DECISIONS/0001-mesh-brokers-nodes-host-agents-think.md) replaces this with names
taken from what each part owns. Until then, this is the vocabulary in the code.
## Starting a module
For each assigned module the runtime, at startup:
1. Reads the manifest, if there is one — a flag module has nothing to read.
2. Skips a module's capabilities if a variable they require is unset. This is quiet by design
and hard to distinguish from a module that has no capabilities.
3. Loads the capabilities the module carries.
4. For a service module, ensures it is installed and running.
Installation is idempotent and does the unglamorous work: ensure the runtime directory,
reconcile the link from the catalogue's definition into it, generate the environment, run any
outstanding local migrations, create data directories with the right ownership, then start the
service under supervision.
**The installer is the only thing that creates a link** ([ADR
0011](../../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md)). It reconciles rather than assumes: a
missing link is created, a stale one repointed, and a real file found where a link belongs is
adopted into the node's override area and replaced. Nothing else — not a hook, not a fix, not a
person debugging — creates one.
That is the as-is. The intent is to remove linking altogether and derive a real file instead,
which the reconciliation machinery already makes possible
([ADR 0012](../../02-DECISIONS/0012-the-mesh-creates-no-symlinks.md), proposed). What is described above
is what runs today.
## Supervision
Services run under the host's init system via a templated unit, one instance per module. It is
a thin layer: the unit starts and stops a container group.
Whether the mesh keeps this, drops the per-module layer, containerises the daemons, or writes
its own supervisor is **open** — costed in research effort 003 and deliberately undecided. It
no longer gates anything.
Two failures worth knowing about, both in the shape of "the change did not apply". A
per-instance copy of the unit template shadows the template, so edits to the template do
nothing. And a session-scoped one-shot job loses the environment it was given, because the
import is one-time and not persisted.
## How a node comes into being
Three bootstrap scripts, and which one runs depends on the situation:
- **First node.** Nothing exists yet, so the script stands up the database the rest of the
mesh reads from, publishes the catalogue, and starts the mesh. This resolves the
circularity of a mesh whose source of truth is itself a provisioned module.
- **Joining.** The node registers, takes its assignment from the database, and syncs.
- **Rescue.** A node that cannot reach the mesh is brought back far enough to.
Bringing a node into being is therefore a **database operation with a script attached**, not a
checkout. There is no per-node content in the repository to copy.
Adoption of a pre-existing machine's configuration was the original path and is now a legacy
one, explicitly out of scope for the lab
([`01-to-be/01-end-to-end-testing.md`](../01-to-be/01-end-to-end-testing.md)).
## Node identity
Each node carries an identity text in its own record, which the daemon writes onto the node at
startup so that a session on that machine knows which node it is on and how that node
presents itself. It carries identity only; shared rules live separately.
Like everything else derived onto a node, it is generated and not edited there.