Design 33 in progress against ADR 0154 (the twelve verbs, the prerequisites built); design 35 for the records module under ADR 0153, extending 0025; as-is 07 rewritten to a mesh that keeps no store; as-is 12 and 13 updated; issue 006 built and waiting on its live check.
134 lines
8.8 KiB
Markdown
134 lines
8.8 KiB
Markdown
---
|
|
topic: the mesh
|
|
status: accepted
|
|
date: 2026-09-30
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md
|
|
---
|
|
|
|
# 154. The mesh's own verbs are the mesh-controller seat's tools, and which verbs those are
|
|
|
|
## Context
|
|
|
|
[ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md) decided that a seat's protocol
|
|
carries its tools in full, that holding a seat means serving them, and that the mesh's own verbs are
|
|
the `mesh-controller` seat's. It named three prerequisites, none in place: the protocol in the store
|
|
rather than in compiled defaults; a protocol richer than a list of verbs; a node-scoped seat's subject
|
|
carrying the node. And it left one thing to a decision per seat: **which verbs each seat serves**,
|
|
because a seat's tools bind every future holder.
|
|
|
|
The console shipped the same day ([ADR 0152](0152-the-operators-surface-is-a-module-the-console.md))
|
|
and made the gap visible from the operator's chair: a person on a workstation could call every tool a
|
|
*module* serves and none of the mesh's own. What a node runs, what is assigned, whether a push
|
|
applied — the questions issue 147 opened with — still meant a shell on the control node. The console's
|
|
own handshake said so.
|
|
|
|
The control plane already answers every one of those questions, as commands: `status --json`,
|
|
`node show`, `plan --json`, `assign`, `push`. [ADR 0035](0035-one-implementation-several-surfaces.md)
|
|
says a surface is an adapter over those with no decisions in it, and the `api` verb proves the shape:
|
|
every route calls the function the command line calls.
|
|
|
|
## Considered Options
|
|
|
|
**1. Leave the mesh's verbs to the shell until an identity provider authenticates the HTTP API.**
|
|
Rejected. The authenticated network surface is for a browser on another machine; the console is
|
|
already behind the machine's login (0152), and the bus already carries every other tool call under an
|
|
account whose permission list says what it may ask. Waiting would keep the one surface the mesh has
|
|
from answering the mesh's own questions, for a reason that does not apply to it.
|
|
|
|
**2. Serve the verbs as the mesh-controller *module's* tools, `mesh.mod.mesh-controller.tool.<verb>`.**
|
|
Rejected; 0132 rejected it already. The controller holds a seat, and the verbs must keep their address
|
|
while the control plane is being replaced, which is the moment they are most needed. A module's name
|
|
would change with the implementation; the seat's does not.
|
|
|
|
**3. Call each command's function inside the serving process.** Rejected on two facts: the commands
|
|
print, to the process's standard output, and two calls answered at once would read each other's
|
|
words; and each command opens and closes its own stores, which the serving process holds open. Making
|
|
every command return a value is the larger refactor, and it would give the tools a second code path to
|
|
keep in step with the command line — the thing ADR 0035 forbids.
|
|
|
|
**4. The holder of the seat runs the command it names, in its own binary, and answers what it
|
|
printed.** Chosen.
|
|
|
|
## Decision
|
|
|
|
**The `mesh-controller` seat serves twelve verbs**, and these are its interface, additive within a
|
|
version ([33 — The tools the mesh answers](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) §7):
|
|
|
|
| verb | answers with | takes |
|
|
|---|---|---|
|
|
| `tools` | every seat's tools, from the mesh's records | nothing |
|
|
| `status` | what is wrong, quiet, behind, waiting — `status --json` | nothing |
|
|
| `nodes` | every machine and its mode | nothing |
|
|
| `node` | what one machine reported, what it is assigned, why | `node` |
|
|
| `modules` | every module, its version, commit and machines | nothing |
|
|
| `seats` | every seat, what it delivers, who holds it — `seats --json` | nothing |
|
|
| `builds` | what was built lately and what came of it | `module` (optional) |
|
|
| `plan` | the declaration a machine would be sent — `plan --json` | `node` |
|
|
| `assign`, `unassign` | the mesh's own words, refusal included | `node`, `module` |
|
|
| `push` | that it was sent; `status` says what the machine did | `node` (optional: every machine behind) |
|
|
| `build` | that the build machine was asked; `builds` says what came of it | `repository`, `path`, `ref` |
|
|
|
|
**Each verb runs the command it names, in the controller's own binary, and answers what the command
|
|
printed** — the output, whether it succeeded, and, where the command speaks JSON, the same as data. A
|
|
refusal is the command's refusal in the command's words, because it is the same output. A verb takes
|
|
only the arguments its schema names; nothing reaches a flag the schema did not declare. `push` and
|
|
`build` are sent and not waited for: a call that blocked for a whole apply would time out on every
|
|
machine that takes a minute and say nothing about the others.
|
|
|
|
**The three prerequisites are built.** A seat's protocol is three columns on its row, seeded from the
|
|
compiled defaults where a row had none and additively thereafter, so a verb a release adds joins the
|
|
row and nothing an operator wrote is taken away. A served verb is its name, what it does, and the
|
|
schema of its arguments and answer; a manifest may still write a bare name. A node-scoped seat's tool
|
|
carries the node it is asked of, as the last token of its subject; a mesh-scoped seat's stays flat.
|
|
|
|
**Holding a mesh seat requires serving its verbs**, judged where the store's set is loaded, and the
|
|
refusal names the missing verbs. The controller's own manifest lists the twelve under `tools`.
|
|
|
|
**Discovery reads the records, through the seat.** `tools` is one of the twelve because the console
|
|
cannot read the store and should not: the mesh answers for its own records through the role that owns
|
|
them, and the answer is true while any *other* holder restarts. It is not true while the control plane
|
|
itself restarts, and the console says so rather than hiding the modules' tools with it.
|
|
|
|
**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.
|
|
|
|
## Consequences
|
|
|
|
- **The console answers the mesh's own questions.** Issue 147's first paragraph closes: what a node
|
|
runs, what is assigned, whether a push applied, from the machine the person sits at, over the bus,
|
|
under an account whose permission list says so.
|
|
- **Whoever may call `mesh-controller.push` may change the mesh.** That is the console's `*` on a
|
|
machine whose login owns the mesh (0152), and a person's account only if `operator issue` says so.
|
|
A grant reviewer reads `*` and `seat:mesh-controller.` with the same care.
|
|
- **A verb here binds every future controller.** Twelve is deliberate: what an operator asks weekly,
|
|
and nothing that is still finding its shape (`take`, `converge`, `settings`, `secret` stay commands).
|
|
- **A command's text is the answer**, and text changes. The three verbs that speak JSON carry it as
|
|
data; the rest are read by a person or an agent, not parsed. Anything that needs a shape asks for
|
|
`--json` to be added to the command first, which is the right order.
|
|
- **What got harder:** the mesh-controller seat's row now carries a protocol an operator could edit, and
|
|
a verb removed from the row is a verb the controller stops serving without a build. That is
|
|
ADR 0122's arrangement applied to tools, and `seats` shows the row.
|
|
|
|
## How this is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| Every declared verb is one the binary can run, with the arguments its schema names | a test walks the table and derives a command line for each |
|
|
| A verb missing a required argument is refused in its own words, before anything runs | a test per shape |
|
|
| A holder that does not serve a mesh seat's verbs cannot hold it, and the refusal names them | a catalogue test against a seat with two verbs and a holder with one |
|
|
| A node-scoped seat's tool carries the node; a mesh seat's does not | the bus composition test: two nodes derive two addresses |
|
|
| The controller subscribes its seat's tools and may answer | the composition test, and the golden user list |
|
|
| `*` reaches a role's tools; `seat:` grants one and refuses a name with no verb | the composition test |
|
|
| The protocol is seeded into the row and widened additively | the store-backed seat test |
|
|
| Live: the console lists `mesh-controller.status` and a call answers what `status --json` prints | the rollout of this record |
|
|
|
|
## References
|
|
|
|
- [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md) — extended: the prerequisites built, the verbs decided
|
|
- [ADR 0152](0152-the-operators-surface-is-a-module-the-console.md) — the surface that lists them
|
|
- [ADR 0035](0035-one-implementation-several-surfaces.md) — a surface is an adapter with no decisions in it
|
|
- [ADR 0122](0122-a-seat-is-data-a-rename-is-a-database-update.md) — the row is the mesh's, and now carries the protocol
|
|
- [33 — The tools the mesh answers](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md) — the design this completes
|