Compare commits

..
Author SHA1 Message Date
mesh-admin dcb5d5ce38 Merge pull request 'To-be 45: the bus module says every user's slow consumer, and S9 raises it (issue 270, ADR 0357)' (#352) from design/270-the-bus-module-says-every-users-slow-consumer into main 2026-10-14 07:00:14 +00:00
jochen bb8075b37b Give ADR 0357 a home in design 45: the bus module says every user's slow consumer, and S9 raises it (issue 270)
mesh/merge-gate head bb8075b37bc1 merged into trunk 40a39c860278 — pass: the change touches no module of the mesh's graph
mesh/repo-check head bb8075b37bc1 merged into trunk 40a39c860278 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-14 08:42:54 +02:00
mesh-admin 40a39c8602 Merge pull request 'To-be 57: building config and provisioning, as 37 packages each an issue (ADR 0348, ADR 0349)' (#350) from design/57-building-config-and-provisioning into main 2026-10-13 23:57:18 +00:00
mesh-adminandClaude Opus 5.5 e5b98e3b0a Design 57: answer the second review of hq#350
mesh/merge-gate head e5b98e3b0a42 merged into trunk fd416cef91bf — pass: the change touches no module of the mesh's graph
mesh/repo-check head e5b98e3b0a42 merged into trunk fd416cef91bf — pass: its merge-check.sh passed
mesh/delivery delivered
The word setting waits for R2, P3 waits for F3, the group's order is declared with after: lines, and a stop's fix is a new merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:53:59 +00:00
mesh-adminandClaude Opus 5.5 2f3e6e6641 Design 57: answer the review of hq#350
mesh/merge-gate head 2f3e6e6641d0 merged into trunk fd416cef91bf — pass: the change touches no module of the mesh's graph
mesh/repo-check head 2f3e6e6641d0 merged into trunk fd416cef91bf — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
Role names instead of node names, the failure rule as ADR 0276 and 0299 have it, readiness and adoption of existing credentials said exactly, and the counts and refusals corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:47:32 +00:00
mesh-adminandClaude Opus 5.5 1405dd39e8 Design 57: ship the packages as one release, rehearsed in the lab
mesh/merge-gate head 1405dd39e8bb merged into trunk fd416cef91bf — pass: the change touches no module of the mesh's graph
mesh/repo-check head 1405dd39e8bb merged into trunk fd416cef91bf — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
The operator chose one delivery over one per package; the packages meet on one branch per repository and reach main as one delivery group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:40:15 +00:00
mesh-adminandClaude Opus 5.5 11294372b7 Design 57: the work of ADR 0348 and ADR 0349 as packages, each an issue, in dependency order
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 11294372b769 merged into trunk fd416cef91bf — pass: the change touches no module of the mesh's graph
mesh/repo-check head 11294372b769 merged into trunk fd416cef91bf — pass: its merge-check.sh passed
So config and provisioning are built in small, provable steps with at most five worked at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:12:23 +00:00
5 changed files with 393 additions and 114 deletions
+26 -100
View File
@@ -69,8 +69,7 @@ is listed under [Homonyms](#homonyms), with the qualified form each domain uses.
**Recorded in** the catalogue, one manifest per module. **Upstream of** every other domain; its words
are a published vocabulary, fixed by the manifest's schema. **Decided by** ADR 0040, 0126, 0174, 0188,
0207, 0236, 0262.
**Uses:** module, seat, config value (Config), provision (Provisioning), data class (Data), health (Health
and repair).
**Uses:** module, seat, provision (Provisioning), data class (Data), health (Health and repair).
- **manifest** — the file a module is defined by (`module.json`): what it is, what it provides and
requires, the seats it claims, the resources it declares, its tools, its data and its health. Say
@@ -84,9 +83,18 @@ and repair).
it guards: the `mesh-controller`, `postgres` and `nats` modules claim the `mesh-controller`,
`mesh-store` and `mesh-broker` seats (ADR 0079,
ADR 0116).
- **setting** — a value a manifest declares and an assignment gives, one of the two ways a node varies
a module (ADR 0174).
What a "flavor" once varied is a setting or a separate module.
*Not:* ~~flavor~~
- **preference** — a setting whose manifest gives it a default, because its value is true of the
software and only tuned per node: a font size, a width
(ADR 0262).
A setting with no default is the operator's own value: a domain, an identity.
*Not:* ~~tunable~~ (hq)
- **kept region** — a marked block in a managed file the mesh writes *into*, where the operator's own
lines survive every send and are given back when the module goes (ADR 0174). The other way a node
varies a module, besides a config value; a **managed** file's marked block is one (Provisioning).
varies a module.
- **bundle** — the artifact a module's own code is built into — its tools, a seat's implementation, a
daemon — in any language the mesh has a toolchain for; never an image. A **tools bundle** speaks MCP to
the tool runner. One module may declare several
@@ -103,55 +111,6 @@ and repair).
(ADR 0236).
The controller's verb `upgrade` shows it.
## Config — the values someone may change
**Purpose.** To hold, check and resolve every value a module reads that someone may change, and to say
where each resolved value came from. **Recorded by** the `mesh-config` module, in its buckets on the bus,
with its store as the history behind them. **Upstream of** Provisioning, which places the values;
downstream of Module and Placement. **Decided by** ADR 0262, 0277, 0348.
**Uses:** module, node, manifest (Module), assignment (Placement), provision (Provisioning), proposal
(Operator and conversation).
- **config value** — anything a module reads that someone may change: a font size, a domain, an address.
A module declares every one it reads under `config` in its manifest, with a type, a description,
validation, whether it is trusted, optionally the predefined key it falls back to, and a default or
`required`; it lands in a template file as `${config:<key>}` or is read by the module's code at runtime.
It resolves from four sources, the most specific winning: the default in the manifest; **mesh config**,
the value of the predefined key it falls back to for the whole mesh; **node config**, that key's value
on one node; and **module-assignment config**, the module's own value on one node. There is no module
value for the whole mesh. *Config* is the concept, the verb (`config`) and the manifest field
(ADR 0348). Mesh config is a source level, the value set for the whole mesh; `mesh-config` is the module
that keeps every level. The controller holds, mirrors and resolves no config value (ADR 0348 §9).
Older records and documents say "setting" for a config value and "settings" for the concept; new writing
says config value and config. The word goes on this entry's *Not:* line once the documents in force that
still use it in running prose are reworded, which is issue 683; until then it is checked by review. The
manifest field and the verb "settings" are kept as aliases until every module has moved (ADR 0348 §1,
issue 630), and are named on the line below with the rest once issue 683 is done.
*Identifier until renamed:* `takes-settings`, `${setting:}` — manifest forms kept as aliases until every
module has moved to their config names (ADR 0348 §1, issue 630).
What a "flavor" once varied is a config value or a separate module.
*Not:* ~~flavor~~
- **predefined key** — a key of the catalogue's `predefined.json`, the fixed list of keys for which mesh
config and node config hold values, each with its type, description, validation and whether it is
trusted, such as `font-size`. A config value may name the predefined key it falls back to, of its own
type; one that names none resolves from its default and the module's own value only. The list is the
catalogue build's: a change to it is validated with every manifest naming a key, and removing a key still
named or still holding a value is refused (ADR 0348).
- **trusted** — said of a config value that decides what runs or who can reach what. At every level it is
set only at the controller's terminal or through `config` with propose, on the operator's Approve; a
module value that falls back to a trusted predefined key is itself trusted (ADR 0277, ADR 0348 §8). Not
the trusted sender of an operator message (see [Homonyms](#homonyms)).
- **preference** — a config value whose manifest gives it a default, because its value is true of the
software and only tuned per node: a font size, a width
(ADR 0262).
A config value with no default is `required`: the operator's own value, such as a domain or an
identity, and while nothing sets it the module's files are not written (ADR 0348).
*Not:* ~~tunable~~ (hq)
- **`mesh-config`** — the module that owns every config value, and not the source level mesh config: it
holds the seat `mesh-config` once for the mesh, validates, takes a trusted value only from the
controller's terminal or the operator's Approve, keeps the history with who set it and why, and resolves
the four sources (ADR 0348).
## Core — the mesh's own machinery
**Purpose.** To keep the controller, the bus, the store and every node's engine running and agreed, so
@@ -219,7 +178,7 @@ every other domain has something to run on. **Recorded by** the controller and t
(ADR 0229).
- **record mirror** — a key-value bucket on the bus that the controller writes from one kind of its
records (nodes, assignments, walks, builds), after each change commits in its store and only
records (nodes, assignments, walks, builds, settings), after each change commits in its store and only
when the value differs, with one event per write. The store stays the record; the self-check compares
the two and raises `mirror-drifted` on any difference
(ADR 0289). Shortened to *mirror* where the
@@ -231,10 +190,9 @@ every other domain has something to run on. **Recorded by** the controller and t
**Purpose.** To decide, send and apply what each node runs, and to say why.
**Recorded by** the controller's `inventory` database. **Upstream of** Provisioning, Connectivity, Data
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261.
**Uses:** node, machine, module, seat, manifest (Module), config value (Config).
**Uses:** node, machine, module, seat, setting (Module), manifest (Module).
- **assignment** — a module put on a node. Its config values on that node (module-assignment config,
Config) are `mesh-config`'s, never part of what the controller records (ADR 0348 §9).
- **assignment** — a module put on a node, with the settings that node gives it.
- **pending assignment** — an assignment of a module whose build is still running, kept, and made by the
controller when that build registers the module, or ended with why when it does not
(ADR 0261).
@@ -269,7 +227,7 @@ and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 02
(ADR 0181).
Not a name a manifest carries.
- **agent account** — the login every coding agent on a node runs as, when the node names one of its own;
stated on the node record at the controller's terminal, never by a verb or a config value. Empty means agents
stated on the node record at the controller's terminal, never by a verb or a setting. Empty means agents
run as the operator account. It cannot become root without a person, and the node-engine judges that on
every look (ADR 0266).
- **account group** — a group of the machine's group database that a module puts an account in, by
@@ -280,21 +238,15 @@ and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 02
## Provisioning — one module serving another
**Purpose.** To resolve which module serves a provision for which consumer, to wire the two, and to
place every value a module needs into its files on its node. **Recorded by** the controller and
`mesh-provisioner`. **Upstream of** Data; downstream of Module, Placement, Config and Identity.
**Decided by** ADR 0027, 0084, 0138, 0225, 0230, 0263, 0349.
**Uses:** module, seat, node-engine (Core), declaration (Placement), config value (Config), credential
and vault (Identity and access).
**Purpose.** To resolve which module serves a provision for which consumer, and to wire the two.
**Recorded by** the controller. **Upstream of** Data; downstream of Module, Placement and Identity.
**Decided by** ADR 0027, 0084, 0138, 0225, 0230, 0263.
**Uses:** module, seat, credential (Identity and access).
- **provision** — a service one module `provides` and others `require`; the mesh resolves a provider
and wires the two with an endpoint and a credential. A provision is a service you offer, a seat is a
role you occupy, and the two meet where a seat delivers a provision: occupying the seat is what makes
a module *the* provider of it.
Every value a module needs is a provision from the domain that owns it: placement from the controller,
config values from `mesh-config`, shared secrets from the vault, a consumer's values from the module
providing it, an item of a collection from its contributor. A module says only what it needs and where
it lands (ADR 0349).
- **provider** and **consumer** — the module that provides a provision, and the module that requires
it. Which of several providers serves a consumer is resolved
(ADR 0084).
@@ -312,28 +264,6 @@ and vault (Identity and access).
node seat `node-mounts`, as a mount unit and an automount unit, mounted on access (ADR 0263).
- **occasional source** — a share on a device that is not a node and comes and goes, such as a USB disk;
its absence is a state, *not present*, never a fault (ADR 0263).
- **mesh-provisioner** — the module that places provisions on the nodes: it holds the seat
`mesh-provisioner` once for the mesh, keeping which domain answers which kind of placeholder, what each
module on each node needs, what is applied where and the items of every collection, and the seat
`node-provisioner` on every node, which its provisioner client serves (ADR 0349).
- **provisioner client** — mesh-provisioner's part on one node. For each module there it reads the
placeholders of its template files, records the needs, watches each owner's values for the node, fills a
template only once every value is there, has the node-engine write the file through a narrow local verb,
makes the program take the change as the file declares (`restart-on`, `reload-on`, `read-on-use`), and
reports `prov.applied`. It runs as a service with a bus account of its own, never in the tool runner
(ADR 0349).
- **collection** — a provision with many providers. The **collector**, a module that takes additions from
others (a shell's snippets, a bar's blocks), requires it; each **contributor** ships its **item** as a
template in its own repository and provides it. The provisioner client fills the collector's template
from the items of the contributors placed on its node, and an item goes when its contributor leaves the
node (ADR 0349).
- **owned** — a placed file that is the mesh's whole: the provisioner writes it back whenever it differs.
It is one of three answers a manifest gives for each file it places (`owner-at-runtime`): **owned**,
**seed** or **managed**. Whichever it is, the file has one writer: the node-engine writes it only on the
provisioner client's word (ADR 0349).
- **seed** — a placed file the provisioner writes once; from then on it is the program's.
- **managed** — a placed file of which only its named keys or a marked block (a kept region, Module) are
the mesh's; the program keeps the rest.
## Identity and access — who may do what
@@ -348,7 +278,7 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
- **secret** — a credential or other value the vault keeps sealed and the node-engine unseals into a
file at apply (ADR 0085).
- **secret family** — a module's own secrets declared as one name ending in `-*`; each member is the prefix
and a name (`smb-password-<source>`, one per source the module's config values name), given by its full name
and a name (`smb-password-<source>`, one per source the module's settings name), given by its full name
and never made by the mesh
(ADR 0283).
- **vault** — the module that owns every secret (`mesh-vault`,
@@ -492,7 +422,7 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
running session, started before, does not have that group yet. Said by the node-engine as the module's
resource of kind `account`; it clears at the first look after a new login (ADR 0252).
- **waiting** — a resource's health when its module's tool check says it waits for the operator: a named own
secret or config value of the module, not given yet, without which a part cannot work. Not a fault: the
secret or setting of the module, not given yet, without which a part cannot work. Not a fault: the
controller checks it and says it as the condition `needs-operator`, naming the act, and the first-node gate
reads it as *waits for a person*
(ADR 0283).
@@ -616,7 +546,7 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272.
published for that asker alone. The only thing an asker acts on (ADR 0259).
- **proposal** — a change an agent, or anyone the bus admits, puts forward through a verb and that takes
effect only on the operator's warrant, bound to the exact thing shown: a shared instruction section (ADR
0274), a trusted config value (ADR 0277, ADR 0348).
0274), a trusted setting's layer (ADR 0277).
A proposal changes nothing by itself; declined, expired or refused, it is discarded.
- **approval mark** — a tool's or verb's declaration in its manifest (`needs-approval`) that each call to it runs
only on the operator's warrant for that one call, with its exact arguments; the tool runner that serves it, or
@@ -633,7 +563,7 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272.
build seat's holder, whose seat name `node-build-agent` is an identifier to rename.
- **instruction section** — one named section of the managed instruction file, which every agent session on the
nodes it is for reads first, as the mesh's word: set at the controller's terminal in the agent module's
config, or **proposed** by an agent and made active on each node only by the operator's warrant, which that node
settings, or **proposed** by an agent and made active on each node only by the operator's warrant, which that node
proves itself (ADR 0274). A hand-over between sessions is one.
- **personal rule** — one short text in the operator's own words about how the operator wants their agent
to work with them, registered for the operator (the **operator scope** of the agent module) and rendered
@@ -648,10 +578,10 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272.
## Media — what a person watches, listens to and reads
**Purpose.** To find, fetch and file copies of the works a person watches, listens to and reads, each in
the library its kind names. **Recorded by** the `media-importer` module's config values and its memory of what
the library its kind names. **Recorded by** the `media-importer` module's settings and its memory of what
it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Decided by** ADR 0295, 0296,
0302.
**Uses:** module, seat, config value (Config).
**Uses:** module, seat, setting (Module).
- **content** — a work a person watches, listens to or reads: a film, an album and its tracks, an
episode, a book, a session of a sport event. It is known by an identity from a catalogue outside the
@@ -662,7 +592,7 @@ it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Dec
- **media release** — one copy of a work an engine can fetch, such as an NZB or a video. Downloaded, it
sits in the downloads folder, not yet filed. ADR 0302 says *release*.
- **library** — one folder of one kind, filed by one importer: the media-importer's, named in its
config, or a servarr app's. Several libraries may hold one kind, as TV Shows and Anime both hold
settings, or a servarr app's. Several libraries may hold one kind, as TV Shows and Anime both hold
series.
- **media file** — a file on disk together with its metadata: its kind, the fields that identify the
work, and what part of the work the file is. It is what the media-importer takes.
@@ -782,10 +712,6 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| | a question someone must answer | **issue** | The record |
| deploy | the per-node part of a delivery plan | **deploy plan** | Change and delivery |
| | a wrapped program's own deploy | its own word | — |
| managed | a placed file of which only named keys or a marked block are the mesh's | **managed** (`owner-at-runtime`) | Provisioning |
| | a file the mesh writes, such as the agent's instruction file or `managed_settings` | the file's own name, or *a file the mesh places* | — |
| trusted | a config value that decides what runs or who can reach what | **trusted** config value | Config |
| | a sender on the operator's own list | **trusted** sender, its message an **operator message** | Operator and conversation |
## How this page is kept
@@ -2,8 +2,9 @@
layer: to-be
status: in-progress
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
updated: 2026-10-12
updated: 2026-10-14
decisions:
- "ADR 0357"
- "ADR 0325"
- "ADR 0323"
- "ADR 0318"
@@ -259,7 +260,7 @@ signal and asserts its condition. `doctor signals` shows, for every row, the age
| S6 | a build asked → its outcome | build seat | each ask | max(20 min, 3 × the p90 of measured builds), 1 h while nothing is measured, *provisional* (the build seat declares no timeout) | `ask-lost` | warning | — |
| S7 | a call running → finished | controller | each call | the verb's bound: push, rotate and command 30 min; assign and unassign 15 min; `doctor` 3 min; others 10 min | `call-hung` | warning | — |
| S8 | a provider's failing word repeated | provider | every 15 min while failing (ADR 0224) | 30 min | `provider-silent` | warning | — |
| S9 | bus advisories: slow consumer, maximum deliveries, permission violation, consumer deleted | bus server's system subjects | any | any occurrence; a message given up on for as long as `DEAD_LETTERS` holds it (ADR 0264) | `slow-consumer`, `max-deliveries`, `refused`, `consumer-lost` — each naming the call, consumer or module in the mesh's words | warning | H3 for `consumer-lost` |
| S9 | bus advisories: slow consumer, maximum deliveries, permission violation, consumer deleted; every user's connection the server closed as a slow consumer or for breaking the bus's rules (ADR 0357) | bus server's advisory subjects; the controller's connection; the bus module's watch of its server (`bus.slow-consumer`, `bus.connection-closed`) | any | any occurrence; a message given up on for as long as `DEAD_LETTERS` holds it (ADR 0264) | `slow-consumer`, `connection-closed` (one per bus user), `max-deliveries`, `refused`, `consumer-lost` — each naming the call, consumer or module in the mesh's words | warning | H3 for `consumer-lost` |
| S10 | the self-check's heartbeat | controller's `doctor` | every run | 2 × its interval, watched **from the second machine** (§5) | `self-check-silent` | urgent | — |
| S11 | tool runner heartbeat | tool runner | its interval | 3 × interval, *provisional* | `tools-silent` | warning | — |
| S12 | the controller lease renewed | controller | every 5 s | 15 s; a holder that lost the lease or was found expired, and a lease bucket found raised again from nothing, said for an hour; a controller serving without the lease, while it does | `lease-lost` | urgent | — |
@@ -274,10 +275,24 @@ signal and asserts its condition. `doctor signals` shows, for every row, the age
The bus advisories (S9) cost one read-only subscription: the server already publishes them. The
controller translates each into a condition naming the thing in the mesh's words, as the refused reply
of issue 265 is translated today. An advisory clears after an hour without another; a deleted consumer
is said only for one the mesh names and expects, and clears when it exists again. Until the bus has a
system account the controller hears a slow consumer and a refused subject for its own connection only
([issue 270](../../04-ISSUES/270-phase-1-watches-signals-that-come-later-and-hears-only-the-controllers-faults/00-report.md),
open question 2).
is said only for one the mesh names and expects, and clears when it exists again.
**Every user's slow consumer, from the bus module (ADR 0357).** The server closes every client it judges
a slow consumer and keeps its user and reason in its record of closed connections; it publishes neither
in the mesh's account. So the `nats` module's tool bundle reads that record on its own machine every 30
seconds and publishes `bus.slow-consumer` for each connection closed as a slow consumer, and
`bus.connection-closed` for each the server closed for something its client did against the bus's
rules or limits — naming the bus user, the connection and the reason, never a credential. A closure by
the client, by the network or by the server's own shutdown is not said: D15 says a user dropped in a
loop. A closure — its user, connection id and opening time — is said at least once: again when publishing
it failed, and again for the last hour when the bundle starts. The controller's watchdogs read the last
hour of both events back from the events stream on every tick, count each closure once, and raise one
condition per bus user and kind, naming the module and its machine; it clears an hour after the newest
closure. A closure is a condition at most about a minute after the server closed it.
**A refused subject is still heard for the controller's own connection only.** The server tells the
client and writes its log, and says it nowhere else — not in its monitoring endpoint and not to a system
account (issue 702, open).
## 4. The self-check: `doctor` (rule 6)
@@ -787,10 +802,10 @@ has a week of entries; the durations are recorded for every machine.
| Repository | Delivers |
|---|---|
| mesh-controller | the condition store, its verbs, history and events (§2); ADR 0224's standing moved into it; watchdogs for S1–S11 and S13 with bounds set from Phase 0's durations (S12 is Phase 2's, S14 Phase 5's); the bus advisories subscribed and translated (S9); `doctor` with D1–D4, D6–D10, DW and its heartbeat (§4; D5 is Phase 2's); `status` led by open conditions; the test generated from the signals table |
| mesh-controller | the condition store, its verbs, history and events (§2); ADR 0224's standing moved into it; watchdogs for S1–S11 and S13 with bounds set from Phase 0's durations (S12 is Phase 2's, S14 Phase 5's); the bus advisories subscribed and translated (S9), and every user's slow consumer and closed connection read back from the bus module's events (ADR 0357); `doctor` with D1–D4, D6–D10, DW and its heartbeat (§4; D5 is Phase 2's); `status` led by open conditions; the test generated from the signals table |
| `mesh-host` | the heartbeat carries its interval; D1's validator published as a package the controller imports |
| mesh-tools | the tool runner's heartbeat (S11) |
| mesh-catalog | the `operator-channel` seat and its holder; the Telegram channel and the desktop notifier contributing to it; `mesh-watcher` on a machine other than the control node (§5) |
| mesh-catalog | the `operator-channel` seat and its holder; the Telegram channel and the desktop notifier contributing to it; `mesh-watcher` on a machine other than the control node (§5); the `nats` module's watch of its server's closed connections, publishing `bus.slow-consumer` and `bus.connection-closed` (S9, ADR 0357) |
| mesh-lab | R9: each signal suppressed in turn |
**Done when:** on a lab mesh, suppressing each signal raises its condition within its bound and sends a
@@ -0,0 +1,343 @@
---
layer: to-be
status: designed
code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog]
updated: 2026-10-14
decisions:
- "ADR 0348"
- "ADR 0349"
- "ADR 0201"
- "ADR 0113"
- "ADR 0158"
---
# 57. Building config and provisioning
**The work of ADR 0348 (config is a domain of its own) and ADR 0349 (provisioning), broken into
packages small enough that each ends at something that can be seen to work, in the order their
dependencies allow.** The two decision records are 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
them. It is the shape [design 38](38-building-the-operators-machine.md) gave the operator's machine.
## How this is built
**Each package is one issue in the issue tracker, and one pull request per repository it touches.** The
issue carries the package's number here in its title, links the packages it waits for with `depends-on`,
and links ADR 0348 or ADR 0349. At most five packages are worked at once, each by its own agent in its
own worktree; a package starts once every package it depends on is merged. Every pull request is
reviewed independently at its head and merged only with its checks green.
**It ships as one release.** The operator's decision of 2026-10-14: one delivery, not one per package.
Each of mesh-controller, `mesh-host`, mesh-sdk, mesh-catalog and hq has the branch
`feat/57-config-and-provisioning`, and every package's pull request merges into that branch, never into
main. When the last package is merged, the whole is rehearsed in the lab: the walk, the moves of data in
their order, and every proof that is run live. Then each repository's branch reaches main in one pull
request, and the five, sharing their branch's name, are one delivery group (ADR 0239 §2), judged as one
future state. The five are merged within one merge window, so they are one batch and one walk (ADR 0276
§1, §2), in an order declared with `after:` lines in each pull request (ADR 0239 §11): mesh-sdk, then
`mesh-host`, then mesh-controller, then mesh-catalog, then hq. The release holds every package but T6, R1 and R2:
the media catalogue and the applications live in repositories of their own and move after it, and only
then can the old forms be refused (R1) and the record say what runs (R2).
**The moves of data run after the delivery, in this order, each a verb that says what it did:**
placement out of the settings layers (F2), the settings layers into mesh-config (K5), the secrets into
the vault, nothing rotated (V3), then the providers one by one (B2, B3). Each is rehearsed in the lab
first, and each is checked by reading back through the module that owns the data before anything
points at it.
**What a release costs.** A proof that is run live (dunst on a workstation, a secret reaching its file, a
database consumer connecting) is first seen in the lab rehearsal, and a fault found then holds the
release, not one package. A walk that fails fails every commit it carries (ADR 0276 §3), so a fault on
the release's first node holds all five; when mesh-delivery or a person stops a member, the others are walked
again without it, leaving out every merge that shares its modules (ADR 0299 §1, §2), which in this
release is most of them; the fix is a new merge into the release's branch, and the group is walked again.
**Nothing breaks on the way.** ADR 0349 clause 17: each file is filled the old way or the new way, never
both, by the form of its manifest, and the old forms stay accepted until the last module has moved.
Inside the release this still holds: the media catalogue and the applications kept in their own
repositories move after it, so the old forms stay accepted until they have (R1).
## What exists already, measured
Counted 2026-10-14 at mesh-controller `1601cb1`, the node-engine (`mesh-host`) `1a26778`, mesh-sdk `a2d2af0`, mesh-catalog
`ebf7f37`.
| Piece | Today | Becomes |
|---|---|---|
| the controller's composer | `catalogue.Resolution.compose` fills every namespace but `${secret:}` in one loop (`declaration.go` ~1000–1085); the namespaces and their fields are tabled in `fillers` | fills placement only, for a module that has moved |
| settings | tables `settings`, `settings_history`; the `settings` verb (`modules.go` 407–760); proposals and asks (`proposals.go`, 975 lines); trusted files; `takes-settings` merging; about 4 100 lines and 2 500 of tests | the `mesh-config` module and its buckets; the verb `config` |
| placement kept as settings | `places`, `accesses` and the kept networks are read from settings layers | facts of an assignment, the controller's own |
| secrets | the controller mints, seals and accepts every secret (`internal/secrets`, `inventory/secrets.go` 1 125 lines; tables `secret`, `module_secret`, `secret_ask`, `operator_key`); the vault (`mesh-vault`, TypeScript) is read-only and never asked | the vault, in Go, holding and minting; handing out on request and reply |
| bindings | grant files and binding files composed by the controller (`declaration.go` 750–818); `${bound:}` 117 uses | each provider answers its consumers; the vault mints the pair's credential |
| contributions | `${contribution:}` (4 collectors), `${shell:}` (3), `${environment:}` (1); 22 contributors | collections, filled by the provisioner client |
| file text | 225 file resources in 88 modules, all inline (about 193 KB); 12 modules keep a second copy in 34 files with a test that the two agree (ADR 0349's context counted eleven; the twelfth is lemurs) | template files under `files/`, packed by the module's build |
| packing a directory | the builder's `archive` artifact packs a tree and publishes it by digest; the node-engine fetches and unpacks it | the same mechanism carries a module's templates |
| the node-engine's file writing | `applyFile`, `applyBlock`, `applyInto`, create-once, drift written back, `Keep` backups, restart-on and reload-on, container reads | the same code, reached also through a narrow local write verb |
| the node-engine's local verbs | one socket (`cli.sock`), peer-checked by uid, forwarding to the controller; no local write | a second, narrow socket for the provisioner client |
| bus identities | one principal per node for the tool runner, one per module with a process of its own | the provisioner client gets one of its own |
| the SDK | KV, watches, asks, caller proof; no sealing helper; the TypeScript `provisioner` is a database-provider harness | a seal box, provider helpers, and readers for config, bound values and secrets |
**Two measurements decide the order.** The builder can already pack a module's directory, and the
controller already fills every placeholder at registration time from text; so template files can ship
first, the controller reading the text back from the packed `files/`, and nothing on a node changes. And
the node-engine's writing code needs no change in kind, only a second way in; so the provisioner client
reuses it rather than writing as root itself.
## The order the work allows
```
package waits for package waits for
F1 — P1 —
F2 — P2 P1
F3 — P3 F3
F4 — P4 P3
T1 — P5 P2, P4, T1, K3
T2 T1 P6 P5
T3 T4 T5 T2 P7 P6
T6 T1 V1 F1
K1 T1 (same file) V2 V1
K2 K1 V3 V2
K3 F3, K2 V4 V3, P5
K4 K3 V5 V3, V4
K5 F2, K4 B1 P5, V5
K6 K1 B2 B1
K7 K5, P6 B3 B2
L1 P6 L2 L1
S1 P6 R1 T5, T6, K6, K7, P7, V4, B3, L2, S1
R2 R1
```
The issue tracker carries these as `depends-on` links (thirteen issues also link what they wait for
through another package), so the packages ready to start are those whose linked packages are all merged
into the release's branches. An issue's "Waits for: … delivered" means merged into the release's
branches: nothing is delivered before the release. The first wave touches different repositories and different files:
**T1, F1, F3, P1 and F4**. K1 waits for T1 because both change `internal/catalogue/manifest.go`; P3
waits for F3 because both change `internal/broker`; F2 joins as soon as a slot is free.
## 0 — Foundations
**F1 (issue 650) — A seal box in the SDK.** *mesh-sdk. Half a day.* The ADR 0206 box (X25519, HKDF-SHA256,
AES-256-GCM) moves from the licence manager into `mesh-sdk/go/seal`, with a TypeScript twin, and the
licence manager uses it. **Proof:** a byte-for-byte test against the licence manager's existing sealed
values; the licence manager's tests pass unchanged.
**F2 (issue 651) — Placement leaves the settings layers.** *mesh-controller. Two days.* `places`, `accesses` and the
kept networks become facts of an assignment, held by the controller beside the assignment, with a
migration moving each existing layer's keys; `Settle` stops letting a setting replace a provider's
served keys. **Proof:** composition of every node before and after the migration is byte-identical;
the settings layers hold no placement key afterwards.
**F3 (issue 652) — The assignments and the modules' config declarations on the bus.** *mesh-controller. Two days.*
The controller keeps the bucket of assignments design 54 decides (which module on which node), and a
bucket of each registered module's declared config values, written when they change, with one event per
write. mesh-config needs both to resolve. **Proof:** a test against a real bus: an assignment and a
registration each change exactly one key and raise one event.
**F4 (issue 653) — The words.** *hq. Half a day.* The glossary gains *config value*, *predefined key*, *provision*
and *provisioner client*, *collection*, and the ownership words *owned*, *seed* and *managed*. The word "setting" goes on *config
value*'s *Not:* line only in R2, once the 67 documents still using it are reworded; until then the
glossary says it is the older word. **Proof:** `words.py` passes.
## 1 — Template files (ADR 0349 clause 2)
**T1 (issue 654) — A file resource may name a template.** *mesh-controller. Three days; the largest package of the
stage.* A file resource takes `"template": "files/<name>"` in place of `content`; the module's build packs
`files/` into an archive artifact of its own (the existing `archive` packing); registration and `Resolve`
read each template's text back from the packed tree into `content`, so composition and the node-engine
are unchanged; `module check` and the merge gate read templates from the repository tree beside the
manifest; a file naming both `template` and `content` is refused. **Proof:** composition tests: a module
with its text in `files/` composes byte-identically to the same module with it inline; the merge gate
judges a change to a template alone.
**T2 (issue 655) — The twelve modules with copies.** *mesh-catalog. A day.* adwaita, clipmenu, dunst, feh,
gnome-keyring, i3, i3status-rust, lemurs, picom, rofi, screen-lock and xdg move to their templates; the
34 inline copies and the `sameAsSource` tests go. **Proof:** each module's composed declaration on its
nodes is unchanged (the merge gate's plan says no file changes).
**T3 (issue 656) — The desktop modules.** *mesh-catalog. A day.* Every remaining module assigned only to
workstations. **Proof:** as T2.
**T4 (issue 657) — The servers' modules.** *mesh-catalog. Two days.* Every remaining module with a build. **Proof:**
as T2.
**T5 (issue 658) — The ten modules without a build.** *mesh-catalog. A day.* ca-trust, de-spiegel, invoicing,
local-model-consumer, matrix, node-env, only-office, sshd, step-ca and supabase gain an archive build for
their templates. **Proof:** as T2; afterwards no manifest in the catalogue carries file text.
**T6 (issue 659) — The media catalogue and the applications.** *mesh-media-catalog and each application's
repository. Two days.* **Proof:** as T2, per repository.
## 2 — Config (ADR 0348)
**K1 (issue 660) — A module declares its config.** *mesh-controller. Two days.* The manifest takes `config` (type,
description, validation, trusted, `predefined`, `default` or `required`), `takes-config` and
`${config:<key>}`, with `settings`, `takes-settings` and `${setting:}` read as aliases; registration
refuses everything ADR 0348's "How it is checked" lists for registration: a value without type or
description; a value with neither a default nor `required`; a default failing its validation; an
object or list not trusted; a predefined key not on the list or of another type (with K2); an untrusted
value falling back to a trusted key; a value neither in a template nor read at runtime; a placeholder
naming an undeclared value; a `reload-on` or `restart-on` naming an undeclared file; a moved module's
mergeable file not naming its keys; a mesh word or `puid`/`pgid` in `takes-config`. **Proof:** the registration refusal tests, one per
rule; every manifest in the catalogue still registers.
**K2 (issue 661) — The list of predefined keys.** *mesh-controller and mesh-catalog. A day.* `predefined.json` at the
catalogue's root, with `font-size` and `agent-managed-settings` first; the build validates it and every
manifest naming a key; the merge check refuses removing a key still named or holding a value; the newest
trunk build's list is published. **Proof:** the merge check refuses a planted removal; an older build's
list does not replace a newer one.
**K3 (issue 662) — The mesh-config module.** *mesh-catalog. Four days.* A Go module holding the seat `mesh-config`:
the buckets `mesh-config_values`, `_resolved`, `_predefined`, `_proposals`; the four sources resolved from
F3's assignments and declarations; the verb `config` (set, clear, list, history, narrowed by module and
node); validation; the hand-act log; `config-changed`; only its account writes its buckets. Terminal-only
for trusted values until K4. **Proof:** its tests resolve each source in turn; a test against a real bus
refuses a put from another account.
**K4 (issue 663) — Trusted values are proposed and approved.** *mesh-catalog. Two days.* mesh-config asks under its
own name, with its own grant and cap of open asks, binds each option to the level, key and value, records
the approver beside the value, and checks the proposal on Approve (ADR 0277's rules, ADR 0348 clause 8).
**Proof:** a proposal test end to end with a stand-in channel; an Approve against a changed value is
refused.
**K5 (issue 664) — The values move, and `settings` forwards.** *mesh-controller and mesh-catalog. Two days.* Each
existing layer moves into mesh-config (a node layer to `module.<node>.<module>`; a whole-mesh layer to
mesh config, a manifest default or each node, as ADR 0348's consequences say; claude-code's managed
settings to `agent-managed-settings`), recorded with why; for a module not yet handed to the provisioner
the controller reads `mesh-config_resolved` where it read its layers (ADR 0349 clause 17's old way,
an exception to ADR 0348 §9 that R1 removes), and `settings` answers by forwarding to `config`. **Proof:** composition of
every node before and after is byte-identical; `settings` and `config` list the same values.
**K6 (issue 665) — The catalogue says config.** *mesh-catalog. A day.* The 15 modules using `${setting:}`, the 9
declaring settings and the 4 with `takes-settings` move to the new names and the full declaration.
**Proof:** the catalogue's check finds no old name in a moved module.
**K7 (issue 666) — The operator's acceptance on dunst.** *live. An hour.* With every level cleared the first
workstation's dunstrc says 10; a mesh font size, then that node's font size, then dunst's own value on
that node each appear in its dunstrc within the call's answer, and the history shows each change with who and why. Needs P6.
## 3 — The provisioner (ADR 0349 clauses 3–5, 11, 13)
**P1 (issue 667) — The node-engine knows who owns a placed file.** *`mesh-host`. A day.* `declaration.File` gains
`owner-at-runtime` (`owned`, `seed`, `managed`), mapped onto today's paths (drift written back,
create-once, `into`), and the declaration gains the list of files the provisioner fills. **Proof:** the
apply tests: each ownership behaves as its old form did.
**P2 (issue 668) — The narrow local write verb.** *`mesh-host`. Two days.* A second unix socket accepts a write only from
the provisioner client's account (`SO_PEERCRED`) and only for a path the declaration names as filled,
writes through `applyFile`, `applyBlock` or `applyInto` with `Keep`, and fires restart-on, reload-on and
container reads as an apply does. **Proof:** tests: an undeclared path and another uid are refused; a
write under `reload-on` reloads its program once.
**P3 (issue 669) — The provisioner client's own bus account.** *mesh-controller. A day.* A principal of its own per
node for `node-provisioner`, granted to read its node's keys in each domain's bucket, publish
`prov.applied.<node>.>` and ask the vault; nothing else. **Proof:** the grants test, and a test against a
real bus refusing a read of another node's keys.
**P4 (issue 670) — The provisioner's server.** *mesh-catalog. Two days; after P3.* A Go module holding `mesh-provisioner` once:
the registry of which domain answers which kind, the buckets `_registry`, `_needs`, `_applied` and
`_collect`. **Proof:** its tests; the buckets listed live.
**P5 (issue 671) — The provisioner's client.** *mesh-catalog. Four days.* A Go service on every node, outside the tool
runner: fetches a module's templates by digest, records needs, watches each owner's keys for its node,
fills only when every value is there, writes through P2, reports `prov.applied`, writes an `owned` file
back on drift, removes a module's files and needs when it leaves, fills from current values when its node
comes back. Its first owners are placement (from the declaration) and config (from
`mesh-config_resolved`). **Proof:** the client's test list in ADR 0349, against a real bus.
**P6 (issue 672) — The controller hands a module to the provisioner, and the gate waits for it.** *mesh-controller.
Two days.* For a module marked moved, the declaration carries placement, the templates' digest and the
filled-files list, and the controller runs no other filling pass for it; the first-node gate counts the
module healthy only after its node's `prov.applied` for the new build (`gateFacts`). dunst moves first.
**Proof:** composition and gate tests; dunst on the first workstation filled by the client, live.
**P7 (issue 673) — The node-engine stops writing placed files from the declaration.** *`mesh-host`. A day.* For a moved
module the engine writes its filled files only through P2, and removes as an orphan only what the verb
placed. **Proof:** the apply tests; no placed file written twice in a day's journal on the
first workstation.
## 4 — Secrets (ADR 0349 clauses 6–11, 16; issue 647)
**V1 (issue 674) — The vault in Go, holding secrets.** *mesh-catalog. Four days.* `mesh-vault` is rewritten in Go:
its own store, a copy of each secret sealed to the operator key, `get` on request and reply sealed to the
asker's key (F1's box; the node-engine's key format is settled here and written down), `secret-changed`
without a value, no secret in any bucket. **Proof:** the vault's test list in ADR 0349.
**V2 (issue 675) — The vault's verbs.** *mesh-catalog and mesh-controller. Three days.* `secret accept`, `secret-ask`
(with ADR 0277 §24–§27's record and bounds in the vault's store), `issued` (ADR 0305, the provider's tool
answering the vault alone) and `rotate` become the vault's; the controller's verbs forward. **Proof:** the
`secret-ask` and issuing provider tests in ADR 0349.
**V3 (issue 676) — The secrets move to the vault.** *mesh-controller and mesh-catalog. Two days.* Every row of
`secret`, `module_secret`, `secret_ask` and the operator key's public half are copied into the vault as they are,
nothing rotated, and read back through the vault before anything points at it. **Proof:** for every
secret, the vault's sealed answer opens to the same value the controller's does.
**V4 (issue 647) — A node's secrets come from the vault.** *mesh-catalog, mesh-controller, `mesh-host`.
Three days.* The client fills `${secret:}` from the vault's `get`; for a moved module the controller seals
nothing; the core's credentials travel sealed by the vault to their node and the node-engine opens them.
**Proof:** a secret rotated in the vault reaches its file without a new declaration, live.
**V5 (issue 677) — The vault mints pair credentials.** *mesh-catalog. Two days.* One value per provider assignment and
provision, remade for all holders when the vault sees a consumer's need recorded or deleted (ADR 0158
§4–§6). A need recorded for a bind the vault already holds from V3 adopts that value and remakes
nothing, so the move itself rotates no credential. **Proof:** the provider test in ADR 0349, with a stand-in provider.
## 5 — Bindings (ADR 0349 clause 12)
**B1 (issue 678) — A provider's helper in the SDK.** *mesh-sdk. Two days.* Go: watch the needs for a kind, write one
entry per consumer, refuse a derived value for a multi-holder consumer, answer from the vault's pair
credential. The TypeScript `provisioner` harness is renamed so the word means one thing. **Proof:** its
tests with a stand-in bus.
**B2 (issue 679) — PostgreSQL answers its consumers.** *mesh-catalog and mesh-controller. Three days.* The first
provider moves; its grant files and binding files are retired. **Proof:** every database consumer on
every node connects after the move with the credential it had, adopted by the vault as V5 says, live.
**B3 (issue 680) — The other providers.** *mesh-catalog. A week, one pull request per provider.* **Proof:** as B2, per
provider; afterwards the controller composes no grant file.
## 6 — Collections (ADR 0349 clause 13)
**L1 (issue 684) — A collection is declared and filled by the client.** *mesh-controller and mesh-catalog. Three days.*
A collector requires a collection; a contributor provides an item from a template; the client fills the
collector's template from `_collect`. `${contribution:}`, `${shell:}` and `${environment:}` read as
collections. **Proof:** zsh's file holds every placed contributor's item and loses one whose contributor
leaves.
**L2 (issue 681) — The collectors and contributors move.** *mesh-catalog. Two days.* zsh first, then the bar, the
display session, the hotkeys, xorg, power and node-env, with their 22 contributors. **Proof:** each
collector's composed file is unchanged at the move.
## 7 — A module's code asks at runtime (ADR 0349 clause 14)
**S1 (issue 685) — The SDK's readers.** *mesh-sdk and mesh-catalog. Two days.* `config`, `bound` and their watches,
answered by the node's client for what the manifest declares. `secrets` refuses until the module runs
under a bus account of its own, which waits on issue 569 and its decision. **Proof:** the SDK's test in
ADR 0349; claude-code reads its managed settings through it.
## 8 — The old forms go (ADR 0349 clause 17)
**R1 (issue 682) — Refused and removed.** *mesh-controller, `mesh-host`, mesh-catalog. Three days.* Once every module of
every catalogue has moved: the catalogue's check refuses inline text, `settings`, `${setting:}` and the
old contribution forms; the controller's filling passes but placement, its settings, secret and binding
code and tables (a new migration) go; the node-engine keeps opening sealed values for the core's files
only; the controller stops reading `mesh-config_resolved`. **Proof:** the controller's test that its
code opens no secret outside genesis and reads no config value; the absence of the
removed code.
**R2 (issue 683) — The record says what runs.** *hq. Two days.* The as-is designs are written, designs 27 and 32 are
amended, the 67 documents that still say "setting" in running prose are reworded so the glossary can
retire the word (F4 left it off the *Not:* line for them), design 49 lists the Config domain, ADR 0348's and 0349's built status is said, and this document's status becomes `implemented`.
## What is deliberately not here
- **Secrets in a module's own code** wait on issue 569 (a module's tools run as the tool runner's
account) and its decision; S1 ships without them.
- **Release separated from delivery** stays issue 644's, and **the bus's replicas** issue 645's.
- **Renaming "walk"** is issue 646's.
## How this list is kept true
Each package's proof that runs in its tests is run when it is merged into the release's branch, and its
line here gains the date and the commit; a proof that runs live is run in the lab rehearsal and again
after the release's delivery. A package whose proof fails is not reworded; the failure is recorded under it and the package
stays open. The issue tracker says which packages are worked now; this document says only the order.
+1
View File
@@ -56,6 +56,7 @@ document is written and this one's status becomes `implemented`.
| [`54-the-controllers-records-on-the-bus.md`](54-the-controllers-records-on-the-bus.md) | **Designed.** The controller keeps five record mirrors on the bus (nodes, assignments, walks, builds, settings): each a bucket written from its store only when the record changed, with one event per write, and audited against the store every self-check run, a difference raised as `mirror-drifted` before the healer H6 repairs it | ADR 0289 |
| [`55-the-knowledge-base.md`](55-the-knowledge-base.md) | **Designed.** The mesh keeps its decision records and research efforts whole in one module, `mesh-knowledge`, on the control-node: number, status, topic, typed links both ways, each decision's clauses with their own status, and the reasoning, with every version; a record is proposed, checked at the write, reviewed by the knowledge base's own judgement and accepted there, with the operator's Approve when it needs it; agents ask what holds in one bounded call | ADR 0290 |
| [`56-the-mail-client-set-up-by-the-mesh.md`](56-the-mail-client-set-up-by-the-mesh.md) | **Designed.** On every node with a display server, the `betterbird` module installs Betterbird from a pinned upstream release mirrored into the artifact store, ships the Owl add-on pinned and installed by Betterbird's enterprise policy, and declares the operator's mail accounts in a trusted setting without credentials; each account signs in once at a person's first use, and one the profile already holds is adopted by its address | ADR 0316, ADR 0317 |
| [`57-building-config-and-provisioning.md`](57-building-config-and-provisioning.md) | **Designed.** The work of ADR 0348 and ADR 0349 as 37 packages, each an issue with its proof: foundations, template files, config in its own module, the provisioner and its client, secrets in the vault, bindings from providers, collections, a module's runtime readers, and removing the old forms; at most five worked at once | ADR 0348, ADR 0349 |
## Not yet written
-6
View File
@@ -1,6 +0,0 @@
# Release 57: config and provisioning
This branch carries the work of design 57 (ADR 0348, ADR 0349) in this repository. Each package
merges here through its own reviewed pull request; the branch reaches main as one pull request, in one
delivery group with the branch of the same name in mesh-controller, mesh-host, mesh-sdk, mesh-catalog
and hq, after a rehearsal in the lab. This file is deleted by the last commit before that pull request.