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>
This commit is contained in:
2026-10-13 23:47:32 +00:00
co-authored by Claude Opus 5.5
parent 1405dd39e8
commit 2f3e6e6641
@@ -33,7 +33,8 @@ Each of mesh-controller, `mesh-host`, mesh-sdk, mesh-catalog and hq has the bran
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 and delivered by one walk (ADR 0276). The release holds every package but T6, R1 and R2:
future state. The five are merged within one merge window, so they are one batch and one walk (ADR 0276
§1, §2), in the group's order: mesh-sdk, `mesh-host`, mesh-controller, mesh-catalog, hq (ADR 0239 §12). 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).
@@ -43,10 +44,12 @@ the vault, nothing rotated (V3), then the providers one by one (B2, B3). Each is
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 g14, 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 failed member of the group stops the walk, which walks the others again without it
(ADR 0299).
**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; the merges walked again without one exist only after a person's
stop, and leave out every merge that shares its modules (ADR 0299 §1, §2), which in this release is most
of them.
**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.
@@ -64,9 +67,10 @@ Counted 2026-10-14 at mesh-controller `1601cb1`, the node-engine (`mesh-host`) `
| 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:}` 116 uses | each provider answers its consumers; the vault mints the pair's credential |
| 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 | template files under `files/`, packed by the module's build |
| 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 |
@@ -102,8 +106,10 @@ L1 P6 L2 L1
S1 P6 R1 T5, T6, K6, K7, P7, V4, B3, L2, S1
R2 R1
```
The issue tracker holds the same links as `depends-on`, so the packages ready to start are those whose
linked packages are all merged into the release's branches. The first wave touches different repositories and different files:
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.
@@ -164,9 +170,12 @@ repository. Two days.* **Proof:** as T2, per repository.
**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 what ADR 0348's check list names (a value without type or description, a default failing its
validation, an untrusted object, an untrusted value falling back to a trusted key, a placeholder naming
an undeclared value, a mesh word in `takes-config`). **Proof:** the registration refusal tests, one per
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
@@ -191,17 +200,18 @@ 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; the controller reads `mesh-config_resolved`
where it read its layers, and `settings` answers by forwarding to `config`. **Proof:** composition of
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 g14's dunstrc says
10; a mesh font size, then g14's node font size, then dunst's own value on g14 each appear in g14's
dunstrc within the call's answer, and the history shows each change with who and why. Needs P6.
**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)
@@ -236,11 +246,12 @@ comes back. Its first owners are placement (from the declaration) and config (fr
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 g14 filled by the client, live.
**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 file written twice in a day's journal on g14.
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)
@@ -255,7 +266,7 @@ answering the vault alone) and `rotate` become the vault's; the controller's ver
`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 export is copied into the vault as it is,
`secret`, `module_secret`, `secret_ask` and the operator key's public half are copied into the vault as it is,
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.
@@ -266,7 +277,8 @@ nothing; the core's credentials travel sealed by the vault to their node and the
**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). **Proof:** the provider test in ADR 0349, with a stand-in provider.
§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)
@@ -277,7 +289,7 @@ 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 (no rotation), live.
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.
@@ -307,11 +319,13 @@ ADR 0349; claude-code reads its managed settings through it.
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. **Proof:** the controller's test that its code opens no secret outside genesis; the absence of the
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. A day.* The as-is designs are written, designs 27 and 32 are
amended, ADR 0348's and 0349's built status is said, and this document's status becomes `implemented`.
**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