Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17ca9a262b | ||
|
|
967c793eaa | ||
|
|
27c1db8a86 | ||
|
|
3d54fcbb86 | ||
|
|
f1941304cc |
@@ -131,22 +131,6 @@ def main():
|
|||||||
else:
|
else:
|
||||||
seen[number] = name
|
seen[number] = name
|
||||||
|
|
||||||
# And decision records, which 155's fix left out: on 2026-10-02 two ADRs numbered 0169 landed
|
|
||||||
# on main from two sessions within the hour, and every check passed.
|
|
||||||
seen_records = {}
|
|
||||||
for path in sorted(glob.glob(os.path.join(ROOT, "02-DECISIONS", "[0-9]*.md"))):
|
|
||||||
name = os.path.basename(path)
|
|
||||||
number = name.split("-", 1)[0]
|
|
||||||
if not number.isdigit():
|
|
||||||
continue
|
|
||||||
if number in seen_records:
|
|
||||||
bad(os.path.join("02-DECISIONS", name),
|
|
||||||
"is numbered %s, and so is %s -- a record's number is how it is cited. Take the next "
|
|
||||||
"free number across main AND every open pull request; the branch that lands last "
|
|
||||||
"renumbers" % (number, seen_records[number]))
|
|
||||||
else:
|
|
||||||
seen_records[number] = name
|
|
||||||
|
|
||||||
for path in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))):
|
for path in sorted(glob.glob(os.path.join(ROOT, "04-ISSUES", "*", "00-report.md"))):
|
||||||
front = frontmatter(path)
|
front = frontmatter(path)
|
||||||
if front is None:
|
if front is None:
|
||||||
|
|||||||
@@ -95,21 +95,3 @@ another — and a mesh you cannot name precisely is a mesh two people describe d
|
|||||||
A new name for an existing thing lands here first, in the same change that introduces it in code. A
|
A new name for an existing thing lands here first, in the same change that introduces it in code. A
|
||||||
record under `02-DECISIONS/` keeps whatever word it was written with — those are immutable — so a
|
record under `02-DECISIONS/` keeps whatever word it was written with — those are immutable — so a
|
||||||
term retired here may still appear there, and the mapping above is how to read it.
|
term retired here may still appear there, and the mapping above is how to read it.
|
||||||
|
|
||||||
## The operator's machine
|
|
||||||
|
|
||||||
- **node tools** — the one tool runtime per node, a host-side process the host supervises, that loads
|
|
||||||
every assigned module's tools bundle and serves every tool and held seat's verb on the subjects the
|
|
||||||
memberships issue; its serving mode on loopback is what was called **the console**
|
|
||||||
([ADR 0175](../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)).
|
|
||||||
Replaces **"console"** as the module's name; *console* remains the word for the person's end of it.
|
|
||||||
- **bundle** — the artifact a module's tools are built into, interpreted or compiled; never an image.
|
|
||||||
- **kept region** — a marked block in a managed file the mesh writes *into*, where the operator's own
|
|
||||||
lines survive every push and are given back when the module goes
|
|
||||||
([ADR 0174](../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)).
|
|
||||||
One of the two ways a node varies a module; the other is a **setting**.
|
|
||||||
- **installed / holding** — a module may be assigned (its package installed, its files placed) without
|
|
||||||
holding the seat its family declares; *holding* is being the one — the login shell, the display
|
|
||||||
session — on that node ([ADR 0176](../02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md)).
|
|
||||||
- ~~flavor~~ — not used. What a flavor varied is a setting or a separate module.
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
---
|
|
||||||
status: graduated
|
|
||||||
initiated: 2026-10-02
|
|
||||||
touches:
|
|
||||||
- 02-DECISIONS/0040-what-a-module-is.md
|
|
||||||
- 02-DECISIONS/0011-managed-files-are-generated-never-edited.md
|
|
||||||
- 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
|
|
||||||
- 02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md
|
|
||||||
- 02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md
|
|
||||||
- 02-DECISIONS/0161-what-deserves-a-seat.md
|
|
||||||
- 03-DESIGN/01-to-be/05-the-node-host.md
|
|
||||||
- 03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md
|
|
||||||
- 03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md
|
|
||||||
- 03-DESIGN/01-to-be/34-the-console.md
|
|
||||||
- 03-DESIGN/00-as-is/10-module-catalogue.md
|
|
||||||
- 04-ISSUES/160-a-machine-says-little-about-itself-and-only-when-asked/00-report.md
|
|
||||||
- 04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md
|
|
||||||
became:
|
|
||||||
- 03-DESIGN/01-to-be/37-the-operators-machine.md
|
|
||||||
- 02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md
|
|
||||||
- 02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md
|
|
||||||
- 02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md
|
|
||||||
- 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md
|
|
||||||
- 02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 018 — The operator's machine as modules
|
|
||||||
|
|
||||||
**What.** The mesh owns the whole machine, not only the services on it. Everything a person
|
|
||||||
configures on a node — the login manager, the display server, the window manager, the shell, the
|
|
||||||
terminal, the launcher, the notifier, the audio setup, the boot images, the downloads folder, the
|
|
||||||
agent at the terminal — is a module: a package, the files it owns under `/etc` and under the
|
|
||||||
operator's home, the seat it holds, the tools it serves. One default configuration per module,
|
|
||||||
varied per node only through settings rendered into the file or a kept operator region, never
|
|
||||||
through an edit. The servers take the universal modules (shell, prompt, git, the agent); the
|
|
||||||
workstations take those and the graphical stack, which a capability the machine reports gates.
|
|
||||||
This effort writes that behaviour down, measures what the predecessor's desktop modules actually
|
|
||||||
contain, and settles what the mesh must gain before the first of them can be written.
|
|
||||||
|
|
||||||
**Why.** The predecessor is retired on every node. What it still owned on the two workstations —
|
|
||||||
about thirty modules' worth of dotfiles, user units and `/etc` files — is now owned by nothing:
|
|
||||||
no generator regenerates them, and a fix to one of them is a hand edit that nothing records. The
|
|
||||||
migration scoped these modules out as *the workstation's own environment*, and
|
|
||||||
[to-be 29](../../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) names them as the last
|
|
||||||
thing the predecessor was keeping alive. To-be 29 covers one directory, `~/.ssh`, and draws a
|
|
||||||
boundary inside it. The operator wants no boundary: the machine is the mesh's, as far as it makes
|
|
||||||
sense to configure it. That is a wider scope than any design states, and it reaches three records
|
|
||||||
that were written for services: what a module is, where a module's tools run, and what a managed
|
|
||||||
file may be.
|
|
||||||
|
|
||||||
**What it touches.** The module definition ([ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md)),
|
|
||||||
seats and their contracts ([ADR 0132](../../02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md)),
|
|
||||||
where a module's tools run ([ADR 0150](../../02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md),
|
|
||||||
[ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md),
|
|
||||||
[to-be 33](../../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §6), the host's vocabulary
|
|
||||||
([to-be 05](../../03-DESIGN/01-to-be/05-the-node-host.md)), managed files and settings
|
|
||||||
([ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md),
|
|
||||||
[issue 168](../../04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md)),
|
|
||||||
and the catalogue's shape ([as-is 10](../../03-DESIGN/00-as-is/10-module-catalogue.md)).
|
|
||||||
|
|
||||||
**Documents.**
|
|
||||||
|
|
||||||
- [01 — The intended behaviour](01-the-intended-behaviour.md): the operator's wish, written as
|
|
||||||
how the mesh behaves, in the mesh's own words.
|
|
||||||
- [02 — What exists, and what is missing](02-what-exists-and-what-is-missing.md): the
|
|
||||||
predecessor's desktop modules measured; which records already say what is wanted; the gaps.
|
|
||||||
- [03 — One tool executor per node](03-one-tool-executor-per-node.md): where a module's tools
|
|
||||||
run. The direction the operator set, the evidence for it, and what it supersedes.
|
|
||||||
- [04 — The seats of the environment](04-the-seats-of-the-environment.md): the roles a machine
|
|
||||||
has once, their candidate contracts, and what gates each.
|
|
||||||
|
|
||||||
**What it had to settle, and where each landed.** *(Graduated 2026-10-02.)*
|
|
||||||
|
|
||||||
1. A module is one *managed thing*, software or not, and every module may serve tools — or ADR
|
|
||||||
0040 already says this and only its examples are narrow.
|
|
||||||
2. One tool executor per node, host-side, module-agnostic; which records it supersedes and
|
|
||||||
in what form the console continues.
|
|
||||||
3. Per-node variation is a setting rendered into the file or a kept region, never an edit —
|
|
||||||
ADR 0011 stands — and issue 168 is fixed before any environment module carries a setting.
|
|
||||||
4. User-scoped units on the host's `service` shape, and a service-manager seat whose holder
|
|
||||||
serves the tools about them.
|
|
||||||
5. The operator account stated on every node; today no node record carries one.
|
|
||||||
6. The seats of the environment and their verbs, one record per seat, slowly, because a
|
|
||||||
seat's tools bind every future holder.
|
|
||||||
7. Where the environment modules live: this catalogue, or one of their own as the media chain
|
|
||||||
has; and whether a third-party organisation's tooling belongs in a public catalogue at all.
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
# 01 — The intended behaviour
|
|
||||||
|
|
||||||
*Written 2026-10-02 from the operator's words, in the mesh's words. What is wanted, before what
|
|
||||||
exists. Where a sentence restates a record, the record is named; where it goes further, that is
|
|
||||||
said.*
|
|
||||||
|
|
||||||
## The machine is the mesh's
|
|
||||||
|
|
||||||
**Everything configurable on a node is declared by a module.** Not only the services the mesh
|
|
||||||
runs: the login manager, the display server, the window manager, the bar, the launcher, the
|
|
||||||
notifier, the compositor, the lock screen, the terminal emulator, the clipboard, the shell and its
|
|
||||||
prompt, the editor, the audio setup, the boot images, the package manager's configuration, the
|
|
||||||
agent a person runs at a terminal, and the folders a person works in — a downloads folder that is
|
|
||||||
tidied, backed up, distributed to other nodes and asked questions of. System folders and the
|
|
||||||
operator's home alike. The operator is the only person on every node, so the mesh manages the
|
|
||||||
person's machine, not a machine with a person on it.
|
|
||||||
|
|
||||||
This is [ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md)'s definition applied without
|
|
||||||
the service bias its examples carry. A module is one managed thing, named once, described
|
|
||||||
completely by its manifest. It may have a package, files, a container, a unit, a binary, a seat it
|
|
||||||
holds, and tools it serves — any one of these, or all, or two. There is **no kind of module**: zsh
|
|
||||||
has a package, files, a seat claim and the tools that claim obliges it to serve; downloads has a
|
|
||||||
folder, a process and tools; nftables has a package, files, a service, a seat and tools. The
|
|
||||||
difference is what each declares, not what each is.
|
|
||||||
|
|
||||||
**The home has no boundary.** [To-be 29](../../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md)
|
|
||||||
owns one directory under the home and draws a line inside it between the mesh's and the person's.
|
|
||||||
Here the line is drawn only by what the modules declare: every file some module places is the
|
|
||||||
mesh's; what no module declares is found and left alone, exactly as the adoption rules already
|
|
||||||
say for a machine. The reach is bounded by sense, not by a rule — the mesh configures what can
|
|
||||||
be configured, and a person's documents, projects and history are data under
|
|
||||||
[ADR 0051](../../02-DECISIONS/0051-shared-data-is-the-operators.md), not configuration.
|
|
||||||
|
|
||||||
**A module names no node and no path.** The operator account is a node fact and the home is
|
|
||||||
derived from it ([to-be 29](../../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) §1–2,
|
|
||||||
shipped in the controller; its record is proposed in an open change). A module places a file
|
|
||||||
*under the home, owned by the account*, and the same manifest lands on a server and a laptop.
|
|
||||||
|
|
||||||
## One default, varied by settings, never by edits
|
|
||||||
|
|
||||||
**One module, one default configuration.** The window manager module ships the configuration
|
|
||||||
that is right for every node. There are no flavors: the predecessor's one desktop module carried
|
|
||||||
four, one per class of machine, and what differed between them is what settings are for.
|
|
||||||
|
|
||||||
**A node varies a module in exactly two ways.** A **setting**, declared by the module with its
|
|
||||||
type, meaning and default (proposed alongside the container-runtime records), set for the mesh
|
|
||||||
or for one node, and rendered into the file at composition — the value is in the file, not in an
|
|
||||||
environment variable the file reads. Or a **kept region**: a block in a file the mesh writes
|
|
||||||
*into*, where the operator's own lines survive every push
|
|
||||||
([ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md)). An
|
|
||||||
edit to a managed file outside such a region is not a third way; it is overwritten, as
|
|
||||||
[ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md) says, and the
|
|
||||||
predecessor's habit of adopting disk drift back into its database is not carried over.
|
|
||||||
|
|
||||||
The predecessor's theming — some ninety environment variables substituted into templates at sync
|
|
||||||
time, with tools to list and set them — is the same idea with the wrong rendering. The knobs
|
|
||||||
become declared settings; the file carries the value.
|
|
||||||
|
|
||||||
## Roles a machine has once are seats, and seats carry tools
|
|
||||||
|
|
||||||
**A role a machine fills at most once is a node-scoped seat**, declared by a module
|
|
||||||
([ADR 0121](../../02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md),
|
|
||||||
[ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md)): the login shell, the
|
|
||||||
display session, the display server, the terminal emulator, the launcher, the notifier, the
|
|
||||||
compositor, the lock screen, the service manager, the boot loader. Several modules may be able to
|
|
||||||
hold one — zsh, fish and bash can all hold the login shell — and the assignment on each node says
|
|
||||||
which does. Installing a shell is installing software; holding the seat is being *the* shell.
|
|
||||||
|
|
||||||
**A seat's contract is its tools** ([ADR 0132](../../02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md)).
|
|
||||||
Every holder of the login-shell seat serves `execute`, which takes one string, the command, and
|
|
||||||
runs it on the node the seat is scoped to. Every holder of the boot seat serves "rebuild the boot
|
|
||||||
images", so *"rebuild your boot images"* is a verb addressed to a machine, not a one-off step in
|
|
||||||
a hook. Every holder of the service-manager seat answers for the units on the machine, system and
|
|
||||||
user scope. A module may serve its own tools beside the seat's
|
|
||||||
([ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md) §2): show the rendered
|
|
||||||
configuration, set a theme value, report status.
|
|
||||||
|
|
||||||
**Any tool may be called from any node.** The operator's statement, and the grant model it
|
|
||||||
implies: the executor on each node may call everything, as the console already may. A verb that
|
|
||||||
needs root on the machine is the module's concern — the tool escalates, the executor and the
|
|
||||||
caller do not know.
|
|
||||||
|
|
||||||
## Servers and workstations differ by capability, not by catalogue
|
|
||||||
|
|
||||||
The same catalogue serves every node. A module declares what it needs — a graphical session, a
|
|
||||||
display server, a container runtime — and the machine reports what it has, as the profile already
|
|
||||||
reports eight capabilities today ([issue 160](../../04-ISSUES/160-a-machine-says-little-about-itself-and-only-when-asked/00-report.md)).
|
|
||||||
Assignment refuses the wrong placement by name
|
|
||||||
([ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md) §3). So every node takes the shell,
|
|
||||||
the prompt, git and the agent; only a node with a graphical session can take the display server,
|
|
||||||
and only a node holding the display server can take a window manager. Nothing in a module says
|
|
||||||
"workstation".
|
|
||||||
|
|
||||||
## What the operator would say to the mesh
|
|
||||||
|
|
||||||
*Set the login shell on the build node to fish. Rebuild the laptop's boot images. Show me the
|
|
||||||
window manager's effective configuration on the desktop and where each value comes from. Give
|
|
||||||
the downloads folder on the laptop to the home server. Run `uptime` on every node.* Each of these
|
|
||||||
is a seat verb or a module tool, addressed to a node, answered by whatever holds the role there.
|
|
||||||
-91
@@ -1,91 +0,0 @@
|
|||||||
# 02 — What exists, and what is missing
|
|
||||||
|
|
||||||
*Measured 2026-10-02 on one installation: two workstations, two servers, all four converged to
|
|
||||||
the mesh; the predecessor retired on the last workstation the day before. Numbers are from the
|
|
||||||
machines and the repositories, not from memory.*
|
|
||||||
|
|
||||||
## 1. What the predecessor's desktop looks like
|
|
||||||
|
|
||||||
The predecessor's catalogue on the laptop held **34 modules**, of which **28** are the operator's
|
|
||||||
environment rather than services. By what they declare:
|
|
||||||
|
|
||||||
| shape | count | examples |
|
|
||||||
|---|---|---|
|
|
||||||
| package only | 9 | browser, mail client, process monitor, media player, file manager, chat |
|
|
||||||
| package + `/etc` files + system service | 5 | login manager, display server, power and thermal daemons, package manager configuration |
|
|
||||||
| package + files under the home | 6 | shell and prompt, the agent at the terminal, scripts, the sync client, a music player |
|
|
||||||
| files under the home + user units + hooks | 2 | the desktop environment, audio |
|
|
||||||
| third-party organisation tooling | 6 | out of scope here |
|
|
||||||
|
|
||||||
**The desktop module alone** declares **88 files**, **4 flavors** (the window-manager stack, and
|
|
||||||
one per class of machine), **2 user units** with a hook to enable them, 8 files under `/etc`, a
|
|
||||||
wallpaper shipped as an asset, and reads **about 90 environment variables** as theme knobs,
|
|
||||||
substituted into its templates at sync time and set through a theming tool. Its hook exists
|
|
||||||
because *shipping a unit file does not run it*: one unit had been deployed for months and ran on
|
|
||||||
one machine only, because somebody had enabled it there by hand.
|
|
||||||
|
|
||||||
**The shell module** ships `~/.zshrc`, the prompt configuration, an `~/.ssh/config` that the
|
|
||||||
predecessor generated from its registry, and a `LOGIN_SHELL` variable applied with `chsh` by a
|
|
||||||
hook. Two flavors: the prompt theme, and autocompletion.
|
|
||||||
|
|
||||||
**Other modules write into the desktop module's files.** The chat client places i3 and notifier
|
|
||||||
snippets into `config.d` directories the desktop module owns, and its launch flags, window
|
|
||||||
placement and notification colours are each a variable with a default.
|
|
||||||
|
|
||||||
**One-off steps live in hooks** across the set: enable user units, `chsh`, create a swap file,
|
|
||||||
`mkinitcpio`, enable a vendor VPN service the package ships disabled. Every one is state the
|
|
||||||
host could declare or a verb a seat could serve; none is today.
|
|
||||||
|
|
||||||
## 2. What the migration did with them
|
|
||||||
|
|
||||||
The migration's module to-do scoped the whole set out as *desktop / workstation ricing — the
|
|
||||||
workstation's own environment* and *node/OS tooling — managed on the node, never catalogue*. The
|
|
||||||
last workstation's runbook then split the same set three ways: **A**, system scope, which the
|
|
||||||
host's vocabulary can express today (the login manager, the display server, the power daemons,
|
|
||||||
the package manager, the container runtime); **B**, under a home or a user unit, waiting on
|
|
||||||
to-be 29; **C**, package only, the operator's call. The migration log closes the workstation with
|
|
||||||
*the operator's desktop awaiting its design*.
|
|
||||||
|
|
||||||
Two things followed from scoping them out. Nothing regenerates those files now, so a fix is a hand
|
|
||||||
edit — the login manager's session script was fixed this way on the day of writing, and recorded
|
|
||||||
in a repository nothing deploys from. And the one piece of this family written as a mesh module,
|
|
||||||
the ssh client, was closed on hold in the catalogue until the controller carried the account fact.
|
|
||||||
|
|
||||||
## 3. What the records already give
|
|
||||||
|
|
||||||
| wanted | record | state |
|
|
||||||
|---|---|---|
|
|
||||||
| one module per managed thing; every module may have tools | [ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md) | accepted; examples are services, and the shell is named as a *shared* seat |
|
|
||||||
| a module declares its own node-scoped seat | [ADR 0121](../../02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md), [ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md) | accepted |
|
|
||||||
| a seat's contract is its tools; a holder may add its own | [ADR 0132](../../02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md), [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md) | accepted; one node seat serves verbs live |
|
|
||||||
| a capability the machine reports gates a holder | [ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md) §3 | accepted; the profile already reports `graphical-session` |
|
|
||||||
| the account as a node fact; a file under the home owned by it | [to-be 29](../../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) §1–2 | built in the controller; its record proposed in an open change |
|
|
||||||
| inside a home: owned, written into, written by the module, found | proposed in the same change | proposed |
|
|
||||||
| a setting declared with type, meaning, default and cost | proposed with the container-runtime records | proposed |
|
|
||||||
| a managed file is derived; an edit is overwritten | [ADR 0011](../../02-DECISIONS/0011-managed-files-are-generated-never-edited.md) | accepted |
|
|
||||||
| the mesh writes into a shared file, never over it | [ADR 0102](../../02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md) | accepted |
|
|
||||||
| a module names no path; the host resolves the home | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md) | accepted |
|
|
||||||
| the `user` shape: a login shell is declared state | [to-be 05](../../03-DESIGN/01-to-be/05-the-node-host.md) | designed; used by no module |
|
|
||||||
|
|
||||||
## 4. What is missing
|
|
||||||
|
|
||||||
1. **The account is recorded nowhere.** The node record has the column; on all four nodes it
|
|
||||||
is empty. Every home-scoped module is unassignable until the operator states it.
|
|
||||||
2. **User-scoped units.** The host's `service` shape has no user scope. To-be 29 says it
|
|
||||||
plainly: *a workstation's per-user daemons have no form the mesh can send.* The desktop
|
|
||||||
module's two units, the audio masks, the power module's memory guard and the thermal
|
|
||||||
daemon's profile switcher all need it.
|
|
||||||
3. **One-off steps.** `mkinitcpio`, `chsh`, creating a swap file. Each is either declared
|
|
||||||
state the host lacks a shape for, or a verb a seat should serve. An action in a declaration
|
|
||||||
is refused over the link, and rightly.
|
|
||||||
4. **Settings leak** ([issue 168](../../04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md)):
|
|
||||||
a setting reaches every mergeable file and every contribution of its module. Ninety theme
|
|
||||||
knobs on that mechanism would reach ninety files. The proposed settings record says a setting
|
|
||||||
names the file it lands in; that has to ship first.
|
|
||||||
5. **Where tools run.** Every module that serves a tool today does so from its own container
|
|
||||||
per node. See [03](03-one-tool-executor-per-node.md).
|
|
||||||
6. **A seat's verbs are undecided for every seat but three.** To-be 33 leaves which verbs each
|
|
||||||
seat serves as *a decision per seat, slowly*. The environment adds a dozen seats.
|
|
||||||
7. **Catalogue placement.** The media chain left this catalogue for its own; whether the
|
|
||||||
environment does the same, and whether a third-party organisation's tooling belongs in a
|
|
||||||
public catalogue, are unasked.
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# 03 — One tool executor per node
|
|
||||||
|
|
||||||
*The direction the operator set on 2026-10-02, the evidence it rests on, and what it supersedes.
|
|
||||||
A direction, not yet a decision: the record is written when this effort graduates.*
|
|
||||||
|
|
||||||
## Where tools are served today
|
|
||||||
|
|
||||||
[To-be 33](../../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) names three families — a
|
|
||||||
role's tools on the seat, a module's own tools on the module, the mesh's own verbs on the
|
|
||||||
controller seat — and [ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
|
|
||||||
says a runtime serves the subjects its membership issues. What *runs* that runtime is
|
|
||||||
[ADR 0150](../../02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md):
|
|
||||||
one supervised process per module, under the module's own account, carrying that module's
|
|
||||||
compiled tools. Measured on the live mesh:
|
|
||||||
|
|
||||||
| who answers | how it runs | count |
|
|
||||||
|---|---|---|
|
|
||||||
| the mesh's own verbs | the controller binary, on its node | 17 verbs |
|
|
||||||
| the store seat | the store's own runtime | 2 verbs |
|
|
||||||
| the packet-filter seat | **a container per node**, built on the tool-runtime base image, with the network namespace and `NET_ADMIN`, on all four nodes | 3 verbs and 1 own tool |
|
|
||||||
| every module's own tools | the module's container, one per node it runs on | 67 tools across the catalogue |
|
|
||||||
| the console | a container per node, loopback MCP, `invokes: *` | serves none, calls all |
|
|
||||||
| the host | — | serves nothing; answers no question about the machine |
|
|
||||||
|
|
||||||
**The packet-filter holder is the case to look at.** The module is a package, three files and a
|
|
||||||
system service. To serve three verbs it also declares a built image and a container on every
|
|
||||||
node whose only job is to answer them. Scaled to the environment — a shell, a prompt, a launcher,
|
|
||||||
a notifier, a compositor, a login manager, a service manager, a boot loader, a downloads folder —
|
|
||||||
that is one container per module per node for software that is itself not a container, and the
|
|
||||||
operator's judgement is that tools should not run inside a container at all.
|
|
||||||
|
|
||||||
## The direction
|
|
||||||
|
|
||||||
**One tool executor per node, on the host side.** A process the host supervises, the way the
|
|
||||||
launcher supervises the host ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)): not a
|
|
||||||
container, one bus credential for the node, module-agnostic. It loads the tool code of every
|
|
||||||
module assigned to the node and serves each module's tools and each held seat's verbs on the
|
|
||||||
subjects the membership issues — nothing changes in what [ADR 0159](../../02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)
|
|
||||||
and [ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
|
|
||||||
say about subjects, grants and memberships; what changes is that one process subscribes for the
|
|
||||||
node instead of one per module.
|
|
||||||
|
|
||||||
- **A module brings its tools as a built artifact**, a bundle the pipeline produces, never an
|
|
||||||
image. The executor knows bundles and subjects; it knows nothing of zsh or nftables.
|
|
||||||
- **A tool is code the module wrote**, one function behind an MCP verb. `execute` on the shell
|
|
||||||
seat is a function with a string argument. The executor does not declare, template or
|
|
||||||
interpret tools; it runs them.
|
|
||||||
- **Root is the module's concern.** A tool that must change the packet filter or rebuild boot
|
|
||||||
images escalates itself. The executor does not run as root for everyone, and the caller does
|
|
||||||
not know.
|
|
||||||
- **Any node may call any tool on any node.** The executor's credential may call everything,
|
|
||||||
as the console's already does; per-module grants on the calling side are not kept.
|
|
||||||
- **The mesh's own verbs stay with the controller** ([ADR 0154](../../02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)),
|
|
||||||
and a mesh-scoped seat's verbs run on the node that holds it
|
|
||||||
([ADR 0121](../../02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)).
|
|
||||||
No hub is added; the controller's node is already one.
|
|
||||||
|
|
||||||
**The console is the executor, renamed.** It already runs on every node with a credential that
|
|
||||||
may call everything, and it already serves the mesh's tools to whoever is on the machine over
|
|
||||||
MCP on loopback ([ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)).
|
|
||||||
It moves out of its container into the host's process tree, gains the serving half, and takes a
|
|
||||||
name that says what it is — *the node's tool runtime* or simply *node tools*; "console" names
|
|
||||||
the operator's half only.
|
|
||||||
|
|
||||||
## What it supersedes, and what it keeps
|
|
||||||
|
|
||||||
| record | effect |
|
|
||||||
|---|---|
|
|
||||||
| [ADR 0047](../../02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md), [ADR 0150](../../02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md) | superseded *for tools*: one process per node runs every module's tool code, under one account. A module's long-running service — a daemon, a container — is untouched; the executor runs tools, not services. The record must say why one account for every module's tools is acceptable: every tool may be called from every node anyway, and root is taken by the tool, not granted to the process |
|
|
||||||
| [ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md) | kept in substance — a module, assigned per node, loopback MCP, the machine's login is the authority — changed in form: host-side, not a container; serves as well as calls; renamed |
|
|
||||||
| [to-be 33](../../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §6, [to-be 34](../../03-DESIGN/01-to-be/34-the-console.md) | amended the same way |
|
|
||||||
| [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md) §3, *a container may ask for a capability* | moot for that holder: the verbs run on the host side and escalate as they need |
|
|
||||||
| the container-runtime seat, proposed in an open change: *the holder runs as a supervised process and serves the verbs locally to the host and on the bus* | consistent — a supervised process serving verbs is what the executor is; the open question is whether that holder keeps its own process or serves through the executor like everyone else |
|
|
||||||
| the tool-runtime base image | no longer the way tools reach a node; may remain the way a module's *service* is built |
|
|
||||||
|
|
||||||
## What stays open
|
|
||||||
|
|
||||||
- **The executor's language.** The host is a static Go binary and loads no plugins, so the
|
|
||||||
executor is a sibling process, and its language decides the language of every tool bundle.
|
|
||||||
One decision, taken once.
|
|
||||||
- **How a bundle reaches the node.** An artifact of the module's build, delivered as the host
|
|
||||||
delivers everything else; whether it is a file resource in the declaration or a thing the
|
|
||||||
executor fetches by digest.
|
|
||||||
- **Reload.** A push that adds or upgrades a module's bundle reaches a running executor as a
|
|
||||||
reload, not a restart, or every tool on the node blinks on every push.
|
|
||||||
- **The host's own questions.** [Issue 160](../../04-ISSUES/160-a-machine-says-little-about-itself-and-only-when-asked/00-report.md)
|
|
||||||
wants a machine to say more about itself. With an executor on every node, "what is this
|
|
||||||
machine made of" is a seat verb like any other, served there.
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# 04 — The seats of the environment
|
|
||||||
|
|
||||||
*Candidates, not decisions. To-be 33 says which verbs a seat serves is a decision per seat,
|
|
||||||
taken slowly, because a seat's tools bind every future holder. This document lists the roles the
|
|
||||||
operator's machine has once, who could hold each, what gates it, and a first verb or two — so
|
|
||||||
each record has a starting point.*
|
|
||||||
|
|
||||||
## The rule for what is a seat here
|
|
||||||
|
|
||||||
A role the machine fills **at most once** is a node-scoped seat, declared by the module family
|
|
||||||
that fills it ([ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md)). A thing
|
|
||||||
several of which coexist without contention — editors, browsers, media players — is not a seat;
|
|
||||||
each is a module with its own tools, and nothing is singular about it. A seat is held by one
|
|
||||||
assignment per node; other modules of the same family may be installed beside it without
|
|
||||||
holding it ([ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md) §1, read with the sharper
|
|
||||||
distinction: *installed* is not *holding*).
|
|
||||||
|
|
||||||
## Candidate seats
|
|
||||||
|
|
||||||
| seat | holders | gated by | first verbs |
|
|
||||||
|---|---|---|---|
|
|
||||||
| **login shell** | zsh, fish, bash | nothing: universal | `execute(command)`; `show-config`; the holding itself sets the account's login shell through the host's `user` shape |
|
|
||||||
| **service manager** | systemd | the `service-manager` capability the profile reports | units: list, status, start, stop, restart, enable, journal; **user scope** on each |
|
|
||||||
| **boot** | grub, systemd-boot | a machine that boots itself (not a container host) | `rebuild-images`; `entries` |
|
|
||||||
| **package manager** | pacman, apt | the `package-manager` capability | search, installed, upgrade, orphans; today a capability the host uses, not a seat anyone holds |
|
|
||||||
| **display server** | xorg, wayland compositors that are their own server | the `graphical-session` capability | `displays`; `layout` |
|
|
||||||
| **display session** | i3, sway | the display server seat held on the node; i3 needs x11, sway needs wayland | `reload`; `workspaces`; `windows`; `move` |
|
|
||||||
| **terminal emulator** | xterm, alacritty, foot | display session | `open`; `font` |
|
|
||||||
| **launcher** | rofi, dmenu | display session | `show`; `theme` |
|
|
||||||
| **notifier** | dunst, mako | display session | `send`; `history`; `rule` |
|
|
||||||
| **compositor** | picom | display server (x11 only) | `restart`; `effects` |
|
|
||||||
| **lock screen** | i3lock, swaylock | display session | `lock` |
|
|
||||||
| **bar** | i3status-rust, waybar | display session | `reload`; `blocks` |
|
|
||||||
| **login manager** | lemurs, greetd | graphical session | `sessions`; `default-session` |
|
|
||||||
| **audio** | pipewire, pulseaudio | the machine reports a sound device | `sinks`, `sources`, `default`, `volume`, `mute` |
|
|
||||||
| **clipboard** | greenclip, cliphist | display session | `history`; `clear` |
|
|
||||||
|
|
||||||
Not seats, modules with their own tools: the editor, the browser, the mail client, the file
|
|
||||||
manager, the media player, the chat client, the agent at the terminal, the downloads folder, the
|
|
||||||
scripts folder, the sync client, the power and thermal daemons that are specific to one machine's
|
|
||||||
hardware.
|
|
||||||
|
|
||||||
## What the table implies
|
|
||||||
|
|
||||||
**Capabilities come first.** `graphical-session`, `service-manager` and `package-manager` are
|
|
||||||
reported today. *A display server is held* is not a capability but a seat being held, and a
|
|
||||||
module that needs it declares a dependency on the seat, not on a capability: *i3 needs the
|
|
||||||
display server seat held by xorg*. Whether a held seat can gate another's assignment is a
|
|
||||||
question for the controller's resolver, and the first environment module after the shell will
|
|
||||||
ask it.
|
|
||||||
|
|
||||||
**The service manager comes early.** Four of the predecessor's modules ship user units, and the
|
|
||||||
executor itself is a unit. User scope on the host's `service` shape is a host change whichever
|
|
||||||
module holds the seat; the seat's holder answers the questions about units, it does not apply
|
|
||||||
them — the host does, as it does for every declared resource.
|
|
||||||
|
|
||||||
**The shell comes first.** Universal, no capability, one verb that is immediately useful on
|
|
||||||
every node, and the `user` shape already makes the login shell declared state. It is the module
|
|
||||||
that proves the pattern: a package, files under the home owned by the account, a seat claim,
|
|
||||||
tools served by the executor, settings for the few things that vary per node, and a kept region
|
|
||||||
for the operator's own lines.
|
|
||||||
|
|
||||||
**The login manager is the first system-scope one**, because it needs nothing new: a package,
|
|
||||||
two files under `/etc`, a service — the same shape the ssh daemon module has today — and the
|
|
||||||
session script it owns is the file that was hand-fixed the day this effort opened.
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
status: active
|
|
||||||
initiated: 2026-10-02
|
|
||||||
touches: [lab, the lab module, the catalogue, assignments, settings, the controller's store]
|
|
||||||
became: []
|
|
||||||
---
|
|
||||||
|
|
||||||
# 019 — A warm twin of the running mesh
|
|
||||||
|
|
||||||
## What is being investigated
|
|
||||||
|
|
||||||
Whether the lab can keep a **warm twin of the mesh as it actually runs**: the same machines, carrying
|
|
||||||
the same catalogue, the same assignments and the same settings as the live mesh, raised once and kept
|
|
||||||
ready, so that a change can be tested against the mesh as it is rather than against a scenario
|
|
||||||
written to resemble it. A run against the twin would go through the lab module like any other run:
|
|
||||||
a branch per repository, the twin restored from its snapshot, the change applied, the beds run.
|
|
||||||
|
|
||||||
## Why
|
|
||||||
|
|
||||||
The lab's beds raise meshes from declarations written for the bed. They prove the mechanism. They
|
|
||||||
do not prove that a change works on the mesh that runs, with its accumulated assignments, its
|
|
||||||
operator settings, its adopted machines and its modules in their real combinations. The gap showed
|
|
||||||
on 2026-10-02:
|
|
||||||
|
|
||||||
- a change to how a module's settings reach its files was correct in every bed, and would have put a
|
|
||||||
setting into the container runtime's configuration on every machine running that module. Only the
|
|
||||||
composed plan for a real machine showed it;
|
|
||||||
- a firewall change composed cleanly and still left one machine's wired port unfiltered, because
|
|
||||||
of a link that machine had and no bed did;
|
|
||||||
- a recovery step was needed on every machine at once, after a change that every bed had passed.
|
|
||||||
|
|
||||||
The lab already has a warm mode, a snapshot of a raised scenario restored between attempts. What it
|
|
||||||
does not have is a scenario that **is** the running mesh, kept current with it.
|
|
||||||
|
|
||||||
## What it touches
|
|
||||||
|
|
||||||
- **What a twin is made of.** The catalogue and the assignments are records; settings are records;
|
|
||||||
secrets are sealed to machines and cannot be copied. Which of these can be carried to the lab as
|
|
||||||
they are, which must be substituted, and how a twin says what it substituted.
|
|
||||||
- **Data.** A twin with the real catalogue and no real data proves composition and delivery, not a
|
|
||||||
migration. Whether a twin carries data, a sample of it, or none.
|
|
||||||
- **Keeping it current.** A twin raised once goes stale with the first merge. Whether it is
|
|
||||||
re-derived from the live records on each run, refreshed on a schedule, or rebuilt only when asked.
|
|
||||||
- **Machines.** The live mesh has machines of different kinds: a server on the internet, machines
|
|
||||||
behind a home router, a laptop that sleeps. Which of their properties a twin must reproduce for a
|
|
||||||
test to mean anything (reachability, the private network, the found firewall).
|
|
||||||
- **Cost.** The lab machine's memory and disk, and how long a twin takes to raise from cold.
|
|
||||||
- **The lab module's tools.** A run against the twin rather than a named bed: one more tool, or an
|
|
||||||
argument to the run tool.
|
|
||||||
|
|
||||||
## Starting point
|
|
||||||
|
|
||||||
The lab module (ADR 0172) runs beds through the mesh, and the lab's warm mode already snapshots and
|
|
||||||
restores a raised scenario. The beds that raise a machine shaped like one live machine from the
|
|
||||||
catalogue are the nearest existing thing, and the first to compare against.
|
|
||||||
@@ -78,8 +78,6 @@ capability. The host hardcodes no firewall, supervisor, package manager or runti
|
|||||||
generic apply primitives and platform detection, so it runs where none of those exist — an Android
|
generic apply primitives and platform detection, so it runs where none of those exist — an Android
|
||||||
phone has no ufw, systemd, pacman or Docker.
|
phone has no ufw, systemd, pacman or Docker.
|
||||||
|
|
||||||
> **The mechanism changed — 2026-10-02, by [ADR 0176](0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md).** The shell example above — *bash, zsh and fish all join `shell`; one may be default* — is read as *installed is not holding*: the three may all be installed, and the `login-shell` seat is node-scoped and held by exactly one. The decision — what a module is, and the three relationships — stands; [ADR 0173](0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md) applies it to the operator's whole machine.
|
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- **Supersedes the earlier "grouped by domain" decision** (folded in consolidation; see the
|
- **Supersedes the earlier "grouped by domain" decision** (folded in consolidation; see the
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ extends: 0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md
|
|||||||
|
|
||||||
# 47. A module runs its code as its own process, with its own account
|
# 47. A module runs its code as its own process, with its own account
|
||||||
|
|
||||||
> **The mechanism changed — 2026-10-02, by [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md).** A module's *tools* are no longer served by the module's own process under its own account: one tool runtime per node, on the host side, serves every assigned module's bundle. A tool is still served on its own subject and only the module that serves it answers; what moved is the process and the account.
|
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
A module is one self-contained thing ([ADR 0040](0040-what-a-module-is.md)), and it gets a broker
|
A module is one self-contained thing ([ADR 0040](0040-what-a-module-is.md)), and it gets a broker
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ extends: 02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-ow
|
|||||||
|
|
||||||
# 150. A module's own code runs as supervised processes under the module's one account
|
# 150. A module's own code runs as supervised processes under the module's one account
|
||||||
|
|
||||||
> **The mechanism changed — 2026-10-02, by [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md).** For a module's *tools*, read that record: one runtime per node, the node's one account, bundles loaded from the memberships. This record still governs a module's long-lived processes — a daemon, a provisioner, a scheduled ingest — and the account invariant for them.
|
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
The repository answers "what runs a module's own code" two ways and reconciles them nowhere
|
The repository answers "what runs a module's own code" two ways and reconciles them nowhere
|
||||||
|
|||||||
@@ -111,8 +111,6 @@ operator owns; narrowing what it may call is a setting on its assignment, which
|
|||||||
[ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md) already provides for
|
[ADR 0046](0046-a-module-configuration-is-its-assignments-not-its-manifest.md) already provides for
|
||||||
and nothing here builds.
|
and nothing here builds.
|
||||||
|
|
||||||
> **The mechanism changed — 2026-10-02, by [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md).** The surface stays a module assigned per node, on loopback, with the machine's login as the authority. It is no longer a container: it is the node tools runtime's serving mode, host-side, and that runtime also serves every assigned module's tools. The module is renamed `node-tools`.
|
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- **The way a person drives the mesh is inside the mesh.** It is declared, delivered, replaced and
|
- **The way a person drives the mesh is inside the mesh.** It is declared, delivered, replaced and
|
||||||
|
|||||||
@@ -94,13 +94,6 @@ itself restarts, and the console says so rather than hiding the modules' tools w
|
|||||||
**A grant of `*` reaches a role's tools; `seat:<seat>.<verb>` grants one.** The console's `*` needed no
|
**A grant of `*` reaches a role's tools; `seat:<seat>.<verb>` grants one.** The console's `*` needed no
|
||||||
change to reach the mesh's verbs, which is what a grant meaning *every tool* should mean.
|
change to reach the mesh's verbs, which is what a grant meaning *every tool* should mean.
|
||||||
|
|
||||||
> **The mechanism changed — 2026-10-02, by [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md).**
|
|
||||||
> The seat gains a generic verb beside the named ones: `command`, which takes one command line as the
|
|
||||||
> controller's binary takes it and answers what it printed. The named verbs stand and keep their
|
|
||||||
> schemas; `command` is the whole binary, added because the operator decided any node may call any
|
|
||||||
> tool and a verb per command was the only thing keeping `node account`, `node show` and the rest
|
|
||||||
> behind a shell on the control node. Additive within the version, as §"additive" above allows.
|
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- **The console answers the mesh's own questions.** Issue 147's first paragraph closes: what a node
|
- **The console answers the mesh's own questions.** Issue 147's first paragraph closes: what a node
|
||||||
|
|||||||
+146
@@ -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`
|
||||||
+105
@@ -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)
|
||||||
+161
@@ -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)
|
||||||
@@ -113,22 +113,6 @@ That is a difference a take shows, not a fault, and is decided when it bites.
|
|||||||
| `node show` lists filters with owners; `status` names a converged machine something else filters and is not well; the preview lists filters and fates | controller tests over a fixture report |
|
| `node show` lists filters with owners; `status` names a converged machine something else filters and is not well; the preview lists filters and fates | controller tests over a fixture report |
|
||||||
| Live | the home server's record names the predecessor's chain in the runtime's user chain as *other*; `status` names the machine; after the operator removes the chain, the next report drops it and `status` is well |
|
| Live | the home server's record names the predecessor's chain in the runtime's user chain as *other*; `status` names the machine; after the operator removes the chain, the next report drops it and `status` is well |
|
||||||
|
|
||||||
## Built and proven live, 2026-10-02
|
|
||||||
|
|
||||||
> **Progressive insight — 2026-10-02.** The decision stands; these are the facts of its building.
|
|
||||||
|
|
||||||
Built in mesh-host 67 (every refusing table and legacy chain classified with an owner, reported with
|
|
||||||
every apply; the found firewall retired on every converged apply, *found inactive* kept apart from
|
|
||||||
*disabled by the mesh*, a skipped step said) and mesh-controller 211 (kept per node, shown on `node
|
|
||||||
show`, named by `status` and not well, previewed with fates). The live row was read at 10:10Z: the home
|
|
||||||
server's record named the predecessor's chain in the legacy filter's user chain as *other*, beside two
|
|
||||||
chains a retired front end left in the IPv6 legacy filter; the control node's record named the same two
|
|
||||||
leftovers; the laptop and the workstation read *the mesh alone*; `status` named both machines. The five
|
|
||||||
rule sets were removed at 12:46Z through the packet filter seat's `remove` verb
|
|
||||||
([ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)), and the next report read *the mesh alone* on
|
|
||||||
all four machines. The control node's record still says the mesh retired its front end, which issue 143
|
|
||||||
records as a hand's work: the host trusts its record, and from this build on the distinction is kept.
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md), [ADR 0103](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md), [ADR 0140](0140-the-filter-constrains-what-arrives-from-outside.md), [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md), [ADR 0163](0163-taking-a-module-over-is-a-comparison.md)
|
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md), [ADR 0103](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md), [ADR 0140](0140-the-filter-constrains-what-arrives-from-outside.md), [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md), [ADR 0163](0163-taking-a-module-over-is-a-comparison.md)
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
---
|
|
||||||
topic: the mesh
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0004-a-node-and-how-it-joins.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 169. A machine joins through the tunnel, and the bus is never public
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
The bus is the one channel every machine depends on: enrolment, every declaration, every tool. The
|
|
||||||
`nats` module declares it reachable from the mesh only. The controller still opens it to the whole
|
|
||||||
internet on the machine that runs it, as a *foundation* port that no module declares and nothing may
|
|
||||||
close ([issue 051](../04-ISSUES/051-the-mesh-cannot-update-what-it-depends-on/00-report.md)).
|
|
||||||
The reason is joining. [ADR 0004](0004-a-node-and-how-it-joins.md) has a new machine enrol over the bus
|
|
||||||
**before** it has a tunnel. [ADR 0007](0007-connectivity.md) states it as a requirement: the node
|
|
||||||
running the broker must be reachable from wherever nodes are, at a stable address.
|
|
||||||
|
|
||||||
So the bus listens on the internet permanently, for an event that happens a few times a year. A
|
|
||||||
sweep of every machine on 2026-10-02 found no client using the public path. Every connection arrives
|
|
||||||
over the tunnel or from the machine itself. The join token does not use it either: it carries the
|
|
||||||
controller's configured broker address, a mesh name with the old broker's port.
|
|
||||||
|
|
||||||
ADR 0004 already says what a joining machine needs: *an identity, an address, and one peer to reach*.
|
|
||||||
The tunnel can be that peer, if the hub knows the new machine's key before the machine first knocks.
|
|
||||||
WireGuard answers nothing to a key it does not know, which is why the tunnel's own port is safe to
|
|
||||||
leave open where the bus's is not.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
1. **Keep the bus public.** It is authenticated and encrypted, but every exposure of it, and of the
|
|
||||||
server behind it, is exposure of the one thing everything depends on.
|
|
||||||
2. **Open the bus publicly only while a join token is live.** Small, and the hub is open only during a
|
|
||||||
join window. But the window is real, the rule is about time rather than about who may reach the
|
|
||||||
bus, and the opening and closing are pushes that can fail between them.
|
|
||||||
3. **The controller makes the new machine's tunnel key and puts it in the token.** One step for the
|
|
||||||
operator, but the private half leaves a machine it does not belong to. ADR 0004 refuses that for
|
|
||||||
every key a node holds.
|
|
||||||
4. **The machine makes its key first, and the token is issued for it.** The machine prints the public
|
|
||||||
half of its tunnel key. The operator issues the token for that key. The controller gives the
|
|
||||||
machine its address and adds it as a peer on the hub. The token carries the hub's tunnel endpoint
|
|
||||||
and key, the machine's address, and the bus's address on the private network. The machine brings
|
|
||||||
up its tunnel and enrols over it.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**Option 4.**
|
|
||||||
|
|
||||||
- **A machine makes its own tunnel key before it has a token**, and prints the public half. The private
|
|
||||||
half never leaves it, as ADR 0004 says of every key a node holds.
|
|
||||||
- **A token is issued for a tunnel key.** Issuing it assigns the machine's address on the private
|
|
||||||
network, records the key, and makes the machine a peer of the hub. The hub is sent that before the
|
|
||||||
token is shown, so the tunnel answers the moment the machine first uses it.
|
|
||||||
- **The token carries the one peer.** It adds the hub's tunnel endpoint and public key and the
|
|
||||||
machine's own address. **Where** becomes the bus's address on the private network, which needs no
|
|
||||||
name resolution.
|
|
||||||
- **The machine joins through the tunnel.** It brings the tunnel up from the token alone, then enrols
|
|
||||||
over it exactly as before. The enrolment checks that the key it is offered is the one the token was
|
|
||||||
issued for.
|
|
||||||
- **The bus is never public.** It is no longer a foundation port. Its reach is what the `nats` module
|
|
||||||
declares: the mesh. The tunnel's port stays open, as the one way in.
|
|
||||||
|
|
||||||
This changes three things earlier records say. ADR 0004's *where* is the bus's private address, and the
|
|
||||||
token carries the peer. ADR 0007's requirement that the broker be reachable from wherever nodes are
|
|
||||||
becomes: **the hub's tunnel is**. Issue 051's broker port stops being a foundation port.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- Joining is two commands on the new machine, with the token issued between them. A token issued for
|
|
||||||
the wrong key gives a tunnel that never answers, and the machine says so rather than timing out at
|
|
||||||
the bus.
|
|
||||||
- An unused token leaves a peer on the hub until it expires. Expiry removes it, the same way it voids
|
|
||||||
the secret.
|
|
||||||
- A machine already in the mesh is unaffected: it reaches the bus over its tunnel today.
|
|
||||||
- The genesis machine, the first one, raises the bus on itself and needs no tunnel to reach it.
|
|
||||||
|
|
||||||
## How this is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| A token is refused without a tunnel key, and carries the hub's peer and the machine's address | a controller test |
|
|
||||||
| Issuing a token makes the machine a peer of the hub before the token is shown | a controller test over the hub's composed tunnel |
|
|
||||||
| An expired, unused token's peer is gone from the hub | a controller test |
|
|
||||||
| Enrolment refuses a tunnel key other than the one the token was issued for | a controller test |
|
|
||||||
| No machine's filter opens the bus to anywhere | a controller test over the composed filter, and the live sweep from outside the mesh |
|
|
||||||
| A new machine joins from outside the hub's network with the bus closed to it | the lab, then by hand |
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
---
|
|
||||||
topic: the mesh
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 170. The firewall seat serves its verbs, and a foreign rule set is removed through one of them
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
[ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md) made the mesh say truthfully
|
|
||||||
what filters a converged machine, and left the removal of what it did not write to the operator's
|
|
||||||
hand. The first time that hand was needed — two machines, five rule sets a predecessor and a
|
|
||||||
retired front end had left — there was no mesh way to lend it: the packet filter is a seat
|
|
||||||
([ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)), a seat's
|
|
||||||
holder serves its verbs ([ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md),
|
|
||||||
[ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)), and the
|
|
||||||
firewall seat declared none. The only remaining path was a shell on the machine, which is the path
|
|
||||||
the mesh exists to replace, and which the operator's own tooling rightly refused to an agent.
|
|
||||||
|
|
||||||
A seat's verbs are the contract every holder implements, whatever filter it speaks. What a person
|
|
||||||
asks a machine's packet filter is the same whether nftables, a front end or a legacy filter answers:
|
|
||||||
what are the rules, reload the mesh's own, remove this thing the mesh did not write. What differs by
|
|
||||||
filter is the holder's own business and may be its own tools beside the seat's.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**1. The `node-packet-filter` seat serves three verbs**, and a module that claims it serves all
|
|
||||||
three or is refused the claim, as with every seat:
|
|
||||||
|
|
||||||
- `rules` — the packet filter as the machine enforces it now: the nftables ruleset, and the legacy
|
|
||||||
filter's listings where that tool exists; narrowed to one table or chain when asked. Read-only.
|
|
||||||
- `reload` — load the mesh's own filter again from the file the mesh writes, and answer with the
|
|
||||||
mesh's table as loaded. The holder's own act on the holder's own rules.
|
|
||||||
- `remove` — remove one rule set the mesh did not write, named exactly as the host reports it under
|
|
||||||
ADR 0168 (`chain HAL-MESH-ONLY (iptables-legacy)`, `table ip6 filter, chain DOCKER-USER`), and
|
|
||||||
answer with what was done. It refuses the mesh's own tables, the container runtime's own chains,
|
|
||||||
a built-in chain other than the runtime's user chain, and any chain of a found firewall that is
|
|
||||||
in force. The runtime's user chain is emptied back to its one return; another chain loses the
|
|
||||||
jumps into it, is flushed and deleted; a table of the machine's own is deleted whole. Each is an
|
|
||||||
operator's act, by name, on one thing the mesh reported — never a flush, never a rule the mesh
|
|
||||||
itself marked.
|
|
||||||
|
|
||||||
**2. A holder may serve its own tools beside the seat's.** The nftables module keeps its reading of
|
|
||||||
the mesh's table as its own tool, and a holder speaking a filter with specifics of its own may add
|
|
||||||
tools for them; the seat's three are what every holder owes.
|
|
||||||
|
|
||||||
**3. A container may ask for a capability.** Serving `remove` and `reload` needs the machine's
|
|
||||||
network namespace and the right to change its packet filter; a holder's runtime declares
|
|
||||||
`capabilities: ["NET_ADMIN"]` on its container and runs on the machine's network. The host grants
|
|
||||||
exactly the capabilities declared, names them in the container's spec so a change recreates it, and
|
|
||||||
refuses a name that is not a capability's. A privileged container stays undeclarable.
|
|
||||||
|
|
||||||
**4. ADR 0168's "by hand" is read as "by the operator, through the seat".** Removing what the mesh
|
|
||||||
reports as *other* is still the operator's act and is still never the mesh's own doing; the verb is
|
|
||||||
how the act reaches the machine, recorded on the bus like every other, instead of a shell.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- The seat's row gains the three verbs; a mesh that already runs widens its row at the next
|
|
||||||
controller start. The nftables module claims them and gains a runtime — a tool server with the
|
|
||||||
packet filter's tools in its image, on the machine's network, with `NET_ADMIN`.
|
|
||||||
- The host's container vocabulary grows by `capabilities`; an older host refuses a declaration that
|
|
||||||
carries it, so the host rolls before the module.
|
|
||||||
- The two machines of this mesh that ADR 0168 found not filtered by the mesh alone are cleaned
|
|
||||||
through `remove`, and read *the mesh alone* afterwards; `status` returns to well without a hand on
|
|
||||||
either machine.
|
|
||||||
|
|
||||||
## How this is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| The seat declares the three verbs; a claim that serves fewer is refused by name | the catalogue's seat tests |
|
|
||||||
| `remove` refuses the mesh's tables, the runtime's chains, a built-in chain and an active front end's chains, and removes a user chain with its jumps, empties the user chain, deletes an own table | the module's tests over a fake command runner, with the shapes the host reported live |
|
|
||||||
| A container's capabilities reach the runtime and its spec; an unknown name is refused | host tests |
|
|
||||||
| Live | `node-packet-filter.remove@<node>` on the home server and the control node; `node show` reads *the mesh alone* on both; `status` is well |
|
|
||||||
|
|
||||||
## Built and proven live, 2026-10-02
|
|
||||||
|
|
||||||
> **Progressive insight — 2026-10-02.** The decision stands; these are the facts of its building.
|
|
||||||
> Written as 0169 for three hours and renumbered to 0170: another record took 0169 on main first,
|
|
||||||
> and the check that refuses a shared number covered issues only (now records too).
|
|
||||||
|
|
||||||
Built in mesh-host 68 (`capabilities` on a container), mesh-controller 212 (the seat's three verbs)
|
|
||||||
and 213 (the filter file a module names under `filtering.into` counts as declared for a mount — the
|
|
||||||
module's first build was refused without it), mesh-catalog 216 (the nftables module's runtime and
|
|
||||||
verbs) and mesh-tools 27 (the console lists a node-scoped seat's verbs with their scope and carries the
|
|
||||||
machine; before it, the verbs were live on four machines and unreachable from the console —
|
|
||||||
[issue 199](../04-ISSUES/199-a-node-scoped-seats-verb-could-not-be-called-through-the-console/00-report.md)).
|
|
||||||
Each machine's holder was issued its bus account with `mesh-controller.issue`, the broker node pushed
|
|
||||||
first. At 12:46Z the five rule sets ADR 0168 had named were removed through
|
|
||||||
`node-packet-filter.remove`, three on the home server and two on the control node, each answering
|
|
||||||
with the commands it ran; the next report read *the mesh alone* on all four machines and `status`
|
|
||||||
listed nothing under `filtered`. The live row is read. What it cost on the way is
|
|
||||||
[issue 200](../04-ISSUES/200-the-controllers-answer-to-the-console-is-refused-by-the-bus/00-report.md).
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), [ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md), [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)
|
|
||||||
- [Design 33 — The tools the mesh answers](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md), [Design 08 — Connectivity](../03-DESIGN/01-to-be/08-connectivity.md)
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
---
|
|
||||||
topic: the mesh
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0016-the-lab.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 172. The lab is a module, and runs a bed when the mesh asks
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
The lab raises virtual machines and runs the mesh on them, end to end, before a change reaches a real
|
|
||||||
machine ([ADR 0016](0016-the-lab.md)). It runs on one machine of the mesh, the one with the
|
|
||||||
virtualisation it needs. Until now the only way to start a bed there was to sign in to that machine and
|
|
||||||
run the lab's command line by hand, with a dozen environment variables pointing at sibling checkouts.
|
|
||||||
|
|
||||||
Nothing in the mesh could ask for it. An agent working through the mesh's own tools could build,
|
|
||||||
merge and push a change, and could not prove it in the lab first. The operator's direction on
|
|
||||||
2026-10-02: work on another machine goes through a mesh tool, not a shell on it.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
1. **Keep the lab a command line on one machine.** Every run is a person, or an agent with a shell on
|
|
||||||
that machine, outside the mesh.
|
|
||||||
2. **The lab is a module.** Assigned to the machine that can run it, serving tools that run a bed
|
|
||||||
against named branches and say how it went.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**Option 2.**
|
|
||||||
|
|
||||||
- **A `lab` module, assigned where the lab can run**, serves five tools: whether this machine can run
|
|
||||||
beds, run beds against a branch per repository, a run's state, its log, and stopping it.
|
|
||||||
- **A run is the lab's own suite**, against fresh checkouts of the named branches from the mesh's forge,
|
|
||||||
side by side as the lab expects them. It builds what the beds place from those checkouts, as the suite
|
|
||||||
already does. It answers at once with an id, like a build: a bed takes minutes, and a call does not.
|
|
||||||
- **Only branches on the forge are run**, never code handed to the tool. What a run tested is what the
|
|
||||||
forge holds at the commit it names.
|
|
||||||
- **The lab is reached over the mesh only.** Its tools travel the bus, and the module opens no port.
|
|
||||||
- **No grant beyond the mesh's own.** Running a bed is root on the lab's machine, but anyone who can call
|
|
||||||
the mesh's tools can already do worse. The operator's judgement on 2026-10-02.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- An agent proves a change in the lab through the mesh, the same way it builds and pushes one.
|
|
||||||
- The lab's machine carries a module whose runtime holds the virtualisation's and the container
|
|
||||||
runtime's sockets, and a toolchain to build the mesh with.
|
|
||||||
- A run's checkouts are its own, so two runs never build from each other's tree. Old ones are removed
|
|
||||||
when their run ends.
|
|
||||||
|
|
||||||
## How this is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| A run checks out exactly the named branches, and reports the commits it tested | the module's tests over a forge fixture, and each run's answer |
|
|
||||||
| A run answers at once, and its state and log follow it to the end | by hand, the first run |
|
|
||||||
| The module opens no port | the composed filter of the lab's machine |
|
|
||||||
-108
@@ -1,108 +0,0 @@
|
|||||||
---
|
|
||||||
topic: what runs on it
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0040-what-a-module-is.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 173. The operator's machine is the mesh's, and a module is whatever it declares
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
[ADR 0040](0040-what-a-module-is.md) says a module is *one self-contained piece of software the
|
|
||||||
mesh installs and manages*, and every example it gives is a service: a database, an analytics
|
|
||||||
server, a forge. The catalogue followed the examples. Of the predecessor's 34 modules on one
|
|
||||||
workstation, 28 are the operator's environment — a login manager, a window manager with 88 files
|
|
||||||
and four flavors, a shell, a terminal, a launcher, an audio setup, scripts — and the migration
|
|
||||||
scoped all 28 out as *the workstation's own environment*, to be managed by nobody
|
|
||||||
([research 018](../01-RESEARCH/018-the-operators-machine-as-modules/02-what-exists-and-what-is-missing.md)).
|
|
||||||
Since the predecessor retired, nobody is exactly who manages them: a fix is a hand edit that
|
|
||||||
nothing records and nothing regenerates.
|
|
||||||
|
|
||||||
[To-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) reached under the home for
|
|
||||||
one directory and drew a boundary inside it. The operator's statement is wider: *the mesh manages
|
|
||||||
my entire machine, all four of them, as far as it makes sense* — system folders and the home
|
|
||||||
alike, the servers and the workstations from the same catalogue. And the operator refused a
|
|
||||||
distinction this effort first drew between modules that ship code and modules that ship only
|
|
||||||
declarations: *a module can have some tools, a seat implementation, some containers, a unit, a
|
|
||||||
binary, some config files — one of these, or all, or two.*
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
1. **Keep 0040's reading and manage the environment outside the catalogue** — dotfiles in a
|
|
||||||
repository, a script that places them. Rejected: that is the predecessor's first two days, the
|
|
||||||
origin of every inherited shape [as-is 10](../03-DESIGN/00-as-is/10-module-catalogue.md)
|
|
||||||
documents, and it puts the one thing a person looks at outside the one mechanism that is
|
|
||||||
checked.
|
|
||||||
2. **Add a second kind of module for configuration** — a "config module" with files and no
|
|
||||||
process. Rejected by the operator: a kind is a distinction the manifest already makes by what
|
|
||||||
it declares, and a second kind is a second set of rules to keep in step.
|
|
||||||
3. **One definition: a module is one managed thing, described by what it declares.** Chosen.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**1. Everything configurable on a node is declared by a module.** Services, and equally the login
|
|
||||||
manager, the display server, the window manager, the shell, the terminal, the launcher, the
|
|
||||||
notifier, the audio setup, the boot images, the package manager's configuration, the agent at the
|
|
||||||
terminal, and a folder a person works in. The test is *can it be configured on a machine*; if it
|
|
||||||
can, some module owns it. What no module declares is found and left alone, as adoption already
|
|
||||||
says of a machine ([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md)).
|
|
||||||
|
|
||||||
**2. A module is whatever it declares, and there are no kinds of module.** A package, files, a
|
|
||||||
container, a unit, a binary, a seat claim, tools — any one, or all. 0040's *one self-contained piece
|
|
||||||
of software* stands; its examples were services, and that was the whole of the bias. A downloads
|
|
||||||
folder with a process that tidies it, backs it up and answers questions about it is a piece of
|
|
||||||
software by 0040's own test, and so is a shell that is a package, three files and a seat.
|
|
||||||
|
|
||||||
**3. The home has no boundary of its own.** A file under the operator's home is placed and owned
|
|
||||||
the way [to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) §2 built it: by a
|
|
||||||
module, resolved against the account's home, owned by the account. Which files are the mesh's is
|
|
||||||
decided by what modules declare, not by a line drawn through a directory. A person's documents,
|
|
||||||
projects and history are data under [ADR 0051](0051-shared-data-is-the-operators.md) and no module
|
|
||||||
declares them.
|
|
||||||
|
|
||||||
**4. One module ships one default configuration.** No flavors. What differed between the
|
|
||||||
predecessor's four flavors of one desktop module is what [ADR 0174](0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)
|
|
||||||
is for.
|
|
||||||
|
|
||||||
**5. Servers and workstations take the same catalogue.** A module declares what it needs; a
|
|
||||||
machine reports what it has; assignment refuses by name
|
|
||||||
([ADR 0161](0161-what-deserves-a-seat.md) §3). The shell, the prompt, git and the agent are universal.
|
|
||||||
A display server needs a graphical session; a window manager needs the display server held. Nothing
|
|
||||||
in a manifest says *workstation*.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- The catalogue grows by a family of modules that run no service. Each is still built,
|
|
||||||
registered, assigned, pushed and reported like every other, and `status` says whether a
|
|
||||||
machine has applied them.
|
|
||||||
- The account fact becomes load-bearing for every node a person uses. Today it is empty on all
|
|
||||||
four node records of this mesh; stating it is the first step of the build.
|
|
||||||
- A module that *installs* a thing is distinct from a module that *holds its role*: zsh, fish and
|
|
||||||
bash may all be installed, and one holds the login shell
|
|
||||||
([ADR 0176](0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md)).
|
|
||||||
- The host's `package` shape drives the distribution's package manager only. A module whose
|
|
||||||
package is outside the distribution's repositories — the login manager in use is one — needs
|
|
||||||
either an official package or a shape the host does not have. Recorded as a gap, not decided.
|
|
||||||
- The predecessor's hooks go. What they did becomes declared state the host applies, or a verb a
|
|
||||||
seat serves ([ADR 0177](0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md)).
|
|
||||||
|
|
||||||
## How it is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| A manifest with no container, no unit and no binary registers and resolves like any other | the catalogue's registration tests, with a package-and-files manifest |
|
|
||||||
| A file resource under the home resolves against the account and is owned by it | the controller's composition tests (to-be 29 §2, built) |
|
|
||||||
| A home-scoped module is refused on a node with no account, naming the fact | the same tests |
|
|
||||||
| A module needing a capability the machine lacks is refused by name | the resolver's tests (ADR 0161 §3) |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Research 018](../01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md), documents
|
|
||||||
01 and 02 — the behaviour wanted and the inventory measured.
|
|
||||||
- [ADR 0040](0040-what-a-module-is.md), [ADR 0112](0112-a-module-definition-names-no-node-mesh-or-path.md),
|
|
||||||
[ADR 0051](0051-shared-data-is-the-operators.md), [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md)
|
|
||||||
- [To-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) — the account and the home
|
|
||||||
as a placement root, built; the records for them are proposed in an open change.
|
|
||||||
-92
@@ -1,92 +0,0 @@
|
|||||||
---
|
|
||||||
topic: building it
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0011-managed-files-are-generated-never-edited.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 174. A node varies a module through settings and kept regions, never through an edit
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
[ADR 0011](0011-managed-files-are-generated-never-edited.md) says a managed file is derived and an
|
|
||||||
edit to it is overwritten without warning. The predecessor said the same and then undid it twice:
|
|
||||||
a `merge` strategy that adopted disk drift back into its database, so a local edit became the
|
|
||||||
record; and a theming layer of about 90 environment variables substituted into templates at sync
|
|
||||||
time, with tools to list and set them, so that *nearly every value was a variable* — a second
|
|
||||||
configuration language laid over the first.
|
|
||||||
|
|
||||||
The operator wants both the variation and the rule. One window-manager module with one default
|
|
||||||
configuration, and each node tweaking it; and the file carrying the wanted value rather than a
|
|
||||||
variable the file reads. Two mechanisms already exist for exactly this: a **setting**, declared by
|
|
||||||
the module and set per mesh or per node, rendered at composition
|
|
||||||
(`${setting:…}` is live in the resolver's manifest); and a **kept region**, a block in a file the
|
|
||||||
mesh writes *into* where the operator's own lines survive every push
|
|
||||||
([ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md), used by the ssh-client module
|
|
||||||
for the operator's own `Host` blocks).
|
|
||||||
|
|
||||||
What stands in the way is [issue 168](../04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md):
|
|
||||||
a setting today reaches every mergeable file and every contribution of its module. Ninety theme
|
|
||||||
knobs on that mechanism would reach ninety files. The record that fixes it — a setting declared
|
|
||||||
with its type, meaning, default and the file it lands in — is proposed in an open change alongside
|
|
||||||
the container-runtime records.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
1. **Carry the predecessor's merge strategy.** A local edit is adopted into the node's layer.
|
|
||||||
Rejected: two writers and no arbiter, which is the option 0011 removed, and the reason a
|
|
||||||
`/model` choice was silently reverted on every node for weeks before anyone found the cause.
|
|
||||||
2. **Carry the environment-variable theming.** Rejected by the operator: the value belongs in
|
|
||||||
the file; a variable the file reads is a second place for the same fact.
|
|
||||||
3. **A per-node file override** — a whole file replaced for one node. Rejected: it is a flavor
|
|
||||||
under another name, and a module update then misses that node entirely.
|
|
||||||
4. **Settings rendered into the file, and kept regions, and nothing else.** Chosen.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**A node varies a module in exactly two ways.**
|
|
||||||
|
|
||||||
- **A setting.** Declared by the module with a default, set for the mesh or for one node, rendered
|
|
||||||
into the file at composition. The value is in the file. Asked, the mesh lists every setting
|
|
||||||
with its effective value and where it came from.
|
|
||||||
- **A kept region.** A marked block in a file the mesh writes into, in which the operator's own
|
|
||||||
lines are kept across every push and given back when the module goes (ADR 0102).
|
|
||||||
|
|
||||||
**An edit outside a kept region is overwritten, as ADR 0011 says, and never adopted.** Nothing
|
|
||||||
reads a managed file back into the record.
|
|
||||||
|
|
||||||
**The predecessor's theme knobs become settings** of the modules whose files they render — the
|
|
||||||
window manager's colours are the window manager's settings, the bar's are the bar's — each
|
|
||||||
landing in the file that reads it and no other.
|
|
||||||
|
|
||||||
**Issue 168 is fixed before any environment module declares a setting.** A setting must name the
|
|
||||||
file it lands in; until that ships, the environment modules carry their defaults in their files
|
|
||||||
and no settings.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- No flavors, no per-node file copies, no environment layer. A module's definition is one set of
|
|
||||||
files; a node's difference is data in its layer, visible by asking.
|
|
||||||
- The settings record proposed alongside the container-runtime records is on the critical path
|
|
||||||
of every module with a knob, and this record depends on it shipping as proposed.
|
|
||||||
- A kept region is the only place a person edits a managed file, and the file says where it is.
|
|
||||||
The operator's own prompt customisations, aliases and window rules live there.
|
|
||||||
- What got harder: a change that is neither a setting the module declared nor the operator's own
|
|
||||||
lines has no home, and is refused by the mechanism rather than silently kept. That is the point.
|
|
||||||
|
|
||||||
## How it is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| A setting reaches only the file its declaration names | the controller's settings tests, once the proposed record ships; issue 168 closes on it |
|
|
||||||
| A kept region survives a push with its content and is given back on undeclare | the host's write-into tests (ADR 0102), with a region declared by an environment module |
|
|
||||||
| An edit outside a region does not survive a push | the same tests, asserting the file equals the composed content outside the region |
|
|
||||||
| Every effective value names its source | `mesh-controller.settings` and the module's own `show-config` tool |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Research 018](../01-RESEARCH/018-the-operators-machine-as-modules/01-the-intended-behaviour.md) §"One default, varied by settings, never by edits"
|
|
||||||
- [ADR 0011](0011-managed-files-are-generated-never-edited.md), [ADR 0102](0102-the-mesh-writes-into-a-shared-file-never-over-it.md),
|
|
||||||
[issue 168](../04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md)
|
|
||||||
-122
@@ -1,122 +0,0 @@
|
|||||||
---
|
|
||||||
topic: what runs on it
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 175. One tool runtime per node serves every module's tools, on the host side
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
A module's tools are code the module wrote, one function behind each verb, served on the subjects
|
|
||||||
the controller issues in the module's membership
|
|
||||||
([ADR 0160](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)).
|
|
||||||
What *runs* that code is [ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md):
|
|
||||||
a supervised process per module under the module's own account, and in the catalogue as built,
|
|
||||||
that process is a container per module per node, built on the tool runtime's base image.
|
|
||||||
|
|
||||||
Measured on the live mesh ([research 018](../01-RESEARCH/018-the-operators-machine-as-modules/03-one-tool-executor-per-node.md)):
|
|
||||||
67 module tools, each served from its module's container; the packet-filter seat's three verbs
|
|
||||||
served by a container with `NET_ADMIN` on every one of four machines, for a module that is
|
|
||||||
otherwise a package, three files and a service; and the console, a container per node, calling
|
|
||||||
everything and serving nothing. The operator's environment adds a dozen modules of the
|
|
||||||
packet-filter shape, and the operator's judgement is plain: *I would never run MCP tools inside
|
|
||||||
a container; that is a very bad design.* And: *I don't care about permissions or account per
|
|
||||||
module, that just complicates things for no good reason. Just a node-level tool executor. If a
|
|
||||||
command needs root, that's the module's concern.*
|
|
||||||
|
|
||||||
The tool runtime itself was written for this. Its own description: *the per-node process that
|
|
||||||
makes a module's tools actually serve — imports the assigned modules' compiled tool entrypoints,
|
|
||||||
each of which registers its tools as it loads; on a node the host resolves the list and starts it
|
|
||||||
like any other supervised workload.* What the catalogue did instead was build one image per module
|
|
||||||
around it.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
1. **Keep a process per module.** Rejected: one container per module per node for software that
|
|
||||||
is not a container, and the account-per-module invariant it exists to protect is one the
|
|
||||||
operator declines to pay for.
|
|
||||||
2. **The host executes tools itself.** Rejected: the host is a static Go binary that loads no
|
|
||||||
plugins; a module's tools are TypeScript on the SDK, and building a second SDK in Go for the
|
|
||||||
host's sake is the cost ADR 0039 refuses.
|
|
||||||
3. **One tool runtime per node, a sibling of the host, loading every assigned module's bundle.**
|
|
||||||
Chosen. It is what the runtime was written to be.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**1. One tool runtime per node, supervised by the host, on the host side — never a container.**
|
|
||||||
The host starts it the way the launcher starts the host
|
|
||||||
([ADR 0005](0005-the-node-host.md)): a process on the machine, restarted when it dies. It holds one
|
|
||||||
bus credential, the node's. It is module-agnostic: it knows bundles and subjects, nothing of what
|
|
||||||
any module does.
|
|
||||||
|
|
||||||
**2. It serves every assigned module's tools and every held seat's verbs** on the subjects the
|
|
||||||
memberships issue. ADR 0159 and ADR 0160 are unchanged in what they say about subjects, grants
|
|
||||||
and memberships; what changes is that one process on the node subscribes to all of them instead of
|
|
||||||
one process per module. A module that runs a long-lived service of its own — a daemon, a
|
|
||||||
container — keeps it; this record is about tools.
|
|
||||||
|
|
||||||
**3. A module brings its tools as a bundle**, the artifact kind the catalogue already has for
|
|
||||||
interpreted code, built by the pipeline and delivered to the node by the host as it delivers any
|
|
||||||
artifact. Never an image. The runtime loads each bundle as the membership names it, and a push
|
|
||||||
that adds or replaces a bundle reaches a running runtime as a reload.
|
|
||||||
|
|
||||||
**4. Root is the module's concern.** A tool that must change the packet filter or rebuild boot
|
|
||||||
images escalates itself. The runtime does not run as root for everyone's sake; the caller does not
|
|
||||||
know and need not.
|
|
||||||
|
|
||||||
**5. Any node may call any tool on any node.** The runtime's credential may call everything, as
|
|
||||||
the console's already may. A per-module calling grant is not kept.
|
|
||||||
|
|
||||||
**6. The console is this runtime's serving mode, renamed.** [ADR 0152](0152-the-operators-surface-is-a-module-the-console.md)
|
|
||||||
stands in substance — a module assigned per node, MCP on the machine's loopback, the machine's
|
|
||||||
login is the authority — and changes in form: host-side, serving as well as calling, and named for
|
|
||||||
what it is: **node tools**. The mesh's own verbs stay with the controller
|
|
||||||
([ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)); a mesh-scoped seat's verbs
|
|
||||||
run on the node that holds it ([ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)).
|
|
||||||
|
|
||||||
**Where ADR 0047 and ADR 0150 say a module's tools are served by the module's own process under
|
|
||||||
the module's own account, read this record.** Everything else they decided stands: a tool is served
|
|
||||||
on its own subject, only the module that serves it answers, a module's long-lived processes are the
|
|
||||||
machine's to supervise. The invariant 0150 kept — one account per module — no longer holds for
|
|
||||||
tools, and the reason is stated above: every tool is callable from everywhere by decision 5, so the
|
|
||||||
account no longer scopes anything a caller cannot already reach.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- The packet-filter module's container goes; its verbs run on the host side and escalate as they
|
|
||||||
need. [ADR 0170](0170-the-firewall-seat-serves-its-verbs.md) §3's container capability is moot
|
|
||||||
for it.
|
|
||||||
- The tool runtime's base image stays the way a module's *service* may be built; it is no longer
|
|
||||||
the way tools reach a node.
|
|
||||||
- The node tools runtime needs an interpreter on the machine. The module that is the runtime
|
|
||||||
declares it as a package.
|
|
||||||
- The container-runtime seat proposed in an open change says its holder *runs as a supervised
|
|
||||||
process and serves the verbs locally to the host and on the bus*. A supervised process serving
|
|
||||||
verbs is what this runtime is; whether that holder keeps a process of its own or serves through
|
|
||||||
the runtime is for that record's build to say.
|
|
||||||
- What got harder: one process carries every module's tool code on a node, so one module's
|
|
||||||
faulty bundle can take down the node's tools. The runtime loads each bundle guarded and names
|
|
||||||
the one that failed; the others serve.
|
|
||||||
|
|
||||||
## How it is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| The runtime loads every bundle its memberships name and serves each tool on its subject | the runtime's tests against a real bus: two bundles, three tools, each answers |
|
|
||||||
| A bundle that fails to load is named and the others serve | the same tests, with one bundle that throws on load |
|
|
||||||
| The host supervises the runtime and restarts it | the host's tests over the launcher's shape |
|
|
||||||
| A push that replaces a bundle reloads it without a restart | the runtime's tests: a bundle replaced on disk, the membership re-read, the new tool answers |
|
|
||||||
| No module in the catalogue declares a container whose only purpose is tools | a catalogue check: a manifest with `tools` and an image artifact built on the tool runtime's base is refused once the runtime is live |
|
|
||||||
| Live | `login-shell.execute@<node>` answers on every node from the node tools runtime; `docker ps` shows no per-module tool container |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Research 018](../01-RESEARCH/018-the-operators-machine-as-modules/03-one-tool-executor-per-node.md)
|
|
||||||
- [ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md), [ADR 0047](0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md),
|
|
||||||
[ADR 0152](0152-the-operators-surface-is-a-module-the-console.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 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)
|
|
||||||
- [To-be 33](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md), [to-be 34](../03-DESIGN/01-to-be/34-the-console.md)
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
---
|
|
||||||
topic: what runs on it
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 176. The login shell is a node seat held by one shell module, and `execute` is its contract
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
[ADR 0040](0040-what-a-module-is.md) names the shell as its example of a *shared* seat: bash, zsh
|
|
||||||
and fish all join `shell`, and one may be default. The operator's reading is sharper, and it
|
|
||||||
matches [ADR 0126](0126-a-module-declares-its-own-seats.md) better: *installing* a shell is
|
|
||||||
installing software, and several may be installed; *holding* the seat is being the login shell,
|
|
||||||
which a node has exactly one of. A definition says which seats a module can hold; the assignment
|
|
||||||
says which it does.
|
|
||||||
|
|
||||||
A seat carries the tools its holder must serve ([ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md)),
|
|
||||||
and [to-be 33](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) leaves which verbs each
|
|
||||||
seat serves as a decision per seat, taken slowly. This is the first seat of the operator's
|
|
||||||
environment, and the one every node has.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
1. **A shared `shell` seat with a default**, as 0040's example reads. Rejected: *default* is a
|
|
||||||
second concept beside *holder* for the same fact, and the `user` shape already makes the
|
|
||||||
login shell declared state ([to-be 05](../03-DESIGN/01-to-be/05-the-node-host.md)).
|
|
||||||
2. **No seat; each shell module sets the login shell for itself.** Rejected: two assigned shell
|
|
||||||
modules would fight over `chsh`, and nothing would say which won.
|
|
||||||
3. **An exclusive node-scoped seat, `login-shell`, declared by the shell modules, held by one
|
|
||||||
per node.** Chosen.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**1. `login-shell` is a node-scoped seat declared by the shell modules.** zsh, fish and bash each
|
|
||||||
declare that they can hold it; a node's assignment says which does; the controller refuses a
|
|
||||||
second holder by name as for every seat. A shell module that is assigned without holding the seat
|
|
||||||
is installed and nothing more.
|
|
||||||
|
|
||||||
**2. Holding the seat sets the account's login shell.** The holder's declaration carries the
|
|
||||||
`user` shape with the shell it provides, so the login shell is declared state the host applies and
|
|
||||||
gives back when the holding moves — `chsh` stops being a hook.
|
|
||||||
|
|
||||||
**3. The seat's contract is `execute`.** One verb, one argument, the command, run on the node the
|
|
||||||
seat is scoped to as the operator account, answering with what it printed and how it exited.
|
|
||||||
Every holder serves it; a holder may serve its own tools beside it
|
|
||||||
([ADR 0170](0170-the-firewall-seat-serves-its-verbs.md) §2) — show the rendered configuration, list
|
|
||||||
the plugins, set a prompt value.
|
|
||||||
|
|
||||||
**4. Any node may call it on any node.** The grant is the node tools runtime's
|
|
||||||
([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md) §5):
|
|
||||||
*run `uptime` on every node* is five calls to one verb.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- The first environment module is a shell: a package, files under the home owned by the account,
|
|
||||||
a seat declaration and claim, a `user` shape, and one tool. It proves the whole pattern on every
|
|
||||||
node, servers included, before anything graphical is written.
|
|
||||||
- ADR 0040's shell example is read as *installed is not holding*; a dated note in that record says
|
|
||||||
so. Its decision is untouched.
|
|
||||||
- `execute` is a shell on every machine, addressed over the bus. That is the point, and it is
|
|
||||||
the widest verb the mesh serves; it exists because the operator decided every node may call
|
|
||||||
every tool, and this record does not narrow that.
|
|
||||||
|
|
||||||
## How it is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| Two shell modules assigned to one node, one holding: one `user` shape in the declaration, naming the holder's shell | the controller's composition tests |
|
|
||||||
| A second claimant is refused by name | the catalogue's seat tests |
|
|
||||||
| `execute` runs as the account and answers output and exit status | the module's tool tests over a fake runner, and live on every node |
|
|
||||||
| The seat's verb appears with its scope and machine in the node tools listing | the runtime's tests (to-be 33 §4) |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Research 018](../01-RESEARCH/018-the-operators-machine-as-modules/04-the-seats-of-the-environment.md)
|
|
||||||
- [ADR 0040](0040-what-a-module-is.md), [ADR 0126](0126-a-module-declares-its-own-seats.md),
|
|
||||||
[ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md), [ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
---
|
|
||||||
topic: what runs on it
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 177. A unit may be user-scoped, and the service manager is a node seat whose holder answers for the units
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
The host's `service` shape puts a system unit into a state. It has no user scope.
|
|
||||||
[To-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) states the gap: *a
|
|
||||||
workstation's per-user daemons have no form the mesh can send.* Four of the predecessor's
|
|
||||||
environment modules ship user units — the desktop's reload watcher and bar watchdog, the audio
|
|
||||||
module's masks, the power module's memory guard, the thermal daemon's profile switcher — and the
|
|
||||||
predecessor needed a hook to enable them because *shipping a unit file does not run it*; one unit
|
|
||||||
was deployed for months and ran on one machine only.
|
|
||||||
|
|
||||||
[ADR 0040](0040-what-a-module-is.md) says the host hardcodes no supervisor, and a swappable
|
|
||||||
machine mechanism is a module implementing a capability — which is what the nftables module is for
|
|
||||||
the packet filter ([ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)). The service manager is
|
|
||||||
reported today as a capability, `service-manager`, and held by nobody. The operator's proposal: a
|
|
||||||
systemd module that holds the seat and serves the tools about units, system and user.
|
|
||||||
|
|
||||||
## Considered Options
|
|
||||||
|
|
||||||
1. **Keep user units as a module concern** — each module runs `systemctl --user` in a hook.
|
|
||||||
Rejected: that is the hook that silently never ran, and an action over the link is refused.
|
|
||||||
2. **The service-manager module applies units** on behalf of others, as a provision. Rejected by
|
|
||||||
the operator: provisioning is for resources a provider creates for a consumer; a unit is
|
|
||||||
declared state the host applies, as every resource is.
|
|
||||||
3. **The host's `service` shape gains a user scope; a systemd module holds the service-manager
|
|
||||||
seat and serves the verbs about units.** Chosen.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**1. The `service` shape gains `scope`: `system` (the default) or `user`.** A user-scoped unit
|
|
||||||
is applied as the operator account through the account's own service manager: enabled, started,
|
|
||||||
stopped, reloaded on its triggers, exactly as a system unit is, and refused on a node with no
|
|
||||||
account, naming the fact. The host applies it; no module does.
|
|
||||||
|
|
||||||
**2. `node-service-manager` is a seat of the mesh's own, node-scoped**, seeded by the controller
|
|
||||||
under this record, as ADR 0121 requires of a `node-*` name. The `systemd` module claims it and is
|
|
||||||
assigned to every machine whose profile reports `service-manager`.
|
|
||||||
|
|
||||||
**3. The seat's verbs answer for every unit on the machine**, each taking an optional `scope`:
|
|
||||||
`units`, `status`, `start`, `stop`, `restart`, `enable`, `disable`, `journal`. The host applies what
|
|
||||||
is declared; the holder answers questions and operator acts about it, and says, for a mesh-held
|
|
||||||
unit, that the host will restore what its declaration says.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- The host's vocabulary grows by one field on one shape, asserted by its count test
|
|
||||||
([to-be 05](../03-DESIGN/01-to-be/05-the-node-host.md)); an older host refuses a declaration
|
|
||||||
carrying it, so the host rolls before the first module that uses it.
|
|
||||||
- The predecessor's four user-unit modules become declarable without a hook.
|
|
||||||
- The seat's holder is the first system seat held by a module that runs nothing of its own: its
|
|
||||||
verbs are served by the node tools runtime ([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)).
|
|
||||||
- What got harder: `journal` and `status` on a user unit need the account's manager reachable
|
|
||||||
from the runtime's process, which runs as the node's account; the holder's tool escalates or
|
|
||||||
switches user as it needs, which is ADR 0175 §4 applied.
|
|
||||||
|
|
||||||
## How it is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| A `service` with `scope: user` is enabled and started under the account, and refused with no account | the host's tests with a fake service manager |
|
|
||||||
| The seat declares its verbs; a claim serving fewer is refused by name | the catalogue's seat tests |
|
|
||||||
| The verbs act on a named unit in the named scope and name the unit's holder when the mesh declares it | the module's tests over a fake runner |
|
|
||||||
| Live | the desktop's reload watcher declared `scope: user` on a workstation; `node-service-manager.status@<node>` reports it active |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Research 018](../01-RESEARCH/018-the-operators-machine-as-modules/04-the-seats-of-the-environment.md)
|
|
||||||
- [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md), [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md),
|
|
||||||
[ADR 0040](0040-what-a-module-is.md), [ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)
|
|
||||||
- [To-be 05](../03-DESIGN/01-to-be/05-the-node-host.md), [to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md)
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
---
|
|
||||||
topic: the mesh
|
|
||||||
status: accepted
|
|
||||||
date: 2026-10-02
|
|
||||||
deciders: jochen
|
|
||||||
reconstructed: false
|
|
||||||
extends: 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 180. The found front end is uninstalled once a machine is converged
|
|
||||||
|
|
||||||
> **Renumbered 2026-10-02.** Written and merged as 0175 while another record already held that number on main (one tool runtime per node, merged minutes earlier); `cycle.py` refused main. The branch that lands last renumbers: 0178 and 0179 are claimed by open changes, so this is 0180. Nothing cited it by number.
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
[ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md) retires the firewall a machine
|
|
||||||
was found with by disabling it, never flushing it, and keeps its configuration on disk so that
|
|
||||||
returning the node to adopted can enable it again. [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)
|
|
||||||
made the host keep it retired and say so. Both machines of this mesh that had a front end have been
|
|
||||||
converged for days; neither is going back. What remained of the front end on each — its package,
|
|
||||||
its unit enabled for boot on one, its empty chains still wired into the kernel's hooks, a chain of
|
|
||||||
its container integration still dropping traffic on the IPv6 path until the day before this record
|
|
||||||
— was not a rollback path. It was software nobody runs, left where a reader finds it and asks
|
|
||||||
whether the machine has two firewalls.
|
|
||||||
|
|
||||||
The operator asked on 2026-10-02 that it be disabled and uninstalled. Disabled it already was. For
|
|
||||||
uninstalled, the host had no word: a package could be declared present and not absent.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
**1. A package may be declared absent.** `absent: true` on a package resource has the host remove
|
|
||||||
the package when it is installed and leave alone a machine that never had it, through the machine's
|
|
||||||
own package manager, dependencies untouched. A declaration that stops saying a package is absent
|
|
||||||
installs nothing: there is nothing to undo.
|
|
||||||
|
|
||||||
**2. The module that holds the packet filter seat declares the front end it replaced absent**, after
|
|
||||||
its own filter is loaded, so the mesh's table is in force before the front end's package goes. On a
|
|
||||||
converged machine the front end is therefore gone, not merely off; on an adopted machine nothing of
|
|
||||||
this runs, because the filter module is assigned by the flip and not before.
|
|
||||||
|
|
||||||
**3. Returning such a machine to adopted enables nothing.** A machine with no firewall needs no
|
|
||||||
openings ([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md)); the host records the
|
|
||||||
front end as *removed*, says so once, and asks nothing of a command that is not there. What
|
|
||||||
ADR 0100 kept on disk for a return is kept only as far as the package manager keeps a changed
|
|
||||||
configuration file; the rollback path it described is given up on purpose.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
- The host's vocabulary grows by `absent` on a package; an older host refuses a declaration
|
|
||||||
carrying it, so the host rolls before the module.
|
|
||||||
- The nftables module's declaration gains one resource; on the two machines of this mesh that were
|
|
||||||
found with ufw, the next push removes it.
|
|
||||||
- `node show` reads *found firewall: ufw, removed* on those machines from then on.
|
|
||||||
- ADR 0100's sentence about a return to adopted restoring the found firewall holds only while the
|
|
||||||
front end is installed, which after this record it is not on a converged machine.
|
|
||||||
|
|
||||||
## How this is checked
|
|
||||||
|
|
||||||
| Rule | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| An absent package is removed when present, left when not, and read back | host tests over a fake package manager |
|
|
||||||
| An uninstalled front end is recorded as removed and nothing is asked of it | a host test with ufw missing on a converged apply |
|
|
||||||
| Live | the two machines report ufw gone: `pacman -Q ufw` has no answer, `node show` says removed, `status` is well |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md), [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), [ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)
|
|
||||||
- [Design 08 — Connectivity](../03-DESIGN/01-to-be/08-connectivity.md)
|
|
||||||
@@ -179,10 +179,6 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0163** — [Taking a module over is a comparison: what it compares, what it refuses, and what it carries](0163-taking-a-module-over-is-a-comparison.md)
|
- **0163** — [Taking a module over is a comparison: what it compares, what it refuses, and what it carries](0163-taking-a-module-over-is-a-comparison.md)
|
||||||
- **0167** — [A membership carries what its module receives, and who the mesh is](0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)
|
- **0167** — [A membership carries what its module receives, and who the mesh is](0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md)
|
||||||
- **0168** — [A converged machine is filtered by the mesh alone, and the host says what else refuses](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)
|
- **0168** — [A converged machine is filtered by the mesh alone, and the host says what else refuses](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md)
|
||||||
- **0169** — [A machine joins through the tunnel, and the bus is never public](0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md)
|
|
||||||
- **0170** — [The firewall seat serves its verbs, and a foreign rule set is removed through one of them](0170-the-firewall-seat-serves-its-verbs.md)
|
|
||||||
- **0172** — [The lab is a module, and runs a bed when the mesh asks](0172-the-lab-is-a-module-and-runs-a-bed-when-the-mesh-asks.md)
|
|
||||||
- **0180** — [The found front end is uninstalled once a machine is converged](0180-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md)
|
|
||||||
|
|
||||||
### Its tiers, from the bottom up
|
### Its tiers, from the bottom up
|
||||||
|
|
||||||
@@ -273,10 +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)
|
- **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)
|
- **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)
|
- **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)
|
||||||
- **0173** — [The operator's machine is the mesh's, and a module is whatever it declares](0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.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)*
|
||||||
- **0175** — [One tool runtime per node serves every module's tools, on the host side](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)
|
- **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)*
|
||||||
- **0176** — [The login shell is a node seat held by one shell module, and `execute` is its contract](0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md)
|
- **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)*
|
||||||
- **0177** — [A unit may be user-scoped, and the service manager is a node seat whose holder answers for the units](0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md)
|
|
||||||
|
|
||||||
### How it is built
|
### How it is built
|
||||||
|
|
||||||
@@ -298,7 +293,6 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0107** — [Persistent data is a directory bind, never a named volume](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md)
|
- **0107** — [Persistent data is a directory bind, never a named volume](0107-persistent-data-is-a-directory-bind-never-a-named-volume.md)
|
||||||
- **0111** — [A build source is on the mesh's git seat, or it is an external repository](0111-a-build-source-is-on-the-git-seat-or-external.md)
|
- **0111** — [A build source is on the mesh's git seat, or it is an external repository](0111-a-build-source-is-on-the-git-seat-or-external.md)
|
||||||
- **0149** — [The live mesh is the test bed](0149-the-live-mesh-is-the-test-bed.md)
|
- **0149** — [The live mesh is the test bed](0149-the-live-mesh-is-the-test-bed.md)
|
||||||
- **0174** — [A node varies a module through settings and kept regions, never through an edit](0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)
|
|
||||||
|
|
||||||
### How it is checked
|
### How it is checked
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
---
|
---
|
||||||
layer: to-be
|
layer: to-be
|
||||||
status: in-progress
|
status: in-progress
|
||||||
code: [mesh-lab, mesh-catalog modules/lab]
|
code: [mesh-lab]
|
||||||
updated: 2026-10-02
|
updated: 2026-09-11
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0172-the-lab-is-a-module-and-runs-a-bed-when-the-mesh-asks.md
|
|
||||||
- 02-DECISIONS/0016-the-lab.md
|
- 02-DECISIONS/0016-the-lab.md
|
||||||
- 02-DECISIONS/0010-delivery.md
|
- 02-DECISIONS/0010-delivery.md
|
||||||
---
|
---
|
||||||
@@ -120,25 +119,7 @@ In order, on a machine with nothing:
|
|||||||
|
|
||||||
6. **Verification**, as above, before anything is raised.
|
6. **Verification**, as above, before anything is raised.
|
||||||
|
|
||||||
## The lab answers the mesh
|
## Open
|
||||||
|
|
||||||
*2026-10-02* ([ADR 0172](../../02-DECISIONS/0172-the-lab-is-a-module-and-runs-a-bed-when-the-mesh-asks.md)).
|
|
||||||
Once installed, the lab is also a module: `lab`, assigned to the machine that passed `check`. Its
|
|
||||||
tools run there and nowhere else:
|
|
||||||
|
|
||||||
| tool | does |
|
|
||||||
|---|---|
|
|
||||||
| `lab_check` | the lab's `check`, on this machine |
|
|
||||||
| `lab_run` | fresh checkouts of the named branches from the forge, side by side, then the suite on the named beds; answers with an id |
|
|
||||||
| `lab_status` | where a run is, and how it ended: the commits it tested, passed and failed |
|
|
||||||
| `lab_log` | the run's output so far |
|
|
||||||
| `lab_stop` | ends a run |
|
|
||||||
|
|
||||||
The runtime is a container holding the toolchain the suite builds with. It reaches the
|
|
||||||
virtualisation daemon and the container runtime through their sockets on the machine, so what it
|
|
||||||
raises is what a hand run raises. The prerequisites above stay installed by hand. The module uses
|
|
||||||
them, and never installs them.
|
|
||||||
|
|
||||||
|
|
||||||
- **Whether the lab's bootstrap may install packages at all**, given that the mesh's rules
|
- **Whether the lab's bootstrap may install packages at all**, given that the mesh's rules
|
||||||
forbid installing by hand. The resolution is probably that the lab's bootstrap *is* the
|
forbid installing by hand. The resolution is probably that the lab's bootstrap *is* the
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ code:
|
|||||||
- mesh-host internal/apply (the service that reflects a rule set)
|
- mesh-host internal/apply (the service that reflects a rule set)
|
||||||
updated: 2026-10-02
|
updated: 2026-10-02
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0180-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md
|
|
||||||
- 02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md
|
|
||||||
- 02-DECISIONS/0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md
|
|
||||||
- 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
- 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
||||||
- 02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md
|
- 02-DECISIONS/0167-a-membership-carries-what-its-module-receives-and-who-the-mesh-is.md
|
||||||
- 02-DECISIONS/0148-the-meshs-names-are-resolved-not-copied-into-containers.md
|
- 02-DECISIONS/0148-the-meshs-names-are-resolved-not-copied-into-containers.md
|
||||||
@@ -176,28 +173,6 @@ the broker's node must be dialable by every node, at a stable address, and so mu
|
|||||||
reachable; on one network it does not. A mesh whose nodes are all behind NAT cannot be raised, and
|
reachable; on one network it does not. A mesh whose nodes are all behind NAT cannot be raised, and
|
||||||
a broker node whose address moves invalidates every token issued for it.
|
a broker node whose address moves invalidates every token issued for it.
|
||||||
|
|
||||||
*2026-10-02.* **The order changes at step 1: the tunnel comes first, from the token**
|
|
||||||
([ADR 0169](../../02-DECISIONS/0169-a-machine-joins-through-the-tunnel-and-the-bus-is-never-public.md)).
|
|
||||||
The circularity above is real, and it is broken differently. The overlay is configured by the mesh,
|
|
||||||
except for the one peer a joining machine needs, and the token carries that peer. So the sequence
|
|
||||||
becomes:
|
|
||||||
|
|
||||||
```
|
|
||||||
0 the node has an underlay address the machine's own
|
|
||||||
1 the node makes its tunnel key before any token; it prints the public half
|
|
||||||
2 a token is issued for that key its address assigned, and the hub sent it as a peer
|
|
||||||
3 the tunnel comes up to the hub from the token alone: the hub's endpoint and key, its address
|
|
||||||
4 the node dials the bus OVER THE TUNNEL, at the bus's private address
|
|
||||||
5 it proves itself, and is proved to enrolment, checking the key is the one the token named
|
|
||||||
6 the rest of the overlay the whole peer set, delivered as files
|
|
||||||
7 names, filtering, routes as before
|
|
||||||
```
|
|
||||||
|
|
||||||
The link no longer stays on the underlay. The bus is reached over the tunnel by every machine,
|
|
||||||
including one that is joining, so it is never opened to the internet. The precondition becomes: **the
|
|
||||||
hub's tunnel must be dialable by every node, at a stable address.** That port answers nothing to a
|
|
||||||
key it does not know.
|
|
||||||
|
|
||||||
**Whether the link should later move onto the overlay, with the underlay as fallback, is
|
**Whether the link should later move onto the overlay, with the underlay as fallback, is
|
||||||
[open](../../02-DECISIONS/0007-connectivity.md).** It is a decision rather than a derivation: the
|
[open](../../02-DECISIONS/0007-connectivity.md).** It is a decision rather than a derivation: the
|
||||||
gain is which network carries bytes, not what an attacker can reach, since the link is already
|
gain is which network carries bytes, not what an attacker can reach, since the link is already
|
||||||
@@ -768,16 +743,6 @@ tests over a fixture report check the recording, the preview's fates, the status
|
|||||||
predicate. Live: the home server's record names the predecessor's chain as *other* and `status`
|
predicate. Live: the home server's record names the predecessor's chain as *other* and `status`
|
||||||
names the machine until the chain is removed by hand.
|
names the machine until the chain is removed by hand.
|
||||||
|
|
||||||
*2026-10-02, [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md):* removing what
|
|
||||||
the host reports as *other* is reached through the packet filter seat's `remove` verb, an operator's act
|
|
||||||
by name on the bus; the seat also serves `rules` and `reload`, and its holder's runtime declares the
|
|
||||||
`NET_ADMIN` capability on the machine's network. See design 33.
|
|
||||||
|
|
||||||
*2026-10-02, [ADR 0180](../../02-DECISIONS/0180-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md):*
|
|
||||||
once a machine is converged, the front end it was found with is uninstalled, not merely disabled — the
|
|
||||||
packet filter's holder declares its package absent after the mesh's filter is loaded, and a return to
|
|
||||||
adopted then enables nothing. The rollback path ADR 0100 kept on disk is given up on purpose.
|
|
||||||
|
|
||||||
## 5 — Certificates
|
## 5 — Certificates
|
||||||
|
|
||||||
**Two authorities, kept separate on purpose.**
|
**Two authorities, kept separate on purpose.**
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ code:
|
|||||||
- mesh-controller internal/inventory
|
- mesh-controller internal/inventory
|
||||||
- mesh-controller internal/catalogue
|
- mesh-controller internal/catalogue
|
||||||
- mesh-controller cmd/mesh-controller
|
- mesh-controller cmd/mesh-controller
|
||||||
updated: 2026-10-02
|
updated: 2026-10-01
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.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/0120-a-roster-fact-carries-its-format-as-a-template.md
|
||||||
@@ -205,10 +205,6 @@ keeping the predecessor running, so the model questions above are no longer defe
|
|||||||
record, the home as a placement root, user-scoped services and the one-off steps a hook used to run
|
record, the home as a placement root, user-scoped services and the one-off steps a hook used to run
|
||||||
each need a decision before the modules that replace the generators can be written.
|
each need a decision before the modules that replace the generators can be written.
|
||||||
|
|
||||||
## The family beyond `~/.ssh` — 2026-10-02
|
|
||||||
|
|
||||||
The modules §2 calls *a family* — the shell, the terminal, the desktop, everything under a home that is not `~/.ssh` — are designed in [37 — The operator's machine](37-the-operators-machine.md), under [ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md) to [0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md). This document keeps `~/.ssh`, the CA and the roster files. Two things it listed as not built are decided there: user-scoped services (ADR 0177) and the one-off steps a hook used to run (declared state, or a seat's verb).
|
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- The gap was found generating `~/.ssh/config` from the *HAL* registry (`hal/terminal`'s
|
- The gap was found generating `~/.ssh/config` from the *HAL* registry (`hal/terminal`'s
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
layer: to-be
|
layer: to-be
|
||||||
status: implemented
|
status: implemented
|
||||||
code: [mesh-controller, mesh-tools]
|
code: [mesh-controller, mesh-tools]
|
||||||
updated: 2026-10-02
|
updated: 2026-10-01
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md
|
|
||||||
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
||||||
- 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
- 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
||||||
- 02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md
|
- 02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md
|
||||||
@@ -122,8 +121,6 @@ module-specific names that changes the day the forge is replaced.
|
|||||||
asked of the module, through a `tools` verb every runtime answers) and lists a role's tools when the
|
asked of the module, through a `tools` verb every runtime answers) and lists a role's tools when the
|
||||||
records carry them.
|
records carry them.
|
||||||
|
|
||||||
*Amended 2026-10-02 by [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md):* the module that serves this to an agent is the node tools runtime — one per node, host-side, serving every assigned module's tools as well as answering the person on loopback. The console is its serving mode, renamed. See [37 — The operator's machine](37-the-operators-machine.md) §3.
|
|
||||||
|
|
||||||
## 7. Versioning
|
## 7. Versioning
|
||||||
|
|
||||||
A seat's tools are an interface and change like one. Additive within a version. A change that would
|
A seat's tools are an interface and change like one. Additive within a version. A change that would
|
||||||
@@ -172,18 +169,6 @@ either way.
|
|||||||
What stays as designed and not built: which verbs any *other* seat serves, and §3 for module-declared
|
What stays as designed and not built: which verbs any *other* seat serves, and §3 for module-declared
|
||||||
seats' schemas beyond the names their manifests already list.
|
seats' schemas beyond the names their manifests already list.
|
||||||
|
|
||||||
## The firewall seat's verbs, 2026-10-02
|
|
||||||
|
|
||||||
[ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md). The first node-scoped seat
|
|
||||||
to carry verbs: `node-packet-filter` serves `rules` (the filter as the machine enforces it, nftables
|
|
||||||
and legacy), `reload` (the mesh's own filter from its file) and `remove` (one rule set the mesh did
|
|
||||||
not write, named as the host reports it under ADR 0168; refusing the mesh's tables, the runtime's
|
|
||||||
own chains, a built-in chain and an active found firewall's). Every holder serves all three; the
|
|
||||||
nftables module does so from a runtime on the machine's network with `NET_ADMIN`, which is the first
|
|
||||||
container to declare a capability. Removing a predecessor's rule set is an operator's act reached
|
|
||||||
through the seat, recorded on the bus, instead of a shell on the machine. *How it is checked:* ADR
|
|
||||||
0169's table.
|
|
||||||
|
|
||||||
## What this does not settle
|
## What this does not settle
|
||||||
|
|
||||||
- Which verbs each seat should serve. That is a decision per seat, and the reason to do it slowly: a
|
- Which verbs each seat should serve. That is a decision per seat, and the reason to do it slowly: a
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
layer: to-be
|
layer: to-be
|
||||||
status: implemented
|
status: implemented
|
||||||
code: [mesh-catalog, mesh-tools, mesh-controller]
|
code: [mesh-catalog, mesh-tools, mesh-controller]
|
||||||
updated: 2026-10-02
|
updated: 2026-10-01
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
||||||
- 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
- 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
||||||
@@ -20,8 +20,6 @@ An agent reaches them over MCP on the machine's loopback; a person reaches the s
|
|||||||
installed by hand, nothing is configured with an address, and the mesh knows the surface exists because
|
installed by hand, nothing is configured with an address, and the mesh knows the surface exists because
|
||||||
it put it there ([ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)).
|
it put it there ([ADR 0152](../../02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md)).
|
||||||
|
|
||||||
> **Amended 2026-10-02 by [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md).** What this document describes stays true in substance and changes in form: the console becomes the serving mode of the node tools runtime, a host-side process the host supervises rather than a container, which also serves every assigned module's tools from their bundles. The module is renamed `node-tools`. [37 — The operator's machine](37-the-operators-machine.md) §3 is where it now lives.
|
|
||||||
|
|
||||||
## 1. What it is
|
## 1. What it is
|
||||||
|
|
||||||
A module, `mesh-console`, in the catalogue. Its image is the tool runtime's own — the client that
|
A module, `mesh-console`, in the catalogue. Its image is the tool runtime's own — the client that
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
---
|
|
||||||
layer: to-be
|
|
||||||
status: in-progress
|
|
||||||
code: [mesh-host, mesh-controller, mesh-tools, mesh-catalog]
|
|
||||||
updated: 2026-10-02
|
|
||||||
decisions:
|
|
||||||
- 02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md
|
|
||||||
- 02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md
|
|
||||||
- 02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md
|
|
||||||
- 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md
|
|
||||||
- 02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md
|
|
||||||
- 02-DECISIONS/0040-what-a-module-is.md
|
|
||||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
|
||||||
- 02-DECISIONS/0126-a-module-declares-its-own-seats.md
|
|
||||||
- 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
|
|
||||||
- 02-DECISIONS/0161-what-deserves-a-seat.md
|
|
||||||
- 02-DECISIONS/0102-the-mesh-writes-into-a-shared-file-never-over-it.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 37 — The operator's machine
|
|
||||||
|
|
||||||
**Every configurable thing on a node is a module, the home included, and the same catalogue serves
|
|
||||||
a server and a laptop.** One default configuration per module, varied per node by a setting or a
|
|
||||||
kept region; roles a machine has once as node-scoped seats with tool contracts; one tool runtime
|
|
||||||
per node serving every module's tools on the host side
|
|
||||||
([ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md)
|
|
||||||
to [0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md)).
|
|
||||||
This is the design [to-be 29](29-a-node-has-operator-accounts.md) §2 called *a family* and
|
|
||||||
[research 018](../../01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md) measured.
|
|
||||||
|
|
||||||
## 1. What a module of the environment looks like
|
|
||||||
|
|
||||||
Worked on the first one, a shell. The `zsh` module declares:
|
|
||||||
|
|
||||||
- a **package**, `zsh`;
|
|
||||||
- **files under the home**, owned by the account: the shell's rc file with the module's default
|
|
||||||
configuration, carrying a kept region for the operator's own lines, and `${setting:…}`
|
|
||||||
placeholders for the few values a node varies; the account and its home are machine facts the
|
|
||||||
controller resolves ([ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md),
|
|
||||||
to-be 29 §2);
|
|
||||||
- a **seat declaration**, `login-shell`, node-scoped, with its one verb; and a **claim** on it;
|
|
||||||
- a **`user` shape** naming the shell, applied only where the module holds the seat;
|
|
||||||
- a **tools bundle**, the artifact kind for interpreted code, with `execute` and the module's own
|
|
||||||
`show-config`.
|
|
||||||
|
|
||||||
No container, no unit, no service. It is assigned to every node with an operator account. The
|
|
||||||
`fish` and `bash` modules are the same with another package and other files; one of the three
|
|
||||||
holds the seat on each node.
|
|
||||||
|
|
||||||
The second shape is **system scope**: the login manager declares a package, two files under
|
|
||||||
`/etc`, and a service, which is exactly what the ssh daemon module declares today. The third
|
|
||||||
shape is **graphical**: the window manager declares a package, files under the home, a
|
|
||||||
user-scoped unit or two, a claim on the display-session seat, a dependency on the display server
|
|
||||||
being held, and a bundle with its tools. Nothing in any of them says which machine it is for.
|
|
||||||
|
|
||||||
## 2. Variation
|
|
||||||
|
|
||||||
A node differs from the default in two ways and no other
|
|
||||||
([ADR 0174](../../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md)):
|
|
||||||
a **setting** the module declared, set in the node's layer and rendered into the file; or lines in
|
|
||||||
a **kept region** the file marks. The predecessor's ninety theme variables become the settings of
|
|
||||||
the modules whose files read them. Until the settings record proposed alongside the
|
|
||||||
container-runtime records ships — a setting names the file it lands in — environment modules carry
|
|
||||||
defaults in their files and declare no setting; that is the order, not a preference.
|
|
||||||
|
|
||||||
## 3. The node tools runtime
|
|
||||||
|
|
||||||
One per node, started and restarted by the host as a sibling process, never a container
|
|
||||||
([ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)).
|
|
||||||
It is the tool runtime that exists, in the role it was written for: it reads the memberships of
|
|
||||||
every module assigned to the node, loads each module's tools bundle, and serves every tool and
|
|
||||||
every held seat's verb on the subjects issued. It holds the node's one bus credential and may call
|
|
||||||
every tool on the mesh. Its serving mode on the machine's loopback is what the console was
|
|
||||||
([to-be 34](34-the-console.md)); the module is renamed **node-tools** and declares the interpreter
|
|
||||||
it needs as a package.
|
|
||||||
|
|
||||||
A bundle reaches the node as any artifact does. A push that adds or replaces one is a reload. A
|
|
||||||
bundle that fails to load is named in the node's report and the others serve. A tool that needs
|
|
||||||
root escalates itself.
|
|
||||||
|
|
||||||
## 4. The seats of the environment
|
|
||||||
|
|
||||||
Decided now: **`login-shell`** (module-declared; zsh, fish, bash; verb `execute`) and
|
|
||||||
**`node-service-manager`** (the mesh's own; systemd; verbs over units in both scopes). The rest
|
|
||||||
are candidates from [research 018](../../01-RESEARCH/018-the-operators-machine-as-modules/04-the-seats-of-the-environment.md),
|
|
||||||
one record each when its first holder is written: display server, display session, terminal
|
|
||||||
emulator, launcher, notifier, compositor, lock screen, bar, login manager, audio, clipboard, boot.
|
|
||||||
Editors, browsers, media players, the agent, the downloads and scripts folders are modules with
|
|
||||||
tools and no seat.
|
|
||||||
|
|
||||||
A module that needs a role filled depends on **the seat being held** on the node, not on a
|
|
||||||
capability: the window manager needs the display server seat held, by xorg or by a compositor
|
|
||||||
that is its own server. Whether a held seat can gate an assignment is the first question the
|
|
||||||
resolver is asked by the second graphical module; the display server itself is gated by the
|
|
||||||
`graphical-session` capability the profile already reports.
|
|
||||||
|
|
||||||
## 5. What the host gains, and what it does not
|
|
||||||
|
|
||||||
- `service` gains `scope: user`, applied as the account
|
|
||||||
([ADR 0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md)).
|
|
||||||
- The host starts and supervises the node tools runtime as it would any host-side process, and
|
|
||||||
delivers bundles as artifacts.
|
|
||||||
- Nothing else. No hooks, no actions: `chsh` is the `user` shape, enabling a unit is the `service`
|
|
||||||
shape, rebuilding boot images is a verb of the boot seat when that seat is written.
|
|
||||||
- A gap, recorded: the `package` shape drives the distribution's package manager and nothing
|
|
||||||
outside its repositories. The login manager in use is such a package; it waits on an official
|
|
||||||
package or a decision the host does not yet have.
|
|
||||||
|
|
||||||
## 6. The order of the build
|
|
||||||
|
|
||||||
1. **The operator account on every node** — `mesh-controller node` with the login name; empty on
|
|
||||||
all four today. Nothing home-scoped composes before it.
|
|
||||||
2. **The node tools runtime** — mesh-host supervises it; mesh-tools serves bundles from memberships
|
|
||||||
and reloads; mesh-controller composes the bundle into the declaration and the memberships to one
|
|
||||||
runtime per node; the catalogue renames the console. Proven when the packet-filter verbs answer
|
|
||||||
from it and its container is gone.
|
|
||||||
3. **`zsh`**, the first environment module: seat, `user` shape, home files, `execute`. Proven on a
|
|
||||||
server first, then every node.
|
|
||||||
4. **`systemd`** and user scope: the host's field, the seat seeded, the module. Proven by the
|
|
||||||
desktop's reload watcher declared `scope: user` on a workstation.
|
|
||||||
5. **The login manager**, system scope, once its package is installable; then the display server,
|
|
||||||
the window manager, and the rest of the graphical stack, each seat its own record.
|
|
||||||
6. **Settings** for the theme knobs, after the settings record ships and issue 168 closes.
|
|
||||||
|
|
||||||
## How it is checked
|
|
||||||
|
|
||||||
| Claim | Checked by |
|
|
||||||
|---|---|
|
|
||||||
| A module with a package, home files, a seat and a bundle resolves and composes on a node with an account, and is refused on one without | the controller's composition tests |
|
|
||||||
| One runtime per node serves every assigned module's tools; a per-module tool container no longer exists | the runtime's tests; `docker ps` on a converged machine |
|
|
||||||
| A user-scoped unit is applied as the account | the host's tests |
|
|
||||||
| A node's difference from a module's default is visible as a setting with a source or a kept region | `mesh-controller.settings`; the host's write-into tests |
|
|
||||||
| The same manifests assign to a server and a workstation; the graphical ones are refused on the server by name | the resolver's tests and the live mesh |
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Research 018](../../01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md)
|
|
||||||
- [To-be 29](29-a-node-has-operator-accounts.md) — the account and the home; this design is the
|
|
||||||
family its §2 names, beyond `~/.ssh`.
|
|
||||||
- [To-be 33](33-the-tools-the-mesh-answers.md), [to-be 34](34-the-console.md) — the tools and
|
|
||||||
the console, amended by ADR 0175.
|
|
||||||
- [To-be 05](05-the-node-host.md) — the host's vocabulary, widened by ADR 0177.
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
---
|
|
||||||
layer: to-be
|
|
||||||
status: in-progress
|
|
||||||
code: [mesh-tools, mesh-controller, mesh-host, mesh-catalog]
|
|
||||||
updated: 2026-10-02
|
|
||||||
decisions:
|
|
||||||
- 02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md
|
|
||||||
- 02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md
|
|
||||||
- 02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md
|
|
||||||
- 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md
|
|
||||||
- 02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md
|
|
||||||
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
|
|
||||||
- 02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md
|
|
||||||
---
|
|
||||||
|
|
||||||
# 38. Building the operator's machine
|
|
||||||
|
|
||||||
**The work of [design 37](37-the-operators-machine.md), broken into packages small enough that each
|
|
||||||
ends at something a person can see run, in the order their dependencies allow.** Design 37 is the
|
|
||||||
authority on *what* is built; this document holds only the packages, their order, their sizes and
|
|
||||||
their proofs, and is wrong the moment it disagrees with 37 rather than the other way round. It is
|
|
||||||
the shape [design 28](28-building-the-bus.md) gave the bus work, applied here.
|
|
||||||
|
|
||||||
## How this is built, and where it is run
|
|
||||||
|
|
||||||
**On the live mesh, by the operator's decision.** Every package is written with unit tests and
|
|
||||||
committed on one branch per repository; its proof runs on the four machines, not in the lab.
|
|
||||||
[ADR 0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md) already says the live mesh is
|
|
||||||
the test bed; the operator's words on 2026-10-02 were *skip the lab, it is not too bad if something
|
|
||||||
is broken*. The cost accepted: a package that breaks the runtime breaks every tool on a node until
|
|
||||||
the next push, and the controller's own verbs stay reachable through the controller seat whatever
|
|
||||||
happens to a node's runtime — which is the one thing that must hold, and does by construction
|
|
||||||
([ADR 0154](../../02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md)).
|
|
||||||
|
|
||||||
Each package names what proves it. A package that cannot name its proof is divided until it can.
|
|
||||||
|
|
||||||
## What exists already, measured
|
|
||||||
|
|
||||||
Counted 2026-10-02 in the four repositories, non-test source. The point of the count is the same
|
|
||||||
as design 28's: nothing here is new ground; every package reshapes something standing.
|
|
||||||
|
|
||||||
| Piece | Today | Size | Becomes |
|
|
||||||
|---|---|---|---|
|
|
||||||
| the tool runtime | TypeScript: loads `MESH_TOOL_MODULES`, serves one module's tools and its claimed seats' verbs; `serve` is the console | ~1 700 lines over six files | loads every assigned module's bundle; `serve` is node tools |
|
|
||||||
| the host's `process` shape | Go: fetch a bundle by digest, unpack under the mesh's daemons directory, write the unit, run it | 343 lines | **unchanged** — the runtime is one such process |
|
|
||||||
| the host's `archive` shape | Go: fetch and unpack an artifact at a path | 185 lines | **unchanged** — a module's tools bundle is one such archive |
|
|
||||||
| the controller's bus principals | Go: one principal per module per node, grants from what it declares | 132 lines | gains one principal per node for the runtime |
|
|
||||||
| the controller's memberships | Go: one per assignment, the subjects a runtime serves | 143 lines | **unchanged** in shape; the runtime reads several |
|
|
||||||
| the controller's declaration composer | Go, one file | 2 053 lines | gains the runtime's process, the bundles' archives, two env words |
|
|
||||||
| the catalogue | 35 manifests build a per-module tool container on the runtime's base image | — | none do; the runtime is a module of its own |
|
|
||||||
|
|
||||||
**Two measurements decide the shape.** The host needs no change: a `process` and an `archive` are
|
|
||||||
what the runtime and a bundle are, and both are applied today. And the runtime already does
|
|
||||||
nine-tenths of the job — the loop over entrypoints, the seat verbs, the membership subscription —
|
|
||||||
for one module; the work is to let it do the same for a list.
|
|
||||||
|
|
||||||
## The order the work allows
|
|
||||||
|
|
||||||
```
|
|
||||||
WP1 the runtime serves many modules (mesh-tools) ──┐
|
|
||||||
WP2 the controller composes one runtime a node (mesh-controller) ──┤ independent, test-proven
|
|
||||||
│
|
|
||||||
WP3 the runtime is a module; the console is its serving mode (mesh-tools, mesh-catalog)
|
|
||||||
│
|
|
||||||
WP4 the first holder moves: the packet filter (mesh-catalog) ── the live proof
|
|
||||||
│
|
|
||||||
WP5 the shell, on a server (mesh-catalog) ── the first environment module live
|
|
||||||
WP6 the service manager, on a workstation (mesh-host #72, mesh-catalog)
|
|
||||||
│
|
|
||||||
WP7 the login manager, the display server, the window manager … ── one record per seat, after this document
|
|
||||||
WP8 settings for the theme knobs ── after issue 168 closes
|
|
||||||
```
|
|
||||||
|
|
||||||
WP1 and WP2 touch different repositories and meet only at the membership's shape, which neither
|
|
||||||
changes; they are built in parallel. WP3 needs both. WP4 is the first time anything on a machine
|
|
||||||
changes, and it is the proof of the whole. WP5 and WP6 are the first environment modules; the
|
|
||||||
packages after them are design 37 §4's candidates and are not broken down here, because each
|
|
||||||
begins with a decision record this document cannot anticipate.
|
|
||||||
|
|
||||||
## WP1 — The runtime serves many modules
|
|
||||||
|
|
||||||
*mesh-tools. About a day.*
|
|
||||||
|
|
||||||
**What changes.** `serve` takes a list of modules to serve, each with its entrypoints, rather than
|
|
||||||
one module and one credential. The runtime reads one membership per module from the subjects
|
|
||||||
[ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
|
|
||||||
derives for each, and serves each module's tools on that module's subjects and each held seat's
|
|
||||||
verbs on the seat's. The filter that drops a registration under any name but the one module goes;
|
|
||||||
what remains is the rule that a registration under a seat's name is served only where some module
|
|
||||||
the runtime serves claims that seat. A bundle that throws on import is named in the log and in
|
|
||||||
what `tools` answers, and the others serve. The runtime reads `MESH_OPERATOR_ACCOUNT` and
|
|
||||||
`MESH_OPERATOR_HOME` and hands them to every tool's environment.
|
|
||||||
|
|
||||||
**What does not change.** The SDK. The broker client. The MCP surface. A module's tool code.
|
|
||||||
|
|
||||||
**Proof.** The runtime's test against a real bus: three bundles, one of which throws on import;
|
|
||||||
five tools and two seat verbs answer on their subjects; `tools` names the failed bundle; a
|
|
||||||
membership republished mid-run re-subscribes without a restart.
|
|
||||||
|
|
||||||
## WP2 — The controller composes one runtime per node
|
|
||||||
|
|
||||||
*mesh-controller. Two to three days; the largest package.*
|
|
||||||
|
|
||||||
**What changes**, in four pieces, each its own commit:
|
|
||||||
|
|
||||||
1. **A node principal.** Beside one principal per module per node, one per node of kind
|
|
||||||
`node-tools`: its serving grants are the union of every assigned module's tool subjects and every
|
|
||||||
held seat's verbs on that node, its invoking grant is `*`, and it consumes nothing. The
|
|
||||||
per-module memberships are composed as today; nothing else on the bus learns a new shape.
|
|
||||||
2. **Bundle delivery.** For every assigned module whose build produced a `bundle`, the node's
|
|
||||||
declaration gains an `archive` placed under a directory the controller derives, so the host
|
|
||||||
fetches and unpacks it as it does any artifact. The bundle's digest is what the build recorded.
|
|
||||||
3. **The runtime's process.** One `process` per node running the runtime from its own bundle
|
|
||||||
(WP3), `MESH_TOOL_MODULES` composed from the unpacked entrypoints, `MESH_OPERATOR_ACCOUNT` and
|
|
||||||
`MESH_OPERATOR_HOME` from the account fact, `restart-on` naming every bundle so a push that
|
|
||||||
changes one restarts it. A node with no account composes the runtime without the two words.
|
|
||||||
4. **The gate.** A manifest declaring `tools` and a container built on the runtime's base image is
|
|
||||||
refused at registration once the runtime module is registered, naming this record. It is the
|
|
||||||
mechanism that keeps the old pattern from returning by habit.
|
|
||||||
|
|
||||||
**Proof.** Composition tests: a node with three assigned modules, one holding a seat, yields one
|
|
||||||
process, three archives, one node principal whose grants are the union, and the same three
|
|
||||||
memberships as before. The gate's test: the packet-filter manifest as it is today is refused once
|
|
||||||
the runtime is registered.
|
|
||||||
|
|
||||||
## WP3 — The runtime is a module, and the console is its serving mode
|
|
||||||
|
|
||||||
*mesh-tools and mesh-catalog. A day.*
|
|
||||||
|
|
||||||
**What changes.** mesh-tools gains a `bundle` artifact of itself beside its images, and its manifest
|
|
||||||
becomes the `node-tools` module: a package for the interpreter, the loopback listener the console
|
|
||||||
declared, `invokes: *`, and nothing else — the process is the controller's to compose (WP2). In the
|
|
||||||
catalogue, `mesh-console` is retired as a module and `node-tools` assigned where it was. The
|
|
||||||
runtime's `serve` keeps answering MCP on loopback; the person's end of it keeps the name *console*
|
|
||||||
([glossary](../../00-META/glossary.md)).
|
|
||||||
|
|
||||||
**Proof.** On every node: the console's container is gone, `node-tools` runs as a unit the host
|
|
||||||
wrote, `tools/list` on loopback answers as before, and the controller's verbs answer through it.
|
|
||||||
This is the first live step, and it is reversible by re-assigning `mesh-console`.
|
|
||||||
|
|
||||||
## WP4 — The first holder moves: the packet filter
|
|
||||||
|
|
||||||
*mesh-catalog. Half a day. The live proof of ADR 0175.*
|
|
||||||
|
|
||||||
**What changes.** The nftables module drops its container, its `NET_ADMIN` and its runtime
|
|
||||||
artifact; its tools bundle stays and its claim stays. Its `remove` and `reload` escalate inside the
|
|
||||||
tool where they need root, which they have, since the runtime runs as the node's account.
|
|
||||||
|
|
||||||
**Proof.** `node-packet-filter.rules@<node>`, `reload` and `remove` answer from the runtime on all
|
|
||||||
four machines; `docker ps` shows no `mesh-nftables`; `status` is well. Then the fail2ban holder
|
|
||||||
proposed in an open change follows the same way when it lands.
|
|
||||||
|
|
||||||
## WP5 — The shell, on a server first
|
|
||||||
|
|
||||||
*mesh-catalog #224, already written. Half a day to assign and prove.*
|
|
||||||
|
|
||||||
**Order.** Assign `zsh` to one server; push; `login-shell.execute@<server> command="uptime"`
|
|
||||||
answers; the account's login shell reads zsh; its `~/.zshrc` carries the mesh's block with the
|
|
||||||
operator's lines around it. Then the other three nodes. The two things the manifest cannot say
|
|
||||||
— the `user` shape applying only where the seat is held, and a second shell module installed
|
|
||||||
beside the holder — are the first follow-up record after this document.
|
|
||||||
|
|
||||||
## WP6 — The service manager, on a workstation
|
|
||||||
|
|
||||||
*mesh-host #72 merged first; mesh-catalog #224. Half a day.*
|
|
||||||
|
|
||||||
**Order.** Merge the host's user-scope change and let it roll. Assign `systemd` everywhere;
|
|
||||||
`node-service-manager.units@<node> scope=user` answers on a workstation. Then the first user-scoped
|
|
||||||
unit the mesh sends: the window manager's reload watcher, declared `scope: user` by the window
|
|
||||||
manager module when WP7 writes it — until then, the host's change is proven by its tests and by
|
|
||||||
the verb answering.
|
|
||||||
|
|
||||||
## What is deliberately not here
|
|
||||||
|
|
||||||
- **The graphical stack's seats** (WP7). Each begins with a record naming its holders and verbs,
|
|
||||||
and the first graphical module asks the resolver a question this document cannot answer for it:
|
|
||||||
whether a held seat gates another's assignment.
|
|
||||||
- **Settings for the theme knobs** (WP8). Blocked on the settings record proposed in an open change
|
|
||||||
and on [issue 168](../../04-ISSUES/168-a-setting-reaches-every-file-and-contribution/00-report.md).
|
|
||||||
- **Reload without restart.** WP2 restarts the runtime on a bundle change; a reload that keeps the
|
|
||||||
other modules' tools up during one module's change is a refinement for after WP4 proves the
|
|
||||||
simple form.
|
|
||||||
- **Lingering.** A user-scoped unit answers only while the account's manager runs; declaring
|
|
||||||
lingering for the account is a field on the `user` shape, decided when a server first needs a
|
|
||||||
user unit.
|
|
||||||
|
|
||||||
## How this list is kept true
|
|
||||||
|
|
||||||
Each package's proof is run on the live mesh when the package is finished and its line here gains
|
|
||||||
the date and the commit, the way [ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md)
|
|
||||||
carries *built and proven live*. A package whose proof fails is not reworded; the failure is
|
|
||||||
recorded under it and the package stays open. When WP6 is proven, design 37's status moves to
|
|
||||||
`implemented` for what it covers and this document's to the same.
|
|
||||||
@@ -41,8 +41,6 @@ document is written and this one's status becomes `implemented`.
|
|||||||
| [`29-a-node-has-operator-accounts.md`](29-a-node-has-operator-accounts.md) | **In progress.** A node has an operator account and a resource may live under its home — built in the controller; the ssh-client module, the SSH CA, the `~/.ssh` boundary and user-scoped services are not. The account fact still wants its decision record | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0051](../../02-DECISIONS/0051-shared-data-is-the-operators.md) |
|
| [`29-a-node-has-operator-accounts.md`](29-a-node-has-operator-accounts.md) | **In progress.** A node has an operator account and a resource may live under its home — built in the controller; the ssh-client module, the SSH CA, the `~/.ssh` boundary and user-scoped services are not. The account fact still wants its decision record | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0051](../../02-DECISIONS/0051-shared-data-is-the-operators.md) |
|
||||||
|
|
||||||
| [`32-what-a-module-declares.md`](32-what-a-module-declares.md) | **Proposed.** What a module declares and what the bus derives from it: three namespaces, subjects from local names, queues never declared, the five relationships, and the build-publish-deploy lifecycle on one bus | [ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md), [ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md), superseded by [ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md) (superseding [ADR 0125](../../02-DECISIONS/0125-the-bus-is-the-only-broker.md)), [ADR 0041](../../02-DECISIONS/0041-events-are-a-relationship.md) |
|
| [`32-what-a-module-declares.md`](32-what-a-module-declares.md) | **Proposed.** What a module declares and what the bus derives from it: three namespaces, subjects from local names, queues never declared, the five relationships, and the build-publish-deploy lifecycle on one bus | [ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md), [ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md), superseded by [ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md) (superseding [ADR 0125](../../02-DECISIONS/0125-the-bus-is-the-only-broker.md)), [ADR 0041](../../02-DECISIONS/0041-events-are-a-relationship.md) |
|
||||||
| [`37-the-operators-machine.md`](37-the-operators-machine.md) | **In progress.** Every configurable thing on a node is a module, the home included; one default per module varied by settings or kept regions; roles a machine has once as seats with tool contracts; one tool runtime per node on the host side | [ADR 0173](../../02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md), [0174](../../02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md), [0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0176](../../02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md), [0177](../../02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md) |
|
|
||||||
| [`38-building-the-operators-machine.md`](38-building-the-operators-machine.md) | **In progress.** The work of design 37 as packages: the runtime serves many modules, the controller composes one per node, the console becomes its serving mode, the packet filter moves first, then the shell and the service manager — tested on the live mesh by the operator's decision | [ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md), [0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md), [0149](../../02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md) |
|
|
||||||
|
|
||||||
## Not yet written
|
## Not yet written
|
||||||
|
|
||||||
|
|||||||
+85
@@ -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.
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
status: resolved
|
|
||||||
opened: 2026-10-02
|
|
||||||
located-in: [mesh-tools src/client.ts (toolsOn left node-scoped seats out of the listing), mesh-tools src/mcp.ts (the call resolved the key against that listing)]
|
|
||||||
fixed-by: mesh-tools 27 — node-scoped seats are listed with their scope, the verb requires the machine, and the call carries it in the subject
|
|
||||||
amended-design:
|
|
||||||
---
|
|
||||||
|
|
||||||
# 199 — A node-scoped seat's verb could not be called through the console
|
|
||||||
|
|
||||||
## What was observed
|
|
||||||
|
|
||||||
2026-10-02, the first time a node-scoped seat declared verbs
|
|
||||||
([ADR 0170](../../02-DECISIONS/0170-the-firewall-seat-serves-its-verbs.md)). The packet filter's
|
|
||||||
holder on every machine served `rules`, `reload` and `remove` on the seat's per-machine subjects, and
|
|
||||||
the bus admitted them. The console answered every call with *nothing serves
|
|
||||||
node-packet-filter.rules@<machine>*.
|
|
||||||
|
|
||||||
[Design 33 §4](../../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) says a node-scoped seat's
|
|
||||||
tool carries the node it is asked of, as `<seat>.<verb>@<node>`. The console's listing left
|
|
||||||
node-scoped seats out — the comment said they *wait for a caller naming the node* — but the roles map
|
|
||||||
the console resolves a name against is built from that same listing. So the name never resolved as a
|
|
||||||
seat's verb, fell through to a module's subject nobody served, and the refusal named the wrong cause.
|
|
||||||
|
|
||||||
## Why it matters beyond this instance
|
|
||||||
|
|
||||||
A stated behaviour that did not happen, with a refusal that pointed elsewhere: the seat's verbs were
|
|
||||||
live on four machines and unreachable from the one surface a person uses. It could only be found by a
|
|
||||||
node-scoped seat declaring verbs, which none had.
|
|
||||||
|
|
||||||
## Resolved, 2026-10-02
|
|
||||||
|
|
||||||
mesh-tools 27: node-scoped seats are listed with their scope, their verbs take a required `node`, the
|
|
||||||
call carries it in the subject, and a call without one is refused in words. Tested with a round trip
|
|
||||||
asking one machine's holder and being refused without a machine.
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
status: open
|
|
||||||
opened: 2026-10-02
|
|
||||||
located-in: []
|
|
||||||
fixed-by:
|
|
||||||
amended-design:
|
|
||||||
---
|
|
||||||
|
|
||||||
# 200 — The controller's answer to a long console call is refused by the bus
|
|
||||||
|
|
||||||
## What was observed
|
|
||||||
|
|
||||||
2026-10-02. A `push` of the control node asked through the console came back as *mesh-controller.push
|
|
||||||
did not answer in time. Something is serving it, so this is the tool being slow rather than absent.*
|
|
||||||
The push had run; the machine applied. The bus's log on the control node, at the same moment:
|
|
||||||
|
|
||||||
```
|
|
||||||
[ERR] 10.10.0.1:56030 - cid:4015 - Publish Violation - User "controller",
|
|
||||||
Subject "_INBOX.shanks.mesh-console.WRNO5V9IJ1FX35AU5NRBEB.WRNO5V9IJ1FX35AU5PN1UW"
|
|
||||||
```
|
|
||||||
|
|
||||||
The controller's reply to the console's request was refused: the controller's bus account may not
|
|
||||||
publish to the console's reply inbox. Shorter calls (`status`, `node`, `nodes`) answer; the long ones
|
|
||||||
(`push` of a large machine, `issue`) time out on the console's side although they succeed.
|
|
||||||
|
|
||||||
## Why it matters beyond this instance
|
|
||||||
|
|
||||||
A call that succeeds and is reported as a timeout sends a person to retry what already happened — a
|
|
||||||
second push, a second issue — and reads as the mesh being slow when it is the mesh refusing itself.
|
|
||||||
Whether the inbox prefix the console uses is the one the controller's account is allowed to answer, or
|
|
||||||
the request outlives the inbox subscription, is what a diagnosis has to tell apart.
|
|
||||||
|
|
||||||
## Open questions
|
|
||||||
|
|
||||||
- Which reply inboxes may the controller's account publish to, and which does the console request on?
|
|
||||||
- Does a reply after the requester's timeout count as a violation, or is the prefix itself refused?
|
|
||||||
Reference in New Issue
Block a user