Compare commits

..
Author SHA1 Message Date
jschoubben 17ca9a262b 0164: a setting names the file it lands in (issue 198's leak between one module's files); 190 notes the fourth machine now has the resolver 2026-10-02 12:23:28 +02:00
jschoubben 967c793eaa Merge remote-tracking branch 'origin/main' into decision/docker-module 2026-10-02 12:23:27 +02:00
jschoubben 27c1db8a86 Review of 0164-0166 and 190: the mesh's own setting words stay settable; changing runtime verbs are not the console's wildcard; migration steps 1-2 are one push; dnsmasq's dns key dates from 09-23 2026-10-02 00:48:06 +02:00
jschoubben 3d54fcbb86 Merge remote-tracking branch 'origin/main' into decision/docker-module 2026-10-02 00:02:57 +02:00
jschoubben f1941304cc ADRs 0164-0166 and issue 190: the container runtime gets a module, a seat and declared settings
Proposed for the operator's review: settings declared with defaults and cost (0164),
container-runtime as a kernel capability (0165), node-container-runtime seat with the
host creating containers through its holder (0166), and the runtime's file written by
modules that are not its own (190).
2026-10-01 23:13:18 +02:00
13 changed files with 502 additions and 670 deletions
-5
View File
@@ -9,11 +9,6 @@ another — and a mesh you cannot name precisely is a mesh two people describe d
- **node** — a machine in the mesh. There are 0..n of them, and each runs the host agent. A node is
just a machine that has joined; being one implies nothing about what it runs.
- **operator account** — the login name of the person who works on a node, stated on the node
record; empty for a machine nobody logs into. Everything the mesh places under a person's home is
resolved against this account's home and owned by it
([ADR 0169](../02-DECISIONS/0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)).
Not "the user" (ambiguous with a module's own account) and not a name a definition carries.
- **control-node** — the one node that also holds the `mesh-controller` seat. There is exactly one
per mesh. "control-node" is not a separate kind of machine — it is a node that additionally runs
the controller (and, today, the foundation). Lose it and the other nodes keep running what they
@@ -0,0 +1,146 @@
---
topic: what runs on it
status: proposed
date: 2026-10-01
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md
---
# 164. A setting is declared with its default, its meaning and what changing it costs
## Context
The operator asked for one thing for every module, with the container runtime as the first case: **one
consistent default configuration for every machine, overridable per assignment, and easy to change
later.** The four machines' runtime configurations were each written by hand and differ — one keeps
running containers through a daemon restart and one does not, their log rotation differs, and each
names its resolver and its trusted registries in its own words.
Most of this was already decided.
[ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md) said the definition is
identity and **defaults**, the assignment's settings are the configuration, *unset is the default*, and
*an unknown setting is refused*. [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) made
a setting an operator requirement whose contract is "a type and, optionally, a default", answered by
"the assignment's, or the requirement's default, or unresolved". An assignment is a module on a node,
so the node layer of a module's settings already *is* the per-assignment override, and the mesh-wide
layer is the one consistent default a person changes once.
What was built is narrower than what was decided, measured in the controller on the day of deciding:
- **Nothing declares which keys are settable.** A mergeable file's content is its defaults, and every
key of it — and every key not in it — is accepted. Nothing tells a person, or the console, what can be
set, of what type, or what it means.
- **The refusal of an unknown setting is not there for most modules.** The stray-setting report returns
nothing at all for a module with any mergeable file, because such a file "takes any key"
([issue 173](../04-ISSUES/173-a-modules-settings-reach-every-fact-it-contributes/00-report.md) left
files that way on purpose). It reports rather than refuses where it does run.
- **A value in a file that is not JSON can have no default.** `${setting:<key>}`
([ADR 0155](0155-a-definition-names-no-installation-and-how-that-is-checked.md)) is refused when no
layer sets it — right for a mail domain, where a default is the very literal 0155 removes, and wrong
for a tunable like the resolver's upstreams, which the resolver module therefore carries as literals
in its file.
- **A setting reaches every mergeable file its module owns.** The layers are one flat map per module,
laid over each such file. Adding a setting to the resolver module for its own configuration put the
key into the container runtime's file as well — the resolver writes into that file too — and the
runtime refuses keys it does not know. The plan showed it before any push; the runtime's file was
then made to take no settings at all ([issue 198](../04-ISSUES/198-the-lans-dns-server-ran-outside-the-mesh-and-its-filter-closed-it/00-report.md)). Issue 173 stopped settings leaking into
contributions and served facts; between one module's own files the leak remains.
- **What a change costs is said per file, not per key.** A service names the files it is reloaded or
restarted on. The runtime re-reads its trusted registries on a reload and its `dns` key only when it
starts; the resolver module declared a reload, so on two machines the key was written, reloaded,
and never read, and every container got a public resolver for weeks while everything read as
current ([issue 110](../04-ISSUES/110-a-container-on-the-runtimes-own-network-cannot-reach-the-resolver/01-resolution.md)).
## Considered Options
1. **Leave settings implicit; document each module's keys in its README.** Rejected: a key the mesh
does not know cannot be refused, typed, listed by the console or costed, and a README is a rule
enforced by nothing.
2. **A second mechanism for tunables beside settings** — defaults in a new block, settings untouched.
Rejected: two ways to state one person's value, and design 27 already retires six mechanisms
that grew that way.
3. **Settings declared in the definition, as 0112's operator requirement: a key, a type, a meaning,
optionally a default, and what a change costs.** Adopted.
## Decision
**A module declares every setting it takes.** Each declared setting has a name, a type, one sentence
of meaning, optionally a default, and what a change to it costs. The spelling is design 27's to settle
with the rest of the requirement form; this record decides the content.
**A setting with a default is a tunable; a setting without one is the operator's.** A tunable resolves
to its default when no layer sets it — wherever it is read, a mergeable file or `${setting:<key>}` in a
file of any format. A setting with no default is refused by name when nothing sets it, as 0155 decided;
0155's refusal is narrowed to exactly that case, not changed for it. Whether a value has a default is a
fact about the software (a log size does, a mail domain does not), and the definition states it once.
**The layers stay as they are, and every value says where it came from.** The definition's default,
then the mesh-wide layer, then the node's — later wins, objects merge, lists replace. One consistent
configuration for every machine is the default plus the mesh-wide layer; one machine that differs says
so in its own layer and nothing else. Asked for a module's configuration on a machine, the mesh lists
every declared setting with its effective value and its source: *default*, *mesh*, or *node*.
**Changing later is changing one of three places, and the plan shows its reach before anything moves.**
A new default ships with the module's next version and reaches every assignment that does not override
it; a mesh-wide setting reaches every assignment of the module; a node's reaches one. The plan of a
change names each assignment whose effective value moves.
**A declared setting says where it lands.** Each names the file or files of its module that read it,
and reaches no other: a module that owns two mergeable files no longer has one flat map laid over both.
A file that names no setting takes none.
**A declared setting is the only kind accepted.** Setting a key the module does not declare is refused
when it is set, naming the declared keys, rather than reported when the machine is planned. The mesh's
own words — where a port, a directory or an operator's data is placed, how far an endpoint reaches —
are the mesh's to validate as they are today, and no module declares them. A module
that declares no settings keeps today's behaviour until it does; a catalogue test lists those modules,
and the list shrinks to empty before the implicit form is removed — design 27's rule for every retired
mechanism.
**A setting says what it costs: nothing, a reload, or a restart.** When a file changes, the host
applies the strongest cost among the settings whose values moved in it, so a key the software reads
only at start can no longer be written and never read. A setting that reaches a container's environment
costs that container being recreated, which the host already does when a container's specification
changes; it needs no declaration. A service's `reload-on` and `restart-on` keep
naming the files that are not settings — a generated roster, a credential.
**The container runtime is the first module to declare its settings** and the model for the rest:
its log rotation, keeping containers through a daemon restart, and its resolver are tunables, and
its trusted registries are what the mesh tells it.
## Consequences
- The console can show a module's settings as a form: what can be set, of what type, its default,
and where the current value came from. That is the surface the operator wants for changing a
default later.
- `settings set` can refuse an unknown key, so ADR 0046's rule is enforced where it was only stated.
- The resolver's upstreams, the runtime's log rotation, and other literals a definition carries
because it could not give them a default become declared tunables.
- **What got harder:** every module that takes settings must list them, and a mergeable file no
longer silently accepts a key its author did not foresee. A person who needs one adds it to the
definition, which is a new module version, not a setting.
- Issue 173's open question — a consumer checks nothing against a contract — is unchanged; this record
is the operator half of design 27's contract, not the provider half.
- Not decided here: the spelling (design 27); whether a node's layer may be narrowed to a single key
rather than replaced whole, as `settings set` does today.
## How this is checked
| Rule | Checked by |
|---|---|
| Every setting a module takes is declared | A parser test refusing a setting declaration without a type or meaning; a catalogue test listing modules with mergeable files or `${setting:}` and no declarations, which must be empty before the implicit form is removed |
| A tunable resolves to its default; an operator value without one is refused | Resolution tests: an unset tunable in a JSON file and in a text file both take the default; an unset setting with no default is refused naming it (0155's existing test) |
| A setting reaches only the files it names | A resolution test: a module with two mergeable files and a setting declared for one; the other file's content is unchanged by it (the case of issue 198) |
| An undeclared key is refused when set | A controller test: `settings set` with an undeclared key fails naming the declared keys, and nothing is stored |
| Every effective value names its source | A test listing a module's configuration on a node with one key from each of default, mesh and node |
| A change's reach is shown before it moves | A plan test: changing a mesh-wide setting names every assignment whose effective value moves and no other |
| The strongest cost applies | A host test: a file where a reload-cost key and a restart-cost key both moved restarts; a file where only reload-cost keys moved reloads |
| Live | The container runtime's module lists its settings with their sources on every machine, and a mesh-wide change to its log rotation reaches all four at the next push |
## References
- [ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md), [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0155](0155-a-definition-names-no-installation-and-how-that-is-checked.md), [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)
- [Design 27 — a module requires, the mesh resolves](../03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md)
- Issues [110](../04-ISSUES/110-a-container-on-the-runtimes-own-network-cannot-reach-the-resolver/00-report.md), [173](../04-ISSUES/173-a-modules-settings-reach-every-fact-it-contributes/00-report.md)
- mesh-controller `internal/catalogue/settings.go` (`settle`, `UnusedSettings`), `internal/catalogue/setting_into.go`
@@ -0,0 +1,105 @@
---
topic: what runs on it
status: proposed
date: 2026-10-01
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0161-what-deserves-a-seat.md
---
# 165. `container-runtime` is what a machine can run; that a runtime is running is its holder's health
## Context
A capability is a requirement a module places on a machine, detected by the host and renewed with
every report ([ADR 0161](0161-what-deserves-a-seat.md)). The host's `container-runtime` asks the
daemon for its version: *a running daemon, not an installed client*. It was made that way by
[issue 007](../04-ISSUES/007-an-installed-package-is-not-a-capability/00-report.md), where an
installed package was believed to be a working service, and
[design 05](../03-DESIGN/01-to-be/05-the-node-host.md)'s table says the same: *a runtime is
running*. The installer's preflight borrows the same detector to wait for the runtime the
foundation bundle installs, so there is one answer to "is there a runtime here".
The mesh is now to have a module for the runtime itself — its packages, its configuration, its
service — on every machine ([ADR 0166](0166-the-container-runtime-is-a-node-seat-and-the-host-creates-containers-through-its-holder.md)).
That module cannot declare `container-runtime` as defined: it would require the very thing it
installs, the cycle [research 011](../01-RESEARCH/011-the-module-graph/cases.md)'s case 12 names
("something the mesh installs that then becomes a node capability"). The operator defined the word
for it: **`container-runtime` means the machine is able, at the kernel level, to install a runtime and
execute containers** — not that one is installed, and not that one is running.
The host already draws this line once. `seat` is hardware, a display server *could* run here;
`graphical-session` is state, one *is* running; the detector's own comment says "assignment needs the
first". A machine without a display has no seat however much software is installed, and a machine
with one has a seat before anything is.
Fifty-four catalogue modules declare `container-runtime` today, counted on the catalogue's main
branch on the day of deciding: every module that delivers a container. Each relies on the current
meaning to keep it off a machine with no running runtime.
## Considered Options
1. **Keep the meaning; let the runtime's module declare nothing.** Rejected: a module that installs
the runtime has requirements on the machine — the kernel features without which installing it is
pointless — and would state none of them. The cycle stays, only hidden.
2. **Two capabilities, "can run" and "is running".** Rejected: the second is made true by assigning a
module, so it is the module's state, not a fact of the machine; a capability the mesh itself
flips by its own assignment is case 12's cycle with an extra name.
3. **The capability is the kernel's; whether a runtime runs is the runtime module's health, and a
module that delivers a container needs the runtime's seat held.** Adopted.
## Decision
**`container-runtime` is detected from what the kernel offers**, as `seat` is: the namespaces a
container needs, a control-group hierarchy the runtime can manage, and an overlay filesystem the
running kernel has or can load. Present when all three are; absent naming the missing one. Nothing is
run and no runtime is asked. The verdict's detail names what was found, not a runtime's version.
**"A runtime is running and answers" is one probe, owned by the host and used twice:** by the
installer's preflight, which waits for the runtime the foundation installs, and as the runtime
module's health. It asks the daemon, as issue 007 requires. The preflight stops borrowing the
capability's detector, and there is still one answer to "is a runtime running here".
**The runtime's module declares `container-runtime`**, with `package-manager`, `service-manager` and
`privileged`, like any module that manages machine software.
**A module that delivers a container needs the runtime seat held on its machine**, and is refused
otherwise, naming the seat and the modules that could hold it — the refusal design 27 already lists
for an unheld seat. That requirement is derived from the container resource and needs no manifest
field ([ADR 0166](0166-the-container-runtime-is-a-node-seat-and-the-host-creates-containers-through-its-holder.md)).
The fifty-four existing declarations of the capability stay valid and become redundant; a catalogue
test lists them, and they retire when the list is empty.
**The order is fixed, not preferred.** The detector changes only once the seat requirement is
enforced. In between, a machine with the kernel and no running runtime would read as able to run
every containerised module, which is issue 007 again.
## Consequences
- Design 05's capability table changes its `container-runtime` row from *a runtime is running* to
*the kernel can run containers*, and names the runtime module's health as where "running" is now
asked.
- The node listing stops showing the runtime's version beside the capability. The version moves to
the runtime module's health and its seat's verbs.
- A fresh machine with no runtime reads as able to run one, so it can be assigned the runtime's
module, which is what makes the mesh able to install the runtime instead of the bootstrap alone.
- **What got harder:** "is this machine running containers" is no longer one glance at the profile;
it is the runtime seat's holder and its health. The node's listing should show both side by side.
## How this is checked
| Rule | Checked by |
|---|---|
| The capability is the kernel's | Host detector tests over a fixture `/proc` and `/sys`: all three present → present; each one missing → absent naming it; no runtime binary on the fixture machine changes nothing |
| One probe asks whether a runtime runs | A host test that the preflight and the runtime module's health call the same probe, and that the probe fails against a stopped daemon with an installed client (issue 007's shape) |
| A containerised module needs the runtime seat held | A resolution test: a module with a container resource on a machine whose runtime seat is unheld is refused, naming the seat and its candidate holders |
| The order holds | The host release that changes the detector is gated on the controller release that enforces the seat requirement — stated in both changes' descriptions and checked at review |
| Live | Every machine's profile shows `container-runtime` present with the kernel's features as its detail; a machine with no runtime installed reads present too |
## References
- [ADR 0161](0161-what-deserves-a-seat.md) — the profile renewed by every report; a capability that names a dialect
- [ADR 0166](0166-the-container-runtime-is-a-node-seat-and-the-host-creates-containers-through-its-holder.md) — the seat and its holder
- [Issue 007](../04-ISSUES/007-an-installed-package-is-not-a-capability/00-report.md), [research 011](../01-RESEARCH/011-the-module-graph/cases.md) cases 12–13
- [Design 05 — the node host](../03-DESIGN/01-to-be/05-the-node-host.md)
- mesh-host `internal/profile/detectors.go` (the runtime detector), `internal/profile/seat.go` (the hardware/state split), `internal/bootstrap/preflight.go` (the preflight that borrows it)
@@ -0,0 +1,161 @@
---
topic: what runs on it
status: proposed
date: 2026-10-01
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0161-what-deserves-a-seat.md
---
# 166. The container runtime is a node seat, and the host creates containers through its holder
## Context
Every container the mesh runs on a machine is created by the host, which looks for a runtime
(`docker info`, then `podman info`) and drives that runtime's command line itself: run, inspect,
remove, exec. Research 012 called this "detected rather than declared": the host takes over whatever
runtime it finds. Nothing in the mesh owns the runtime. Its package came from the foundation bundle
or was already on the machine. Its configuration file was written by hand, differs on each of the
four machines, and is also written into by two modules that are not the runtime's
([issue 190](../04-ISSUES/190-the-runtimes-configuration-is-written-by-modules-that-are-not-the-runtime/00-report.md)).
Its service is declared by those same two.
The operator set the direction:
- a module for the runtime, on every machine, owning "what is needed to run containers here": its
packages, its configuration and its service;
- that module holds a node seat for the runtime, so a second runtime (podman) can later compete
for the seat;
- the host stops speaking to the runtime directly and uses the seat's holder. The host stays the one
that decides, and the holder becomes the one that executes;
- every container on the machine is in scope, not only the mesh's. A development environment started
by hand, or a test database a tool runs, is legitimate. The host already calls these *strays*: 3,
8 and 25 on three of the machines on the day of deciding;
- the runtime's events and verbs are subjects on the bus, and the mesh's own interface is built on
them. The third-party interface run until now was removed by hand.
[ADR 0161](0161-what-deserves-a-seat.md)'s test for a seat is whether the mesh's own code finds it by
name. Here it does: the host would look up the holder on its own machine. A singular role of a module
held once per machine is a `node-*` seat ([ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)),
in the controller's seed.
The constraint that decides most of this record is a cycle. The bus runs in containers. On the
broker's machine, the broker's own container is created by the host. A holder's code served from a
container cannot create the container that runs it. On a first machine, before the controller exists,
nothing holds anything.
## Considered Options
1. **The host calls the holder's verbs over the bus.** Rejected: with the broker down, no machine can
create any container, including the broker's. The mesh would be unable to restart its own
transport.
2. **The holder picks a dialect that the host speaks itself, as with the uplink.** Rejected: the
host would still drive the runtime, and the module would drive it too for every other caller.
That is two programs speaking to one daemon, and they come to disagree about the same machine
(the installer's preflight already exists to avoid this).
3. **The holder's code runs as a supervised process on the machine and serves the seat's verbs
twice: locally to the host, on the bus to everyone else.** Adopted.
## Decision
**`node-container-runtime` is a seat of the mesh's own, node-scoped,** in the controller's seed under
this record. It delivers no provision; what it carries is its role's protocol: verbs its holder must
serve ([ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md)) and events its holder emits
([ADR 0129](0129-a-seat-carries-the-protocol-of-its-role.md)). The runtime's module, `docker`, claims
it and is assigned to every machine. A podman module may claim it later; one machine runs one.
**The seat's verbs cover every container on the machine:** list, inspect, logs, stats, start, stop,
restart, create and remove. A mesh-held container is marked by the host's label and says which
assignment holds it. **A container the runtime runs can be root on the machine** — privileged, a host
path mounted, the host's network or process namespace, the runtime's own socket — so a caller other
than the host may not create one that is any of these; only a declaration the mesh composed may ask
for them. And the verbs that change anything are granted by name, never by a wildcard: a grant of
every tool (the console's today) reaches the reading verbs only. Issue 193 is what a verb that trusts
its caller costs. **Creating or removing a mesh-held container is the host's alone.** Any other
caller is refused naming the assignment, because the host would undo it at its next apply. Starting,
stopping or restarting one is allowed, and the answer says the host will restore what its
declaration says. A container the mesh does not hold is the caller's to do anything with.
**The seat's events are the runtime's own** — a container created, started, stopped, died, removed,
its health changed. They are emitted on the seat's subjects, so every holder emits the same events and
no reader depends on which runtime holds the seat. As
[ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
decides, the subjects are issued by the controller, not composed by the module.
**The holder's code is a supervised process, not a container** ([ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md)).
A runtime cannot be run by the thing it runs. The process serves the seat's verbs on the bus to the
console, to tools and to the mesh's interface. The same verbs are served on a local socket on the
machine, which only the host may use. **The host creates, inspects and removes its containers
through that socket and nothing else.** If the holder does not answer, the host creates nothing. It
says so in its report, naming the seat. It never falls back to the command line.
**A container needs the seat held on its machine.** An assignment that delivers a container on a
machine whose runtime seat is unheld is refused, naming the seat and its candidates
([ADR 0165](0165-container-runtime-is-what-a-machine-can-run-and-a-running-runtime-is-its-holders-health.md)).
Mounting the runtime's socket into a container is granted by the seat, not by the capability. The
socket's path is the holder's to state, because podman's is not docker's.
**The runtime module owns the runtime's configuration.** Its settings are declared with defaults
([ADR 0164](0164-a-setting-is-declared-with-its-default-its-meaning-and-what-changing-it-costs.md)):
the resolver containers use, the registries it trusts, log rotation, and keeping containers through a
daemon restart. The module is given the resolver's address and the mesh's registry as values; no other
module writes the runtime's file or declares its service.
**The first machine is bootstrapped with the holder, and adopted afterwards.** The foundation bundle
already installs the runtime's package and service. It also carries the holder's process, delivered as
a binary the way the host is ([ADR 0142](0142-the-mesh-delivers-its-own-components-as-binaries.md)).
When the runtime module is assigned, it adopts what the bundle made, as the store and broker modules
adopt theirs ([ADR 0078](0078-the-store-and-broker-are-modules.md)).
## Consequences
- **The migration on the running mesh has a fixed order:**
1. Each machine's hand-written configuration is read, because the module's defaults replace what
differs.
2. In one push per machine: the resolver module and the private network stop writing the
runtime's file ([issue 190](../04-ISSUES/190-the-runtimes-configuration-is-written-by-modules-that-are-not-the-runtime/00-report.md)),
and the runtime module is assigned and adopts the runtime, its file and its service. Split in
two, either the controller refuses two modules declaring one path, or a machine is left with
nothing setting `dns` and `live-restore`.
3. The controller seeds the seat and enforces the container requirement.
4. The host releases the version that uses the holder.
5. The host's command-line path is removed in the release after every machine's holder answers.
Until then, the host reports per machine which path it used.
- **Every container the host makes depends on the holder's process.** A crash-looping holder stops
new containers on its machine. Running containers are unaffected. The host's report names the cause.
- The process form of a module's own code must serve tools on the live mesh before this ships. Only the
showcase declares it, and [issue 117](../04-ISSUES/117-a-modules-own-code-is-a-container-and-a-process/01-diagnosis.md)
found the showcase's tools declared in a form nothing runs. The runtime module is the first whose
tools cannot fall back to a container.
- A user interface subscribing to events directly does not exist. Today a reader of events is a module
that consumes them. The mesh's container view is a module, or waits for that path.
- [ADR 0005](0005-the-node-host.md) ("a container runtime is detected, not chosen") and
[ADR 0006](0006-the-substrate-and-the-control-plane.md)'s matching line describe the mechanism this replaces: on
acceptance, each gets a dated note saying the runtime is now a seat's holder, as the decision
records' rule for a moved mechanism requires. Design 05 and design 26 are amended after acceptance.
- The operator's decision to remove the third-party interface by hand needs no mechanism. No
module-retires-module rule is introduced.
- **What got harder:** the host gains a dependency it did not have, and a first machine's bundle gains
a component. The direct path was simpler and is what makes a runtime a black box to the rest of the
mesh.
## How this is checked
| Rule | Checked by |
|---|---|
| The seat is the mesh's own, node-scoped, with its verbs and events | A catalogue test on the default seats; registration refuses a claimant that does not serve every verb (design 33's existing check) |
| Creating or removing a mesh-held container is the host's alone | A test of the runtime module's verbs: create or remove of a container carrying the host's label, from any caller but the host's socket, is refused naming the assignment; the same verbs on an unlabelled container succeed |
| No caller but the host creates a container that is root on the machine | A test of `create` from the bus: privileged, a host path, the host's namespaces and the runtime's socket are each refused; the same request on the host's socket is accepted. A broker test: a grant of every tool does not reach a changing verb |
| The host uses the holder and never the command line | A host test with a fake holder on the local socket: every container operation goes to it, and with the holder absent the apply creates nothing and reports the seat; after step 5, the host carries no command-line runtime code (checked by build: the package is gone) |
| A container needs the seat held | A resolution test refusing a containerised assignment on a machine with the seat unheld, naming the seat |
| Socket mounts are granted by the seat | A catalogue test: a module mounting the runtime's socket on a machine whose holder states a different path is refused |
| No other module writes the runtime's file | The existing collision check, once the private network's computed resources are inside it ([issue 190](../04-ISSUES/190-the-runtimes-configuration-is-written-by-modules-that-are-not-the-runtime/00-report.md)) |
| Live | `seats` lists `node-container-runtime` held on every machine; the node listing shows each machine's containers, strays included, from the seat's `list` verb; a container started by hand appears as an event on the bus |
## References
- [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md), [ADR 0129](0129-a-seat-carries-the-protocol-of-its-role.md), [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md), [ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md), [ADR 0161](0161-what-deserves-a-seat.md)
- [ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md), [ADR 0142](0142-the-mesh-delivers-its-own-components-as-binaries.md), [ADR 0078](0078-the-store-and-broker-are-modules.md), [ADR 0005](0005-the-node-host.md)
- [ADR 0164](0164-a-setting-is-declared-with-its-default-its-meaning-and-what-changing-it-costs.md), [ADR 0165](0165-container-runtime-is-what-a-machine-can-run-and-a-running-runtime-is-its-holders-health.md), [issue 190](../04-ISSUES/190-the-runtimes-configuration-is-written-by-modules-that-are-not-the-runtime/00-report.md)
- [Design 26 — the seats](../03-DESIGN/01-to-be/26-the-seats.md), [design 33 — the tools the mesh answers](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md)
- mesh-host `internal/apply/apply.go` (the runtime lookup and the command line it drives)
@@ -1,118 +0,0 @@
---
topic: what runs on it
status: accepted
date: 2026-09-27
deciders: jochen
reconstructed: true
extends: 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
---
# 169. The operator account is a node fact, and a home is a placement root
*Reconstructed. The controller shipped this on 2026-09-27 and
[to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) recorded it as built without a
decision behind it. This record states what was decided, from the code and the design, and adds the
two rules the code left implicit — what an empty account means for a module, and that the account is
stated rather than discovered. Written 2026-10-02.*
## Context
[ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) took every host path out of a
module definition and gave a module's *system* data a place: a directory the mesh resolves under the
node's root, owned by the module. It said nothing about the other half of a filesystem — the files
that belong under a person's home and are owned by that person. The predecessor wrote several of
those: the ssh client configuration, the shell's configuration, an agent's instruction files. It knew
whose home it was writing into because each of its node records carried a login name. The mesh took
the machine facts over and dropped the human one.
The loss was found the ordinary way: `ssh <node>` logged into the home-server under the workstation's
own login name, because nothing in the mesh said the home-server's account was a different one
([to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md),
[issue 172](../04-ISSUES/172-the-ssh-client-block-matches-one-spelling-of-a-machine/00-report.md)).
What the controller does since 2026-09-27: a node record carries an operator account and, optionally,
its home; the account and its home are machine facts a definition may name in a resource's path, owner
and content; a roster file may say it lives under the home, and is then rendered per node, placed under
that node's account's home, owned by the account, and left out on a node with no account. On
2026-10-02 **all four nodes of the live mesh carry an empty account**: the fact exists and nobody has
stated it, so no home-scoped resource can land anywhere yet.
## Considered Options
1. **The definition names the login.** `owner: <name>` in the module. Rejected: it is the installation
written into a definition, which ADR 0112 forbids and
[ADR 0155](0155-a-definition-names-no-installation-and-how-that-is-checked.md) checks for, and it is
wrong on the first machine whose login differs — which is exactly the machine that surfaced this.
2. **The host discovers the account.** The first non-system user, or whoever ran the enrolment.
Rejected: a guess. A shared machine has several people on it, a server may have none, and a host
deciding whose files these are is a decision the mesh then cannot see, state or correct.
3. **The account is a fact the operator states on the node record, and the home is derived from it
unless stated.** Chosen.
## Decision
**A node has an operator account: the login name of the person who works on it.** It is stated by the
operator on the node record, the way a node's address or mode is held there, and it is empty for a
machine nobody logs into. Empty is a real state, not a missing value. The mesh holds the fact because
everything below derives from it, and because it is precisely the fact that was lost when the
predecessor's records were not carried over.
**The account's home is derived unless stated.** The superuser's home for the superuser, the
distribution's conventional per-user home otherwise; a node whose account lives elsewhere states its
home. One place computes the default, so a fact and the record cannot disagree about it.
**A resource may be placed under the home, owned by the account.** This is ADR 0112's move one level
over: as a module's system directory is resolved under the node's root, a file under a person's home is
resolved against the account's home, and owned by the account rather than by root or a module's own
account. A definition names the account and its home as machine facts, never as a path; a roster fact
may say it is a home file and is then placed and owned the same way. The controller resolves both at
composition, and the host chowns what it creates.
**A node with no account cannot carry a home-scoped resource, and says so.** A roster fact that lives
under the home is left out of that node's declaration rather than written to nowhere. A resource naming
the account fact on such a node is refused at composition, naming the fact the machine does not have.
A module that writes a person's files is thereby unassignable to a machine with no person on it, which
is the right refusal.
**One account per node is what this record decides.** Several people on one machine is left open, with
the constraint that allowing it must not force the common case — one workstation, one person — to name
anything.
## Consequences
- **The operator states the account before any home-scoped module lands.** Today none is stated, so the
first assignment of such a module begins with four node records.
- The roster carries each node's account, so a composed ssh configuration logs in as the right person
on every machine — the gap that surfaced this, closed by the same fact.
- A family of modules becomes writable: everything the predecessor placed under a home — ssh client,
shell, the agent's instruction files — is now a module naming a fact rather than a path
([to-be 29 §2](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md)).
- **What got harder:** a definition cannot say "my user's home" without the mesh knowing who the user
is, so a module of this family is refused on a freshly enrolled machine until a person is named on
it. That is a prompt, not an obstacle.
- **Not decided here:** several accounts per node; a service unit running as the account rather than
as root or a module; a one-off step run as the account. Each is a record of its own.
## How it is checked
| Rule | Checked by |
|---|---|
| A resource's path and owner resolve the account and its home | controller tests on machine-fact resolution: a file naming the account facts lands under the account's home, owned by the account |
| The home is derived unless stated | a controller test: the superuser's home for the superuser, the conventional home otherwise, the stated home when one is stored |
| A home roster fact is left out on a node with no account | a controller test on roster composition: the file is absent from that node's declaration and present on a node with an account |
| A resource naming the account on a node with no account is refused by name | a controller test on machine-fact resolution: the refusal names `account` and lists the facts the machine does have |
| No definition names a home path | ADR 0112's catalogue test on host paths, which a `/home` or `/root` literal fails |
## References
- [to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) — the design this record
gives a foundation to, and its "what has shipped" section
- [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md) — the system-path placement
this mirrors; [ADR 0155](0155-a-definition-names-no-installation-and-how-that-is-checked.md) — why
a login name may not be in a definition
- [ADR 0120](0120-a-roster-fact-carries-its-format-as-a-template.md) — the roster fact a home file
may be
- [ADR 0170](0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md) — what
the mesh may and may not do inside the home this record lets it reach
- mesh-controller `internal/inventory/nodes.go` (the account and its home on the node record),
`internal/catalogue/machine_into_files.go` and `roster.go` (resolution and the home fact)
@@ -1,117 +0,0 @@
---
topic: what runs on it
status: accepted
date: 2026-10-02
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0118-undeclaring-gives-a-unit-back-the-state-it-was-found-in.md
---
# 170. Inside a home, the mesh owns the directory and the files it places, writes into the tool's own files, and holds everything else as found
## Context
[ADR 0169](0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md) lets a module
place files under a person's home. A home is unlike any directory the mesh has written into so far:
it is shared with the person, and with every program the person runs. The agent's configuration
directory on the laptop makes the point. On 2026-10-02 it holds thirty entries. The predecessor placed
five of them (an instruction file, a conventions rule, a settings file it merged into, two skills); a
sibling module placed a sixth (the node's identity rule). The agent itself writes the other
twenty-four: its settings, its credentials, its history, the memory of every project it has worked in,
its plugins, its session logs. Several of those are what [to-be 15](../03-DESIGN/01-to-be/15-the-agent-session.md)
calls memory *written by the session itself and declared by nobody*: a mechanism that regenerated the
directory would erase a season of it, silently, while reporting success.
The predecessor's own module recorded the hazard in the other direction. Its settings file was first
shipped as *replace*, and every `/model` choice a person made inside a session was reverted to the
template's value on the next synchronisation — on every node, indefinitely, with no indication why. It
was changed to *merge*, and the comment explaining why is still in its manifest.
**And the generator is gone while its output stayed.** The predecessor was retired from the laptop on
2026-10-01. Its six files are still on both workstations, with their content telling every session to
use tools that no longer exist. Nothing owns them; nothing will ever rewrite or remove them.
[To-be 29 §3](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) drew the line for one directory,
`~/.ssh`: the mesh owns the directory and the files it places; it holds the person's private keys and
personal drop-ins as found. That was argued from the lockout `~/.ssh` can cause. The argument here is
the same shape with a different stake — the person's work rather than the person's way in — and it has
to hold for every directory the family of home-scoped modules will touch, so it is a rule, not a
section.
## Considered Options
1. **The module owns the directory whole**, regenerating it from the definition. Rejected: it destroys
the memory, history and local settings the agent writes for itself, which is the failure to-be 15
names and the predecessor's settings file demonstrated at small scale.
2. **The module owns only the files it names, and nothing about the directory.** Rejected: *owning one
file beside foreign ones is not owning anything* (to-be 29). The directory must exist, with the right
owner and mode, before the tool first runs on a fresh machine; and a credentials file in a
world-readable directory is a credentials file in the wrong directory.
3. **The module owns the directory and the files it places; a file the tool writes for itself is
written into, never over; everything else is held as found.** Chosen.
## Decision
**A home-scoped module owns the directory it declares: its existence, owner and mode.** The host creates
it if absent, owned by the account, and never removes it while it holds anything
([ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md)). Inside it, every path the module touches
is in exactly one of four classes, and **the class is visible in the definition from the shape
declared**, not inferred from what happened to be on disk:
| class | declared as | the host's rule |
|---|---|---|
| **owned** | a file with content, or a roster fact | written whole, regenerated, removed when undeclared; a file found there with no record of the mesh making it is kept once before it is written over ([ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md)) |
| **written into** | a file written *into* a structured document | only the keys the definition names are set, every other key is kept, and each set key is given back when undeclared (ADR 0102). The key list is the module's and is short |
| **written by the module's own process** | a secret the mesh delivers to the module, and a step that writes the file from it | the file's content is never a declared file's content, because a declaration travels in the clear on the bus and the host records it; the module's process writes it, owned by the account, atomically. [ADR 0171](0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md) says how for a credential |
| **found** | nothing | never read, never rewritten, never removed. The person's memory, history, projects, local settings, their own rules and skills |
**A file the tool writes for itself is written into, never over.** The agent's settings file and its
own state file are the tool's; the mesh has one or two facts to state in each. Setting those keys and
nothing else is what lets a person's `/model` choice survive a push, and what lets the mesh's keys be
taken back cleanly when the module goes.
**A predecessor's output is found.** A file placed by a generator that no longer exists is, to the
mesh, a file it has no record of making. Where the successor module keeps the path, declaring it
*adopts* it: the host keeps the original once and writes the mesh's. Where the successor does not keep
the path, the mesh does not remove the file, because it removes nothing it did not make; **the operator
removes it, once**, and the module's definition names those paths in its own documentation so the step
is not forgotten. This is the first instance of the one-off setup step to-be 29 leaves open, and the
rule chosen for it is that it is a person's act, listed, not a module's.
**The rule is the family's.** An ssh client module, a shell module, an agent module each declare their
directory and classify their paths this way. A module that cannot say which class a path is in has not
finished its definition.
## Consequences
- A person's work under their home survives every push and every unassign. The mesh's own files come
and go with the module; the mesh's keys in the tool's files come and go with it; the directory stays.
- **Stale files survive too.** Two workstations keep three predecessor files each until a person removes
them — a visible cost, accepted over a mesh that deletes under a person's home. A module author who
renames one of the mesh's own files has the ordinary path: the old resource id is undeclared and the
host removes what it made ([ADR 0118](0118-undeclaring-gives-a-unit-back-the-state-it-was-found-in.md)).
- A module's definition is longer by a classification, and a reviewer has one more question per path.
That is the point: *which parts are managed must be explicit rather than inferred* (to-be 15).
- **What got harder:** a module cannot seed a person's preference once and leave it. A seeded file
([ADR 0087](0087-a-seeded-file-is-created-once.md)) is the shape for that, and it is available to
this family unchanged; what is refused is a seed the module later wants to change, because what grew
in it is the person's.
## How it is checked
| Rule | Checked by |
|---|---|
| The directory is created owned by the account and kept when the module goes | host tests of a directory resource with an owner (ADR 0051's and 0118's), and the family's lab check below |
| An owned file found with no record is kept once, then written | host tests of ADR 0102's kept-original rule |
| Only the declared keys of a written-into file change, and are given back | host tests of ADR 0102: declared keys set, the rest kept, restored when undeclared |
| Nothing found is touched | the family's lab check: a machine with a seeded home holding a person's file beside a predecessor's; after apply the person's file is byte-identical, the predecessor's is kept as the original, the mesh's keys are set and the person's keys in the same file remain; after unassign the mesh's files are gone, the keys are restored, the person's files are untouched and the directory stands |
| Every path a home-scoped module touches is classified | a catalogue review rule for this family: each path is a directory, a file, a file written into, a secret-and-step, or absent — the first module written to it is [to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) |
## References
- [to-be 29 §3](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) — the same boundary drawn for `~/.ssh`
- [to-be 15](../03-DESIGN/01-to-be/15-the-agent-session.md) — a session's memory is declared by nobody
- [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md), [ADR 0087](0087-a-seeded-file-is-created-once.md),
[ADR 0118](0118-undeclaring-gives-a-unit-back-the-state-it-was-found-in.md), [ADR 0030](0030-data-outlives-the-mesh-that-declared-it.md) — the mechanics each class rests on
- [ADR 0051](0051-shared-data-is-the-operators.md) — the third case the host had no word for: what it neither made nor configured
- the predecessor's `claude-code` module manifest, whose comment on `strategy: merge` records the reverted `/model` choice
@@ -1,142 +0,0 @@
---
topic: what runs on it
status: accepted
date: 2026-10-02
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0024-model-access-is-a-provision.md
---
# 171. The mesh binds and delivers a licence; the module alone writes the tool's credential; a switch is the binding changed, asked for through the console
## Context
**The operator's stance, set on 2026-10-02:** the controller has no part in the agent module. The
module owns the agent's directory under the operator's home and every related file, handles them
itself, and carries a licence-switching function as the predecessor's did.
**What the predecessor's switching actually was.** A registry of accounts held server-side; a tool,
callable from a session, that decrypted the chosen account's token on the server, refreshed it if near
expiry, and wrote the agent's credentials file on the target node — never returning the token. Beside
it, a shell helper that ran the agent with a token read from a plaintext file in the operator's own
configuration directory, one token per account, on every workstation; and an enrolment helper that
logged in once in a throwaway home and registered what came out. So the central half did the
refreshing and the writing; the node held nothing it could refresh with; and the convenience path kept
every account's token readable on disk wherever it was wanted.
**What the mesh has.** [To-be 14](../03-DESIGN/01-to-be/14-model-access.md) is built as far as it goes:
a licence is a named record with a vendor; a consumer is a module on a node and is put on one licence;
the access token is sealed per holder and delivered to the holder's machine; for a refreshable grant
the manager node alone holds the refresh token, encrypted, and refreshes centrally — the one stated
carve-out of [ADR 0050](0050-model-access-is-vendor-agnostic.md). The controller has commands to add a
licence, put a consumer on it, release it, accept a key, set a manager, set and refresh a grant. **None
of them is a verb on the `mesh-controller` seat**, so none can be asked for through the console
([ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md) exposes eighteen commands and
not these). The catalogue has the manager module and a consumer module that already writes an
access-token-only credentials file at a path it is told; both are assigned to nothing.
**Why refresh is central and must stay so.** A refreshable grant rotates its refresh token on use. Two
machines each refreshing one account's grant race: the second refresh presents a token the first
retired. The predecessor refreshed centrally for this reason, and ADR 0024 kept that half on purpose
(*the hard half of this already — and it works*). ADR 0050 narrowed the consequence to one node.
**The two designs are not in conflict, and the line has to be drawn in a record.** The controller
resolving *whose* home a file lands in ([ADR 0169](0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md))
and *which* licence a consumer holds (to-be 14) is what the controller does for every module. "No
part" cannot mean that, or the module could not be assigned. It can mean — and this record says it
means — that **the controller learns nothing about the agent**: no file shape, no path, no key, no
word beyond the vendor adapter it already has.
## Considered Options
1. **The module keeps its own registry of accounts and tokens**, the stance read literally. Rejected: a
second secret store outside the vault ([ADR 0113](0113-the-vault-makes-every-secret.md)); a refresh
token on every workstation, widening ADR 0050's one-node carve-out to every machine a person sits
at; and two records of one licence, which drift.
2. **The agent refreshes itself**: the mesh delivers a full grant once at a switch and the agent's own
refresh keeps it alive. Rejected: the refresh race above, between the agent and the manager and
between two machines on one account; and every node then holds a refresh token, which ADR 0050
decided no node does.
3. **The mesh binds and delivers; the module writes; a switch is the binding changed, asked for through
the console.** Chosen.
## Decision
**The consumer is the module on the machine: the operator's interactive sessions on that node, under
that account, hold one licence at a time.** That is to-be 15's `(node, module)` identity, with the
agent module as the module. Two machines may hold different licences, the ordinary case. The mesh's own
sessions on a machine are other modules and hold theirs in their own right.
**The mesh delivers; the module writes.** The module requires `model-access`. The mesh resolves the
licence the consumer is on, delivers the access token sealed to the machine as a secret in the module's
own state, and delivers the non-secret facts — the licence's name, what it serves — beside it. **The
module's own process writes the agent's credentials file** from the delivered secret: under the
account's home, owned by the account, readable by nobody else, written atomically, and access-token-only
— a refresh token found there is removed, because a node never holds one (ADR 0050). The file's content
is never a declared file's content ([ADR 0170](0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)).
The step runs when the delivered secret changes and on a schedule as a backstop, so a refreshed token
reaches the file without anyone asking.
**The controller learns nothing about the agent.** Where the file is, what shape it has, what the
agent calls its keys, how it is told about the console — all of that is the module's definition and
code. The controller contributes the facts it contributes to every module: the account, the home, the
licence, the delivery.
**A switch is the binding changed.** Putting the consumer on another licence is the mesh's existing
act — *use this licence, for this consumer* — and it becomes a verb on the `mesh-controller` seat the
way the other verbs did (ADR 0154): the command it already has, served on the bus, listed by the
console. The module serves two tools of its own: one that reports which licence the machine holds and
when its token expires, and one that invokes the seat's verb for a named licence and then waits until
the credentials file carries the new licence's token, answering with the licence's name — **never the
token, in any answer, log or event**. A skill in the agent's directory wraps the second so a person
asks in a sentence. Switching remains a reaction, not a declaration (ADR 0024): a person asks for it,
and nothing in the declaration language grows a conditional.
**Enrolling an account is the mesh's act on the manager node.** A new licence is added by name, its
grant obtained by a login in a throwaway home on the manager node and adopted sealed to that node's
key, as the manager module already does. No token is pasted into a prompt, printed, or passed as an
argument (to-be 14's rule for keys).
**The shell helper that read tokens from a file is retired, not replaced.** A second concurrent
session on the same machine under a different licence would need a second consumer identity — the
unbuilt half of to-be 14's gap — and is not provided here. Stated so it is not rediscovered as a bug.
**A licence the mesh no longer grants is withdrawn** at the binding (ADR 0024). The credentials file
the module wrote is the module's own output: unassigning the module leaves it, like the agent's other
files, and the access token in it expires within hours. Releasing the consumer from the licence is the
act that ends its access.
## Consequences
- The agent on a workstation authenticates with a token the mesh delivered and refreshes centrally,
and no workstation holds a refresh token or any other account's token.
- **The manager must run.** The refresh path exists in the catalogue and is assigned to nothing; it is
a prerequisite of this record, on the control node, and the first thing the build proves.
- **The controller gains a verb, not knowledge.** The licence commands become seat verbs, each
running the command it names, as ADR 0154 did for the others; nothing in them is about the agent.
- A switch is a round trip — binding, composition, push, apply — rather than the predecessor's direct
write: seconds to a minute, and reported when done rather than assumed.
- **What got harder:** running two sessions on one machine under two accounts at once, which the
retired helper allowed by keeping tokens readable. The price of not keeping them so.
## How it is checked
| Rule | Checked by |
|---|---|
| The module's definition declares no secret in a file's content, and requires `model-access` | a catalogue test on the module's definition |
| The credentials file is access-token-only, owned by the account, atomic | the consumer module's existing unit tests on the strip and the write, carried into this module; a live check that the file names no refresh token |
| A switch through the console changes the licence and the token, and no answer carries a token | a live check: the bound facts name the new licence, the file's fingerprint changes, the tool's answer and the module's log contain neither token |
| The controller's licence verbs run the commands they name and carry no agent vocabulary | the seat verb's test, as for the eighteen before it |
| The refresh path is live before the module is | the manager assigned on the control node and a refresh observed in the licence's record, before the module's first assignment |
| No workstation holds a refresh token | the live check above, on every machine the module is assigned to |
## References
- [ADR 0024](0024-model-access-is-a-provision.md), [ADR 0050](0050-model-access-is-vendor-agnostic.md),
[ADR 0055](0055-model-access-is-answered-by-a-licence-or-a-node.md) — what a licence is, who refreshes, what answers
- [to-be 14](../03-DESIGN/01-to-be/14-model-access.md), [to-be 15](../03-DESIGN/01-to-be/15-the-agent-session.md) — the consumer identity and the gap this leaves where it is
- [ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md), [ADR 0152](0152-the-operators-surface-is-a-module-the-console.md) — how a verb reaches a person
- [ADR 0113](0113-the-vault-makes-every-secret.md) — why there is no second registry
- [ADR 0170](0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md) — the class the credentials file is in
- the predecessor's `claude-code` module: its switch tool, its shell helpers and the rules of its skill
- mesh-catalog `modules/anthropic-manager`, `modules/anthropic-consumer` — the refresh and the write, as built
+3 -3
View File
@@ -269,9 +269,9 @@ python3 00-META/checks/index.py fail if stale
- **0150** — [A module's own code runs as supervised processes under the module's one account](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md)
- **0152** — [The operator's surface is a module the mesh assigns: the console](0152-the-operators-surface-is-a-module-the-console.md)
- **0155** — [A definition names no installation: how that is checked, and the three ways a value that did gets out](0155-a-definition-names-no-installation-and-how-that-is-checked.md)
- **0169** — [The operator account is a node fact, and a home is a placement root](0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)
- **0170** — [Inside a home, the mesh owns the directory and the files it places, writes into the tool's own files, and holds everything else as found](0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
- **0171** — [The mesh binds and delivers a licence; the module alone writes the tool's credential; a switch is the binding changed, asked for through the console](0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md)
- **0164** — [A setting is declared with its default, its meaning and what changing it costs](0164-a-setting-is-declared-with-its-default-its-meaning-and-what-changing-it-costs.md) *(proposed)*
- **0165** — [`container-runtime` is what a machine can run; that a runtime is running is its holder's health](0165-container-runtime-is-what-a-machine-can-run-and-a-running-runtime-is-its-holders-health.md) *(proposed)*
- **0166** — [The container runtime is a node seat, and the host creates containers through its holder](0166-the-container-runtime-is-a-node-seat-and-the-host-creates-containers-through-its-holder.md) *(proposed)*
### How it is built
+1 -9
View File
@@ -4,10 +4,9 @@ status: in-progress
code:
- mesh-controller internal/licences
- mesh-controller cmd/mesh-controller/licence.go
updated: 2026-10-02
updated: 2026-09-05
decisions:
- 02-DECISIONS/0024-model-access-is-a-provision.md
- 02-DECISIONS/0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md
- 02-DECISIONS/0009-modules-and-the-graph.md
- 02-DECISIONS/0054-model-usage-is-recorded-at-two-grains.md
- 02-DECISIONS/0055-model-access-is-answered-by-a-licence-or-a-node.md
@@ -97,13 +96,6 @@ So `(node, module)` tells them apart, and asking for a licence per session neede
identity. Checked rather than argued: two sessions on one machine hold different licences, each is
given its own key, and releasing one leaves the other.
*2026-10-02:* the operator's own interactive agent on a workstation is a consumer the same way —
`(node, claude-code)`, one licence at a time per machine, delivered by the mesh and written by the
module, switched through the console
([ADR 0171](../../02-DECISIONS/0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md),
[36 — The operator's agent on a machine](36-the-operators-agent-on-a-machine.md)). The licence commands
become verbs on the controller's seat for it; none was one before.
**What is still open is the rest of the gap, and it is the harder half.** A *worker* is not one
per machine — many can run on one, from one module — so `(node, module)` cannot name them apart
and this reasoning does not extend to them. That belongs with
@@ -5,10 +5,8 @@ code:
- mesh-controller internal/inventory
- mesh-controller internal/catalogue
- mesh-controller cmd/mesh-controller
updated: 2026-10-02
updated: 2026-10-01
decisions:
- 02-DECISIONS/0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
- 02-DECISIONS/0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
- 02-DECISIONS/0120-a-roster-fact-carries-its-format-as-a-template.md
- 02-DECISIONS/0051-shared-data-is-the-operators.md
@@ -173,14 +171,6 @@ fact, the home as a placement root, and what the mesh may and may not do under a
decision this document names but no record states. They are the next records to write, before the
family of §2 modules is built.
*2026-10-02:* two of them are written. [ADR 0169](../../02-DECISIONS/0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)
records the account as a node fact and the home as a placement root, reconstructed from what shipped;
[ADR 0170](../../02-DECISIONS/0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
generalises §3's boundary to every directory under a home. The first member of the §2 family is
designed in [36 — The operator's agent on a machine](36-the-operators-agent-on-a-machine.md). Still
unwritten: user-scope units, several accounts per node, and the CA. On the same day every node of the
live mesh still carried an empty account.
## Why now, and why not yet
**Why it matters:** when HAL retires, the generators that keep `~/.ssh`, shell config and the
-8
View File
@@ -28,14 +28,6 @@ module's credential and listens on loopback. It has no state, no provision, no s
the bus, which it gets the way every module does: a credential the mesh minted for `<node>.mesh-console`,
sealed to the machine, delivered as the module's own secret.
*2026-10-02:* it gains one provision, at node scope — the MCP endpoint on loopback, serving the port
the machine gave it — so that a module whose software must be told where the console is requires that
and is coupled to an endpoint rather than to a module's name
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)). The first
consumer is the operator's agent, [36 — The operator's agent on a machine](36-the-operators-agent-on-a-machine.md) §6;
a machine without the console refuses such a module by name. Nothing else above changes: no seat, no
state, no tools of its own.
Its manifest says three things nothing else in the catalogue says together:
- `invokes: ["*"]` — it calls every tool on the mesh, and the bus grants exactly that publish side;
@@ -1,257 +0,0 @@
---
layer: to-be
status: designed
code: []
updated: 2026-10-02
decisions:
- 02-DECISIONS/0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
- 02-DECISIONS/0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md
- 02-DECISIONS/0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md
- 02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md
- 02-DECISIONS/0118-undeclaring-gives-a-unit-back-the-state-it-was-found-in.md
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
- 02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md
- 02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md
- 02-DECISIONS/0050-model-access-is-vendor-agnostic.md
- 02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md
---
# 36 — The operator's agent on a machine: the `claude-code` module
**The agent a person runs at a terminal, put on the machine by the mesh, instructed by the mesh,
pointed at the console, and authenticated with a licence the mesh delivers.** It is the first member of
the family [to-be 29 §2](29-a-node-has-operator-accounts.md) names — the modules that place files under
an operator's home — and the smallest, so it is where the pattern is proven before the shell, the
terminal and the desktop follow.
What it replaces: the predecessor's module of the same name, which installed the agent's package and
placed five files under the operator's home, and a sibling that placed a sixth. The predecessor is
retired; those six files are still on both workstations telling every session to use tools that no
longer exist. That is the symptom this design answers, and it answers it by making the files a module's
again rather than by editing them.
## 1. What it is
A module, `claude-code`, universal tier: assigned to every node a person logs into, which is every node
with an operator account ([ADR 0169](../../02-DECISIONS/0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)).
It declares the agent's package, owns the agent's configuration directory under the account's home, and
requires two things: `model-access`, for the licence
([ADR 0171](../../02-DECISIONS/0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md)),
and the console on the same machine, for the tools
([ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)). It names no
node, no path and no login: the account and its home are machine facts, the node's name is a machine
fact, the node's role is a setting on the assignment, and the console's address is what the console
serves.
**The controller has no part in it beyond what it has in every module.** It resolves the account, the
home, the licence and the console's port, and delivers them. It holds nothing about the agent: no file
shape, no key name, no path. The one controller change this design asks for is not about the agent at
all — the licence commands become verbs on the controller's seat, as the other commands did
([ADR 0154](../../02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)).
## 2. What it owns under the home, and what it leaves alone
Every path the module touches is in one of the four classes
[ADR 0170](../../02-DECISIONS/0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
draws, and the class is visible from the shape the definition declares. The agent's directory is
`~/.claude`; its own state file is `~/.claude.json` beside it.
| path | class | declared as |
|---|---|---|
| `~/.claude/` | owned directory | a directory, owner the account, readable by the account alone |
| `~/.claude/CLAUDE.md` | owned | a file: how a session on this mesh works (§4) |
| `~/.claude/rules/00-mesh.md` | owned | a file: this node's identity (§4) |
| `~/.claude/rules/conventions.md` | owned | a file: the rules of the repositories (§4) |
| `~/.claude/skills/mesh-licence/SKILL.md` | owned | a file: the licence skill (§5) |
| `~/.claude/settings.json` | written into | the agent's settings; the mesh's key is `attribution`, and only that (below) |
| `~/.claude.json` | written into | the agent's own state; the mesh's key is the console's entry under the servers the agent speaks to (§3) |
| `~/.claude/.credentials.json` | written by the module's process | a delivered secret and a step (§5) |
| everything else | found | nothing — the person's memory, history, projects, local settings, plugins, their own rules and skills |
**Which keys of the settings file are the mesh's.** A key is the mesh's when it encodes a rule of the
mesh, and the person's when it is a preference. `attribution` — the trailers the agent adds to commits
and pull requests — encodes the repositories' convention and is the mesh's. The model, the spinner, the
drafts, the automation mode and everything else are the person's, and the predecessor's experience with
the model key is the evidence: a mesh that sets a preference reverts a person's choice on every push. A
preference the operator wants on every machine belongs to the family's dotfiles module, not here.
**The agent's own state file is written into for one key.** The agent is told about the console as one
entry among the servers it speaks to, in the file where it keeps that list. Everything else in that
file — the account it is logged in as, its caches, its history of projects — is the agent's, and
ADR 0102's rule is exactly what keeps it: the mesh sets one key and gives it back on undeclare.
## 3. The predecessor's six files
They were placed by a generator that no longer exists; to the mesh they are found. ADR 0170 says what
happens to each kind, and this is the list:
| file | fate |
|---|---|
| `CLAUDE.md`, `rules/conventions.md` | **adopted.** The module declares the same paths; the host keeps the found original once and writes the mesh's content ([ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)) |
| `settings.json` | **written into.** The values the predecessor merged and the person changed since — the model among them — stay; the mesh sets its one key |
| `rules/00-hal-mesh.md` | **removed by the operator, once.** Its successor is `rules/00-mesh.md`; the old name carries the predecessor's and stays otherwise |
| `skills/hal-switch-license/SKILL.md` | **removed by the operator, once.** Its successor is `skills/mesh-licence/SKILL.md` |
| `skills/cleanup/SKILL.md` | **removed by the operator, once.** A repository hygiene skill naming the predecessor's forge and repository; not the mesh's |
The module's documentation names the three removals, so a person assigning it on a workstation that
carried the predecessor knows the step. On a fresh machine there is nothing to remove.
**The console's entry changes name.** The agent on both workstations today reaches the console under
an entry named after this installation. A definition names no installation
([ADR 0155](../../02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md)), so
the module writes the entry as `mesh`, and every tool an agent sees is prefixed accordingly. The
hand-made entry is the person's to remove; until they do, the agent sees the mesh's tools twice.
## 4. What the three documents say
**Prose, not a paste** — the files are the module's; this is what they are for.
**`CLAUDE.md` — how a session on this mesh works.** The console is the only path to the mesh, and its
tools are the vocabulary: the record is asked through the records module's tools, symptom first — the
literal error text before a hypothesis — and that is the *search before you dig* rule rewritten for a
knowledge base that is now the record itself ([ADR 0153](../../02-DECISIONS/0153-the-record-is-read-by-a-module-and-the-console-lists-it.md));
the mesh is asked and changed through the controller seat's verbs — status, plan, assign, push,
settings, and licence once it exists; the forge through the forge module's tools. The hard rules are the
same rules in new words: a file the mesh manages is changed through the verb that owns it or through
the catalogue, never on disk, and `plan` says what the mesh would write; a store's database is never
written by hand; main is never pushed; the mesh creates no symlinks and nobody else does either; a
package is declared, not installed by hand. It uses the glossary's words — controller, foundation,
node, seat, console — and none of the predecessor's.
**`rules/00-mesh.md` — who this node is.** Two facts and one pointer: the node's name, from the
machine; the node's role, from the assignment's settings on this node; and that the other nodes are
asked of the controller's `nodes` verb rather than listed here. The predecessor's rule carried a table
of every node with its public domain and role; a table is a copy that drifts, and the live answer is
one tool call away. No address, no public domain.
**`rules/conventions.md` — the rules of the repositories.** Concise commit messages in the imperative,
focused on why; a branch, a pull request and a human approval for every merge; test before pushing,
because nodes update unattended; follow the playbooks in the record; shared logic in the SDK; the
module repository's rules on manifests. Nothing that names a tool of the predecessor's.
**Where the module gets the name and the role.** The name is a machine fact the controller already
offers a definition. The role is a value a person chooses per node — *the laptop*, *the home-server* —
and is an operator value on the assignment's node layer, refused by name when unset
([ADR 0155](../../02-DECISIONS/0155-a-definition-names-no-installation-and-how-that-is-checked.md)).
So assigning the module to a node is two acts: the assignment, and the node's role in its settings.
## 5. The licence
[ADR 0171](../../02-DECISIONS/0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md)
decides it; this is the shape.
**The consumer** is `(node, claude-code)`: the operator's interactive sessions on that machine, under
that account, on one licence at a time. The module requires `model-access` and is put on a licence like
the consumer module already in the catalogue.
**Delivery and the write.** The mesh delivers the access token sealed to the machine, as a secret in
the module's own state directory, and the bound facts beside it. A step in the module's own process —
the consumer module's existing write, carried over — reads the secret and writes
`~/.claude/.credentials.json`: owned by the account, readable by the account alone, atomically,
access-token-only. The step names the secret file as what it reads and runs again when it changes
([ADR 0099](../../02-DECISIONS/0099-a-step-that-runs-once-names-what-it-reads.md)), and on a schedule as
a backstop, so a refreshed token reaches the file unasked. It runs in the module's own context and
never as the person.
**Refresh** is the manager module's on the control node, as [ADR 0050](../../02-DECISIONS/0050-model-access-is-vendor-agnostic.md)
built it. It is assigned to nothing today and is the first prerequisite of the build.
**The two tools** the module serves, listed by the console under the module's name:
| tool | answers |
|---|---|
| `licence_status` | which licence this machine's agent holds, from the bound facts; when its access token expires; whether the file on disk matches what was delivered — by fingerprint, never by value |
| `licence_switch` | asks the controller seat's `licence` verb to put this consumer on the named licence, waits until the credentials file carries the new licence's token, and answers with the licence's name and expiry. Refuses with the mesh's own words when the licence does not exist or the consumer cannot be put on it |
Neither tool, nor the module's log, nor any event it emits, ever carries a token. The module declares
that it invokes the controller seat's `licence` verb, and nothing else.
**The skill** — `skills/mesh-licence/SKILL.md` — wraps `licence_switch` so a person asks in a sentence,
and carries the predecessor's rules unchanged in substance: never ask for or print a token; never edit
the credentials file by hand; the tool writes the file and the record together; with no licence named,
ask rather than guess.
**Enrolling an account** happens on the manager node: a licence added by name, its grant obtained by a
login in a throwaway home and adopted sealed to that node's key, as the manager module does. **The
shell helper** that ran the agent with a token from a plaintext file is retired and not replaced
([ADR 0171](../../02-DECISIONS/0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md)
says why).
## 6. The console
The agent reaches the mesh through the console on the machine's loopback
([to-be 34](34-the-console.md)). The module must tell the agent the console's address, and the port is
the console's to say: today the console's manifest declares it and the host assigns it, and nothing but
the console knows what was assigned. So **the console provides a node-scoped provision** — the MCP
endpoint on loopback — serving its port, and the module requires it. A requirement names what the
consumer is coupled to ([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)):
the agent is coupled to an MCP endpoint on its own machine, not to a module name. Co-location resolves
it, and a machine without the console refuses the agent module by name — which is right, because an
agent without the console is the predecessor's situation again.
This is a change to the console's definition, not to the controller. [To-be 34 §1](34-the-console.md)
says the console has *no provision*; this is the one it gains, at node scope, and the design is amended
in the same change.
## 7. Scope, settings and the order of assignment
**Every node with an operator account.** None has one today; the operator states them first. A node
with no account refuses the module, naming the fact.
**Per node:** the role, in the module's settings on the node layer. **Per mesh:** nothing.
**Order:** the manager on the control node and a refresh observed; the licences the operator uses,
enrolled; the console's provision and the module in the catalogue; one workstation assigned, the three
predecessor files removed there, and a new session read to confirm it sees the mesh's instructions and
the console's tools; then the rest.
## 8. The package
The module declares the agent's package. The distribution every node of the live mesh runs does not
carry it in its repositories: the two workstations have it from a build the predecessor's helper made
from the community repository, and nothing updates it since the predecessor retired. On those two the
declaration is satisfied — the package is present. **On a fresh machine the host's package manager
refuses it, in its own words, and the module is not applied there.** That is correct and is a gap.
The answer the mesh already has a shape for is a package repository for this ecosystem as a seat
([ADR 0109](../../02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md)), fed by the
builder with a package it builds from the vendor's release, and trusted by every node's package manager.
Then `package: claude-code` is answered the way every package is, and updates arrive the way every
update does. It is not built, and it is not this module's to build: it is a seat and a provider module
of its own, needed by every package the distribution does not carry.
Rejected as the answer: the vendor's own installer, which puts a self-updating binary under the
person's home. It is a hand-installed package the mesh cannot see, reproduce or roll back, and it
updates itself outside the mesh — the arrangement the manifest rule *never install a package by hand*
exists to end.
## How it is checked
| Check | Defends |
|---|---|
| the module's definition names no node, path or login, and declares no secret in a file's content | ADR 0112, ADR 0155, ADR 0171 |
| on a lab machine with an account, a seeded home holding a person's rule file, the predecessor's three leftovers and a settings file with the person's model: after assign, the mesh's files are present and owned by the account, the person's file and model are byte-identical, the leftovers are untouched, the console's entry is set; after unassign, the mesh's files are gone, the two keys are given back, the directory and everything else stand | ADR 0170 |
| on a lab machine with no account, the assignment is refused naming the fact | ADR 0169 |
| the credentials file is owned by the account, readable by it alone, and names no refresh token; a switch through the console changes the licence named in the bound facts and the file's fingerprint; neither the tool's answer nor the module's log holds a token | ADR 0171, ADR 0050 |
| the console's provision resolves by co-location and a machine without the console refuses the module by name | ADR 0027, ADR 0152 |
| a new session on the assigned workstation lists the console's tools under the `mesh` prefix and answers "which node am I" from the identity rule | the exit of the build |
| the package is reported present on the workstations and refused in the package manager's words on a machine without it | §8, honestly |
## What this does not settle
- **Several operator accounts on one node.** ADR 0169 decides one; the module follows.
- **A parallel session under another licence on the same machine.** The retired helper allowed it by
keeping tokens readable; a clean form needs a second consumer identity (to-be 14's open half).
- **The package repository seat.** §8 names it and leaves it to its own design.
- **The rest of the family** — shell, terminal, desktop, user-scoped services — each a module of the
same shape, each proving nothing new about ownership and something new about its own tool.
## References
- [ADR 0169](../../02-DECISIONS/0169-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md),
[ADR 0170](../../02-DECISIONS/0170-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md),
[ADR 0171](../../02-DECISIONS/0171-the-mesh-binds-and-delivers-a-licence-and-the-module-writes-the-tools-credential.md) — the three decisions this rests on
- [to-be 29](29-a-node-has-operator-accounts.md) — the family; [to-be 14](14-model-access.md),
[to-be 15](15-the-agent-session.md) — the licence and the consumer; [to-be 34](34-the-console.md) — the console
- the predecessor's `claude-code` module and its sibling's identity rule — what is replaced, read from the workstations on 2026-10-02
- mesh-catalog `modules/anthropic-consumer` — the write this module carries over; `modules/anthropic-manager` — the refresh it depends on
@@ -0,0 +1,85 @@
---
status: located
opened: 2026-10-01
located-in: [mesh-catalog modules/dnsmasq, mesh-controller internal/overlay/generator.go, mesh-controller internal/catalogue/resolve.go (checkResources)]
fixed-by:
amended-design:
---
# 190 — The container runtime's configuration is written by modules that are not the runtime's
## What was observed
The runtime's configuration file and its service are declared by two parties, neither of which is
the runtime:
- **The resolver module** writes the runtime's `dns` key (the machine's private address) and
`live-restore` into the runtime's file, written into rather than over
([ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)). It also
declares the runtime's service, reloaded when that file changes. The `dns` key has been written
since the resolver module was converted from its predecessor on 2026-09-23; `live-restore` and the
service were added on 2026-09-30 while fixing
[issue 110](../110-a-container-on-the-runtimes-own-network-cannot-reach-the-resolver/00-report.md),
where containers silently resolved through a public resolver.
- **The private network** writes the runtime's `insecure-registries` into the same file, and declares
the same service reloaded on it, as [ADR 0082](../../02-DECISIONS/0082-the-registry-is-reached-by-name-and-trusted-by-the-overlay.md)
and ADR 0102 decided. The controller generates both resources per machine.
On the three machines that run the resolver module, both declare one path and one unit. Nothing refuses
it. The collision check compares the resources of catalogue modules. The private network is computed,
so its resources are produced when a machine's declaration is composed, and the check never sees them.
The machine without the resolver module shows the other half. Its runtime still has the predecessor's
resolver and `live-restore` off, because the only module that sets them is a DNS server. A machine
gets a correct container runtime only as a side effect of being given a resolver.
> **Later the same day, 2026-10-02.** The resolver module and its sibling for the resolver file were
> assigned to the fourth machine ([issue 198](../198-the-lans-dns-server-ran-outside-the-mesh-and-its-filter-closed-it/00-report.md)),
> so all four now have the resolver writing into the runtime's file, and the predecessor's
> `live-restore: false` there is gone. The same work made the runtime's file, as the resolver declares
> it, take no settings: a setting meant for the resolver's own configuration had reached it. The
> collision and the ownership question above are unchanged.
## Why this is here
The operator ruled it a defect, not a design: **a module does not write another software's
configuration.** The need behind each write is real. Containers must resolve the mesh's names
([ADR 0148](../../02-DECISIONS/0148-the-meshs-names-are-resolved-not-copied-into-containers.md) step 2).
A daemon restart must not stop every container. Every machine on the network must trust the mesh's
registry. But each of these is a fact the runtime must be *given*, and the module that gives it is the
runtime's own. With three writers, nobody can say what the file should contain. Two of the facts are
reloaded when one of them needs a restart (issue 110's first fault). And the moment a module for the
runtime exists, it is refused on every machine with the resolver, or, through the private network's
path, accepted without anyone noticing a collision.
## What resolves it
[ADR 0166](../../02-DECISIONS/0166-the-container-runtime-is-a-node-seat-and-the-host-creates-containers-through-its-holder.md)
gives the runtime a module that holds its seat and owns its file and service.
[ADR 0164](../../02-DECISIONS/0164-a-setting-is-declared-with-its-default-its-meaning-and-what-changing-it-costs.md)
gives that module declared settings with defaults. The fix, once both are accepted:
1. The resolver module drops its runtime file and runtime service. It knows nothing of the runtime.
2. The private network stops generating either resource. ADR 0082's decision stands — being on the
network is what grants the trust, and no module author is involved — and only *who writes it*
moves. The mesh gives the registry to the runtime module as a value. ADR 0082 and ADR 0102 each
get a dated note saying where their mechanism now lives.
3. The runtime module writes `dns`, `live-restore` and `insecure-registries`, each a declared
setting with its cost: `dns` costs a restart, which `live-restore` makes harmless.
4. Steps 1–3 land in one push. A runtime module declaring the file beside a resolver module still
declaring it is refused.
5. The collision check sees a computed module's resources as well, so a second writer cannot come
back through generated code.
## Open questions
- **How the resolver's address reaches the runtime.** Either the resolver seat (`node-dns-resolver`)
delivers an address its holder serves, or the runtime module reads a machine fact and the seat
being held is only a precondition. The first tracks a resolver moving off the private address. The
second needs nothing new.
- **What `dns` defaults to on a machine with no resolver seat held.** Nothing, leaving the runtime's
own behaviour, is the honest default. A public resolver hides exactly the failure issue 110 took a
day to find.
- **The adopted machine's predecessor values.** The runtime module adopting a file with a
hand-written `dns` and `live-restore: false` replaces both. That is intended, and is the one
restart the operator must make on that machine.