From bf39baf104b4a2cc6ad8e15910ac4080475f2283 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 2 Oct 2026 16:34:57 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Research=20018=20graduates:=20ADRs=200173?= =?UTF-8?q?=E2=80=930177=20and=20to-be=2037,=20the=20operator's=20machine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every configurable thing on a node is a module, the home included, and a module is whatever it declares (0173, extending 0040). A node varies a module only through a setting rendered into the file or a kept region, never an edit (0174, extending 0011; issue 168 first). One tool runtime per node serves every module's tools on the host side, never in a container; the console is its serving mode, renamed node-tools (0175, extending 0150; 0047/0150/0152 carry dated notes). The login shell is a node seat held by one shell module with `execute` as its contract (0176). A unit may be user-scoped and the service manager is a node seat held by systemd (0177). To-be 37 is handed off in-progress to mesh-host, mesh-controller, mesh-tools and mesh-catalog, with the build in order: the account on every node, the runtime, zsh, systemd, then the graphical stack. To-be 29 keeps ~/.ssh and points at 37; 33 §6 and 34 are amended; the glossary gains node tools, bundle, kept region, installed/holding, and retires flavor. --- 00-META/glossary.md | 18 +++ .../00-overview.md | 12 +- 02-DECISIONS/0040-what-a-module-is.md | 2 + ...as-its-own-process-with-its-own-account.md | 2 + ...-supervised-processes-under-one-account.md | 2 + ...erators-surface-is-a-module-the-console.md | 2 + ...-meshs-and-a-module-is-what-it-declares.md | 108 +++++++++++++ ...settings-and-kept-regions-never-an-edit.md | 92 ++++++++++++ ...es-every-modules-tools-on-the-host-side.md | 122 +++++++++++++++ ...a-node-seat-and-execute-is-its-contract.md | 81 ++++++++++ ...-and-the-service-manager-is-a-node-seat.md | 80 ++++++++++ 02-DECISIONS/README.md | 5 + .../29-a-node-has-operator-accounts.md | 6 +- .../01-to-be/33-the-tools-the-mesh-answers.md | 2 + 03-DESIGN/01-to-be/34-the-console.md | 4 +- .../01-to-be/37-the-operators-machine.md | 142 ++++++++++++++++++ 03-DESIGN/01-to-be/README.md | 1 + 17 files changed, 676 insertions(+), 5 deletions(-) create mode 100644 02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md create mode 100644 02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md create mode 100644 02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md create mode 100644 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md create mode 100644 02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md create mode 100644 03-DESIGN/01-to-be/37-the-operators-machine.md diff --git a/00-META/glossary.md b/00-META/glossary.md index 0befcf4..e05ed65 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -95,3 +95,21 @@ 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 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. + +## 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. + diff --git a/01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md b/01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md index 7946bf9..9b05c70 100644 --- a/01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md +++ b/01-RESEARCH/018-the-operators-machine-as-modules/00-overview.md @@ -1,5 +1,5 @@ --- -status: active +status: graduated initiated: 2026-10-02 touches: - 02-DECISIONS/0040-what-a-module-is.md @@ -15,7 +15,13 @@ touches: - 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: [] +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 @@ -63,7 +69,7 @@ and the catalogue's shape ([as-is 10](../../03-DESIGN/00-as-is/10-module-catalog - [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 this must settle before it graduates.** +**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. diff --git a/02-DECISIONS/0040-what-a-module-is.md b/02-DECISIONS/0040-what-a-module-is.md index c94825d..84cc967 100644 --- a/02-DECISIONS/0040-what-a-module-is.md +++ b/02-DECISIONS/0040-what-a-module-is.md @@ -78,6 +78,8 @@ 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 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 - **Supersedes the earlier "grouped by domain" decision** (folded in consolidation; see the diff --git a/02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md b/02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md index 496b93c..7201c0a 100644 --- a/02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md +++ b/02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md @@ -9,6 +9,8 @@ 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 +> **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 A module is one self-contained thing ([ADR 0040](0040-what-a-module-is.md)), and it gets a broker diff --git a/02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md b/02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md index 8435390..12202ad 100644 --- a/02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md +++ b/02-DECISIONS/0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md @@ -9,6 +9,8 @@ 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 +> **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 The repository answers "what runs a module's own code" two ways and reconciles them nowhere diff --git a/02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md b/02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md index e9f75bd..eaf059b 100644 --- a/02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md +++ b/02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md @@ -111,6 +111,8 @@ 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 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 - **The way a person drives the mesh is inside the mesh.** It is declared, delivered, replaced and diff --git a/02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md b/02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md new file mode 100644 index 0000000..29863f3 --- /dev/null +++ b/02-DECISIONS/0173-the-operators-machine-is-the-meshs-and-a-module-is-what-it-declares.md @@ -0,0 +1,108 @@ +--- +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. diff --git a/02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md b/02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md new file mode 100644 index 0000000..89628c8 --- /dev/null +++ b/02-DECISIONS/0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md @@ -0,0 +1,92 @@ +--- +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) diff --git a/02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md b/02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md new file mode 100644 index 0000000..d32c9fc --- /dev/null +++ b/02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md @@ -0,0 +1,122 @@ +--- +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@` 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) diff --git a/02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md b/02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md new file mode 100644 index 0000000..da3d9e6 --- /dev/null +++ b/02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md @@ -0,0 +1,81 @@ +--- +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) diff --git a/02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md b/02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md new file mode 100644 index 0000000..3bb55e9 --- /dev/null +++ b/02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md @@ -0,0 +1,80 @@ +--- +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@` 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) diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 4b3841a..94ad8ef 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -272,6 +272,10 @@ python3 00-META/checks/index.py fail if stale - **0150** — [A module's own code runs as supervised processes under the module's one account](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md) - **0152** — [The operator's surface is a module the mesh assigns: the console](0152-the-operators-surface-is-a-module-the-console.md) - **0155** — [A definition names no installation: how that is checked, and the three ways a value that did gets out](0155-a-definition-names-no-installation-and-how-that-is-checked.md) +- **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) +- **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) +- **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) +- **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 @@ -293,6 +297,7 @@ 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) - **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) +- **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 diff --git a/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md b/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md index 989eeed..f8672c1 100644 --- a/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md +++ b/03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md @@ -5,7 +5,7 @@ code: - mesh-controller internal/inventory - mesh-controller internal/catalogue - mesh-controller cmd/mesh-controller -updated: 2026-10-01 +updated: 2026-10-02 decisions: - 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 @@ -205,6 +205,10 @@ 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 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 - The gap was found generating `~/.ssh/config` from the *HAL* registry (`hal/terminal`'s diff --git a/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md b/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md index c2926b6..3cdfcc9 100644 --- a/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md +++ b/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md @@ -122,6 +122,8 @@ 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 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 A seat's tools are an interface and change like one. Additive within a version. A change that would diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index c17e6ce..6da15e4 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -2,7 +2,7 @@ layer: to-be status: implemented code: [mesh-catalog, mesh-tools, mesh-controller] -updated: 2026-10-01 +updated: 2026-10-02 decisions: - 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 @@ -20,6 +20,8 @@ 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 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 A module, `mesh-console`, in the catalogue. Its image is the tool runtime's own — the client that diff --git a/03-DESIGN/01-to-be/37-the-operators-machine.md b/03-DESIGN/01-to-be/37-the-operators-machine.md new file mode 100644 index 0000000..904a2ae --- /dev/null +++ b/03-DESIGN/01-to-be/37-the-operators-machine.md @@ -0,0 +1,142 @@ +--- +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. diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index d9afdda..7647a51 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -41,6 +41,7 @@ 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) | | [`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) | ## Not yet written -- 2.54.0 From 73047501f6972f9c43f87261817d605292dcdd45 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 2 Oct 2026 16:53:15 +0200 Subject: [PATCH 2/2] ADR 0154: the controller seat gains a generic command verb (dated note, by ADR 0175) --- ...4-the-meshs-own-verbs-are-the-controller-seats-tools.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md b/02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md index 5baaebf..6260251 100644 --- a/02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md +++ b/02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md @@ -94,6 +94,13 @@ itself restarts, and the console says so rather than hiding the modules' tools w **A grant of `*` reaches a role's tools; `seat:.` grants one.** The console's `*` needed no 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 - **The console answers the mesh's own questions.** Issue 147's first paragraph closes: what a node -- 2.54.0