To-be 38: building the operator's machine as work packages; ADR 0175 collision renumbered to 0180 #296
+3
-1
@@ -7,7 +7,9 @@ reconstructed: false
|
|||||||
extends: 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
extends: 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md
|
||||||
---
|
---
|
||||||
|
|
||||||
# 175. The found front end is uninstalled once a machine is converged
|
# 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
|
## Context
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **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)
|
- **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)
|
- **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)
|
- **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)
|
||||||
- **0175** — [The found front end is uninstalled once a machine is converged](0175-the-found-front-end-is-uninstalled-once-a-machine-is-converged.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
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ 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/0175-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md
|
- 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/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/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
|
||||||
@@ -773,7 +773,7 @@ the host reports as *other* is reached through the packet filter seat's `remove`
|
|||||||
by name on the bus; the seat also serves `rules` and `reload`, and its holder's runtime declares the
|
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.
|
`NET_ADMIN` capability on the machine's network. See design 33.
|
||||||
|
|
||||||
*2026-10-02, [ADR 0175](../../02-DECISIONS/0175-the-found-front-end-is-uninstalled-once-a-machine-is-converged.md):*
|
*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
|
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
|
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.
|
adopted then enables nothing. The rollback path ADR 0100 kept on disk is given up on purpose.
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
@@ -42,6 +42,7 @@ document is written and this one's status becomes `implemented`.
|
|||||||
|
|
||||||
| [`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) |
|
| [`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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user