Design 47: placement is bus state of the seat mesh-placement, changed by a proposal (ADR 0567)

This commit is contained in:
2026-10-28 18:25:19 +01:00
parent 292232dad2
commit aa4c2fe7ef
@@ -4,6 +4,7 @@ status: in-progress
code: [mesh-controller, mesh-catalog modules/mesh-delivery-gate, mesh-delivery, mesh-host, mesh-sdk, mesh-vault, hq 00-META/checks]
updated: 2026-10-28
decisions:
- "ADR 0567"
- "ADR 0565"
- "ADR 0564"
- "ADR 0559"
@@ -131,14 +132,8 @@ source revision is derived from the keys: every **want** equals its module's **f
- **The forge bridge** turns the forge's events into stream messages and is the only part that calls the
forge.
- **The indexer** computes each module's fingerprint at the merge preview and writes the candidates of each
group and the modules of the trunk. At each trunk merge of the catalog it also reads the catalog's
**placement files**, one per node, into its state `placement`: one key `placement.<node>` with that node's
modules, their pins, host ports and bindings, and the source revision. It adds the nodes to `modules.<module>`
and says `placement-changed` only for a node whose key differs (ADR 0549 §1, §2). While a catalog pull request
that changes a placement file is open, the forge bridge carries its placement files at the head and the indexer
keeps, at each head, each node's file that differs from the trunk's as a **placement candidate** in a state of its
own, `placement-candidates`, one key `<group>.<node>`, deleting the group's other keys, and all of them when the
pull request merges or closes; nothing that composes what runs reads it (ADR 0559 §2, §3).
group and the modules of the trunk. It reads no placement: placement is not in any repository, and the forge
bridge carries nothing of placement (ADR 0567).
- **The builder** claims a build by creating its key, once per fingerprint, and orders it as an ask on the stream of the artifact's
language; it makes no artifact's bytes since R4e and chooses no node, because the build modules pull the asks (ADR 0498 §1, ADR 0525 §1, §4). From R4g it
delivers its own module, the build modules and the controller from its keys, and nothing falls back to build-agent, which is removed
@@ -167,21 +162,15 @@ source revision is derived from the keys: every **want** equals its module's **f
the artifact's digest, the source revision and whether the module holds data; the assigner also writes the
node's own want `want.<node>.node`, and never deletes a want (ADR 0408 §4). A move a person must allow is judged
by the node agent, which waits; the assigner raises the ask and on a yes writes the want again naming it
(ADR 0547 §3). It reads which modules run on which node from the indexer's `placement`, on its watch
(ADR 0549 §3); until ADR 0547's third milestone it also reads the controller's assignments, writes no want for
a module on a node that the two do not both name, and raises a condition naming the pair while they differ
(ADR 0549 §5).
(ADR 0547 §3). It reads which modules run on which node from its own state `placement`, held as the seat
`mesh-placement` (ADR 0567); until ADR 0547's third milestone it also reads the controller's assignments, writes no want for
a module on a node that the two do not both name, and raises a condition naming the pair while they differ.
It also runs the **composition check**: whether the machines a change touches still compose with it, read
from the keys the indexer and the builder hold and from the machines, settings and grants it reads, with
mesh-sdk's composition, the one every node agent composes with (ADR 0446, ADR 0547 §2). Its module was made in
R4g with release 5; from release 6 it writes no difference (ADR 0524 §4, ADR 0528, ADR 0539 §8). For a
change that carries placement candidates it composes the change with them laid over the trunk's placement,
counts every node whose placement changes among the machines it touches (ADR 0446 §3, as ADR 0559 §5 changes it), and names each refusal; then it reads each
placed node's answer to the candidate and fails on a module that differs where the change does not intend it (on
a node the trunk does not place yet, as at the seed, no difference is intended), on
a refusal, and on a node that has not answered within 10 minutes, named, so a laptop asleep is never passed;
that node's later answer at the same head asks the check again on its event, as for any part of the mesh not
heard (ADR 0548 §1, ADR 0559 §1, §5). Its own composition stands in for every sealed value, so only a node's answer can say that
R4g with release 5; from release 6 it writes no difference (ADR 0524 §4, ADR 0528, ADR 0539 §8). The check composes
no placement and waits for no node's answer: a pull request changes no placement, since placement is bus state
changed by a proposal (ADR 0567; ADR 0446 §3 read as written). Its own composition stands in for every sealed value, so only a node's answer to a proposal can say that
what the node would run is the same.
- **The node agent**, the node-engine's own process, follows its node's wants, **composes each wanted module for
its own node** with mesh-sdk's composition from the manifest by digest, the module's resolved values from
@@ -230,7 +219,7 @@ there is three milestones, one issuer of each key at every moment (ADR 0547 §11
elsewhere but the bus's user list, which the bus node keeps able to follow until the third milestone so `hand-back` has a list to return to.
3. **Nothing is left with the controller** (issue 774; what remains of issue 1126, the homes of the controller's
other jobs, carries into it by a note on 1126 and is not one of its issues): the vault mints bus accounts, the grants part
writes grants, the try is the checker's own verb, placement is read from the catalog alone (ADR 0549 §5,
writes grants, the try is the checker's own verb, placement is read from the seat `mesh-placement` alone (ADR 0567; ADR 0549 §5,
§6), the vault signs with the mesh's signing key (ADR 0550 §2), and the controller's lease is handed to a holder that is no controller once every other job
it keeps has a home by its own decision (research 072).
@@ -253,7 +242,7 @@ The target (ADR 0547):
```
forge ──forge messages──► forge bridge ──► the bus ◄── done keys ── builder, build modules (build seats)
│ ▲
gate: the merge, the source revision ◄┤ ├── indexer: fingerprints, manifests, placement
gate: the merge, the source revision ◄┤ ├── indexer: fingerprints, manifests
grants part: memberships, assertions,◄┤ ├── assigner: each node's wants, by six rules
the user list, grants │ ├── watchman: presence, pulses, stuck keys
vault: secrets, signing, accounts ◄┤ └── status view: delivered, moving, settled, stuck
@@ -270,7 +259,7 @@ The target (ADR 0547):
- **The controller** is no part of build and delivery (ADR 0547 §1). The jobs it still keeps outside them
(nodes and adoption, identities, secrets and given values, licences, config moves, conditions and facts, ADR
0429 §9) each move by a decision of their own under research 072 before it loses its lease. Two are decided, by
the operator's answers of 2026-10-26: placement lives in the catalog (ADR 0549), and the mesh's signing key in
the operator's answers of 2026-10-26 and 2026-10-28: placement lives on the bus, held by the seat `mesh-placement` (ADR 0567), and the mesh's signing key in
the vault (ADR 0550 §2).
- **The vault** keeps every secret the mesh keeps (ADR 0550 §1): the mesh's signing key, licence grants and
tokens, bus account passwords and module secrets; a holder gets a value only sealed to its own key, or asks the
@@ -693,22 +682,26 @@ backup holder backs up nothing, and the have says so.
- **What a node composes from, and who writes it.** One composer, mesh-sdk's, has three callers: the node agent for
its own node, the assigner's composition check for every node, and the grants part. Each fills the composition's
input from the bus with its own credential and nothing else (ADR 0547 §2, ADR 0549 §3):
- **placement**, the indexer's state `placement`, one key `placement.<node>` read at each catalog merge from
the node's placement file and said by `placement-changed` (ADR 0549 §2);
- **placement**, the state `placement` of the seat `mesh-placement`, one key `placement.<node>`, written by the
assigner's credential alone on an approved proposal and said by `placement-changed` (ADR 0567);
- **the node records**, each in its node's own have key `<node>.node`, written by that node alone and heard as
the key's change (ADR 0555 §3);
- **the manifests**, the indexer's `modules.<module>` and its manifests by digest, or the digest the composing
node's want names where it runs another build; the private network's manifest comes with mesh-sdk;
- **the config values**, mesh-config's resolved state, the composing node's slice alone for a node agent; a module
in the settings form (its manifest declares no config, so mesh-config holds none for it) has its values in the
placement file instead;
placement key instead;
- **its own secrets and credentials**, from the vault's verb `own`, sealed to the asking node's key and answered to
that node's engine alone, and composed again when the vault says `secret-changed` (ADR 0550 §5).
While both sources stand (ADR 0549 §5), mesh-config resolves values for a module either the placement or the
controller's assignments name on a node, and the grants part composes both, holding a seat held once for the mesh
where the placement says.
- **The placement file.** One file per node in the catalog, changed only by a pull request and checked before
it merges by the nodes it places (ADR 0549 §1, ADR 0555, ADR 0559). It holds what a person decides of the node and nothing a machine observes:
- **How placement is changed.** The seat `mesh-placement`, held once for the mesh by the assigner, keeps three
states: `placement`, one key `placement.<node>` per node; `proposals`; and `candidates`. Only the holder's
credential writes them, and the contract (the states' names among it) is mesh-sdk's. No repository holds
placement and none names a node: what differs between nodes is bus state, and a build module's capacity and
build hours are values on each node's layer of mesh-config. A key holds what a person decides of the node and
nothing a machine observes:
- the node's decided facts: its endpoint and site, whether it is the private network's hub, its address on the
private network (a decision written once at enrolment; nothing allocates one at run time), the domain its
routed names are composed under, its operator and agent logins by name with their homes, whether it is
@@ -716,38 +709,61 @@ backup holder backs up nothing, and the have says so.
over), and on the hub the names given to that tunnel's peers. No secret, and keys only as public halves;
- its modules in the order the composition reads them, each with its host ports by the port the software
listens on, its bindings by provision, and its config placement (`places`, `accesses`, `networks`,
`endpoints`, `expose`, `reach`, `ports`) as two layers: the mesh-wide one, repeated in every node's file so a
`endpoints`, `expose`, `reach`, `ports`) as two layers: the mesh-wide one, repeated in every node's key so a
node reads its own key alone, and the node's own, with a mark when the node has a layer that holds no placement,
since an empty layer still carries the module's config values; and, for a module in the settings form only,
the config values a person set in each of those layers (on the day of the seed the media modules of one node);
the config values a person set in each of those layers;
- its pins by provision, and the seats its modules hold.
The catalog's check refuses a placement naming a module it does not hold or a node not enrolled, two modules
of one node on one host port, a pin, binding or holding naming a provider not placed where it says, a seat held
once for the mesh held twice, two nodes with one address and more than one hub, each named in one answer.
- **A node answers a placement change before it merges.** The node agent watches every placement candidate. For
each open group it composes its own node in shadow, from the trunk's placement with the group's candidates laid
over it and its own inputs, the sealed values and vault boxes it already holds among them, and applies nothing.
It compares the result with what it runs, module by module by composition digest, the bus's user-list resource
left out, and writes the answer into its own have key `<node>.node` under `candidates`, by group: the head
answered, the modules the same, each module that differs with both digests, each module added or removed, and
A change is made only by a **proposal** through the seat's verb `propose`, which takes the reason and a list of
changes (place a module, take one off, pin a provider, hold a seat, set a decided field of a node, set a
module's config placement or values); who proposes is the caller as the runtime proves it. The holder lays the
changes over the current keys, names the revision of each, and refuses at once, naming every finding, what
mesh-sdk's placement check refuses: a module the indexer does not hold or a node not enrolled, two modules of one
node on one host port, a pin, binding or holding naming a provider not placed where it says, a seat held once
for the mesh held twice, two nodes with one address and more than one hub. Otherwise it writes the proposal and
one candidate for each node whose key it changes, and the nodes answer. `withdraw` deletes both. Until the lease
write only assignments and pins are proposable, since the controller composes ports, bindings and holdings from
its own and the holder calls its `assign`, `unassign`, `pin` and `unpin` after each approved proposal to keep its
assignments equal; while they disagree with the placement, the assigner withholds the wants of each module that
differs and raises a condition naming it. The state `placement` keeps 64 revisions of each key and the seat's
stream keeps its events, each carrying the proposal whole, the verdict and the warrant, so the history of a node
or a module is answered from the seat's verb `placement` (ADR 0567).
- **A node answers a proposal before it is approved.** The node agent watches the seat's candidates for its node.
For each open proposal that touches it, it composes its own node in shadow, from the current placement with the
candidate laid over it and its own inputs, the sealed values and vault boxes it already holds among them, and
applies nothing. It compares the result with what it runs, module by module by composition digest, the bus's
user-list resource left out, and writes the answer into its own have key `<node>.node` under `candidates`, by
proposal: the modules the same, each module that differs with both digests, each module added or removed, and
each refusal. A module the candidate adds is answered as added, naming the values not yet issued for it, since
its credential and boxes are made only once it is placed; a refusal is a value missing for a module the node
runs. It writes only when the answer changes, removes it when the candidates go, and answers
whether or not the node is placed yet, so before the seed it answers against what it runs from the controller's
runs. It writes only when the answer changes, removes it when the candidate goes, and answers whether or not the
node is placed yet, so before the first placement it answers against what it runs from the controller's
compositions. The candidate's key is the node's event, the answer is a key the node alone writes, and the
composition check hears that key's change; nothing polls. `indexer_placement`, served by the status view, also
shows each open group's candidates and every node's answer, and the indexer's `indexer_placement_reread` reads
placement and the candidates again now, writing only what differs. The contract (the state, its key, the answer,
the composition with a given placement and the comparison) is mesh-sdk's (ADR 0559 §4, §6, §7, §8).
holder hears that key's change; nothing polls. The nodes that must answer are each node whose key the proposal
changes and each node whose composition the holder finds changed with the current inputs on both sides, not every
placed node, so a node gone for good or asleep blocks only a proposal that touches it. The contract (the state,
its key, the answer, the composition with a given placement and the comparison) is mesh-sdk's (ADR 0567).
- **The holder judges the answers and the operator approves.** The holder computes, by node, the modules the
proposal changes and reads every answer. A proposal names no difference of its own, and the verdict fails on no
difference: each difference, and each one the nodes answered that the holder did not compute, is shown by node in
the brief of the ask beside the proposal's reason, and the operator approves what the brief shows. The verdict
fails only when a node that must answer answers a refusal, or has not answered within 10 minutes of the
candidate's write, naming the node; a time-limited key per node that must answer carries that bound, so no part
keeps a timer, and the node's later answer judges again. On a passing verdict the holder raises an authorising ask
under its own name, offered on a channel that proves who answered, bound to the proposal and the digest of its
changes. On the operator's Approve it writes each changed key by compare-and-set at the revision the proposal was
laid over, records the warrant, deletes the proposal's candidates and says `placement-changed` for each node. A
key that moved since is not overwritten: the keys already written stand, and the rest is proposed again over the
new keys, judged and asked again. A declined or expired ask ends the proposal and says `placement-withdrawn`.
- **The node record.** What the machine observes of itself: its tunnel's public key, its capabilities, the
public halves of its sealing and serving keys, the certificate issued for its internal name, its outward links
and networks, and its node-engine's contract. Written at start and when an observed fact changes, only when it
differs (ADR 0555 §3). Whether the hub took its found tunnel over, and which node enrolled under a carried
peer's key, are read from the placement and the records together, never written.
peer's key, are read from the seat's placement and the records together, never written.
- **What a node's account reads to compose.** The grants part's composer gives every node's account these reads,
each read only, and the controller's frozen copy none of them (ADR 0555):
- every node's `<node>.node` of the node-engine's have bucket: the other nodes' records;
- every key of the indexer's `placement`: where modules run and what a person decided of each node;
- every key of the state `placement` of the seat `mesh-placement`: where modules run and what a person decided of each node;
- every key of the indexer's `modules` and `manifests`: the manifests it composes from;
- every key of the builder's `builds`: the manifest of a build its want names when that is not the trunk's;
- mesh-config's `resolved` values of its own node alone, `<node>.>`.
@@ -755,9 +771,9 @@ backup holder backs up nothing, and the have says so.
holds, without these reads, and nodes keep applying while composing their own fails loudly until the next
`take-over`. mesh-sdk's composer test holds the copy, user for user, to every grant of the controller's golden
plus exactly these reads and the bucket-creation advisories of issue 1790.
- **A node composes its own once the catalog places it.** Its agent reads its key `placement.<node>` at start:
- **A node composes its own once the seat places it.** Its agent reads its key `placement.<node>` at start:
with one it composes its own, without one it applies the controller's compositions as before, so nothing changes
on a node before the seeded placement file reaches it; it follows the key, and when it appears the agent begins
on a node before the first placement is written; it follows the key, and when it appears the agent begins
again composing its own, with no restart. A node never applies a stand-in: a composition that stands
in for a value its input lacks (an own secret, a credential, a vault box, the certificate) is refused whole, said,
and the node keeps running what it runs; the bus's user list alone is held out of the apply, since the bus node
@@ -765,14 +781,19 @@ backup holder backs up nothing, and the have says so.
the node composing it keeps what runs until the vault holds it. Likewise a consumer of `model-access` answered by a
licence record, placed before the vault serves licences (ADR 0550 §3, issue 569), is refused by the same guard and
its node keeps what runs; none is placed today.
- **The order a node takes to composing its own.** One delivery group carries every part: the controller's verb
`placement-export`, the node-engine (its record and its own composer), the vault's `own`, the grants part's reads,
mesh-config's and the assigner's reading of placement, and the indexer writing it. Nothing changes on a node by
that delivery alone, since no placement file exists yet: the node records appear, and the grants part's list gives
the nodes their reads. Then the seed: one catalog pull request writes each node's placement file from the
controller's export, and its composition check passes only when every node it places has composed it in shadow
and answered that what it would run is the same (ADR 0559 §1); only then is it merged. The indexer writes `placement`, each node begins composing its own, and the assigner
writes its wants as the build alone.
- **The order a node takes to composing its own.** One delivery group carries every part: the seat `mesh-placement`
on the assigner, the controller's verb `placement-export` (as it is) and the controller's pin only, the node-engine
(its record and its own composer), the vault's `own`, the grants part's reads, and mesh-config's reading of
placement. The grants part's new reads reach the nodes before the node-engine watches the seat's states. Nothing
changes on a node by that delivery alone, since no placement key exists yet: the node records appear, and the
grants part's list gives the nodes their reads. Then the take-over: the seat's verb `adopt` asks the controller's
`placement-export` and proposes its answer whole as the first proposal. Every node composes it in shadow and
answers that what it would run is the same, because a node with no key is judged against what it runs from the
controller's compositions; the operator approves once, and the holder writes every node's key. Before it writes, it
asks the export again and refuses the write if the answer changed. Each node agent then begins composing its own,
and the assigner writes its wants as the build alone. Until the lease write the operator's `hand-back` deletes
every key, the history kept, and every node returns to the controller's compositions; after it, it is answered
as gone (ADR 0567).
- **Composed again on events, never on a timer.** A changed input at the same fingerprint (a setting or given
value, a secret rotated or moved, a credential issued, a provider's contribution, a peer added to the hub) is
composed again when its event is heard, and applied as a revision of what runs (ADR 0547 §2).
@@ -882,9 +903,9 @@ and `delivered` events.
*ADR 0408, ADR 0429 §17, ADR 0539 §4, §12, §13.*
From release 6, module, source revision, build, want and have are one chain, each link answerable from the next
(ADR 0429 §17). Placement is the catalog's, read onto the bus by the indexer (ADR 0549 §1, §2); the controller's
assignments bucket is kept equal to it until ADR 0547's third milestone and is a source of nothing from then
(ADR 0549 §5, §6). The rollouts bucket is kept read-only with its history: no rollout is written after release
(ADR 0429 §17). Placement is bus state of the seat `mesh-placement`, held by the assigner, and no repository holds it (ADR 0567); the controller's
assignments bucket is kept equal to it by the holder until ADR 0547's third milestone and is a source of nothing from then
(ADR 0549 §6). The rollouts bucket is kept read-only with its history: no rollout is written after release
5's last plan (ADR 0539 §12).
| Link | Names | Written by | Bucket |
@@ -892,9 +913,10 @@ assignments bucket is kept equal to it until ADR 0547's third milestone and is a
| build | its module, repository and commit, its reuse key, what it published: an artifact by digest or a package release by name and version | the controller | `mesh-controller_builds` |
| want | the fingerprint a node should run of a module, and the digest of the one composition it allows; the node's own want names its node composition | the assigner | `mesh-delivery-assigner_wants` |
| have | what the node runs of a module: the fingerprint, the composition's digest, the phase and the health | the node agent | `node-engine_have` |
| placement | each node's modules with their pins, host ports and bindings, from the catalog's placement file of that node at each trunk merge of the catalog | the indexer | the indexer's state `placement` |
| placement candidate | while a catalog pull request that changes placement is open: one node's placement file at its head, beside the chain and never composed into what runs (ADR 0559 §3) | the indexer | the indexer's state `placement-candidates` |
| assignment | until ADR 0547's third milestone: the module and node, pinned or not, ended or not, written by `assign` and `unassign` after a merged placement change, so that it equals the catalog (ADR 0549 §5) | the controller | `mesh-controller_assignments` |
| placement | each node's modules with their pins, host ports and bindings, and what a person decided of the node, as one key per node | the assigner, the holder of the seat `mesh-placement`, on the operator's Approve of a proposal (ADR 0567) | the seat's state `placement` |
| placement proposal | while a change is open: its changes, the revision of each key it was laid over, the nodes' answers, the verdict and the ask, beside the chain and never composed into what runs | the assigner | the seat's state `proposals` |
| placement candidate | while a proposal is open: one node's placement as the proposal lays it, which that node composes in shadow and answers | the assigner | the seat's state `candidates` |
| assignment | until ADR 0547's third milestone: the module and node, pinned or not, ended or not, written by `assign` and `unassign` after an approved placement change, so that it equals the placement (ADR 0567) | the controller, called by the holder | `mesh-controller_assignments` |
| rollout | read-only from release 6: each plan of releases 1 to 5, its tiers, the builds it carried and the assignments it changed | nobody from release 6 | `mesh-controller_rollouts` |
Beside the chain, on the bus too: deliveries and groups (the gate's `deliveries` and `groups`, ADR 0239 §37), one
@@ -910,9 +932,9 @@ at its first start of release 6 (ADR 0539 §13).
controller's store holds builds, rollouts, batches or assignments, so no record mirror,
audit or healer exists for them; the nodes, settings, declared-config and predefined-keys mirrors stay as
ADR 0289 and ADR 0356 have them. A module reads a bucket by naming it in `reads`. The assigner, the grants part,
mesh-config and the node agents read placement from the indexer's state, on its watch; from ADR 0547's second
mesh-config and the node agents read placement from the seat `mesh-placement`'s state, on its watch; from ADR 0547's second
milestone no part but the controller reads the assignments bucket, and the assigner until the third
(ADR 0549 §3).
(ADR 0567).
- **No key of the chain is removed.** Each bucket keeps up to 64 earlier revisions of each key as its history. A
value is never cut to a summary and stays under the bus's largest message. The bus's storage is declared as data
protected by a backup, so it is backed up nightly and before a delivery moves the bus.
@@ -1053,9 +1075,9 @@ What the build proves before this design is implemented (research 055's directio
newer fingerprint once its rules hold, a fingerprint nobody wants any more is never applied, and nothing is
released by a person or held in the gate.
12. No table in the controller's store holds builds, deliveries or assignments: builds are the builder's keys,
wants the assigner's, haves the node agents', and placement the catalog's placement files, read onto the bus
by the indexer, with the controller's assignments bucket kept equal to them until ADR 0547's third milestone
(ADR 0549).
wants the assigner's, haves the node agents', and placement the seat `mesh-placement`'s, held by the assigner,
with the controller's assignments bucket kept equal to it by the holder until ADR 0547's third milestone
(ADR 0567).
Tests 1 to 12 are stated as they hold from release 6. Tests 1, 3, 7, 8, 9, 10 and 11 were first proved by
milestones 1 to 5 in the shape of their time, the controller's plans and what it sent (the milestones table keeps
@@ -1146,7 +1168,8 @@ From ADR 0548 and ADR 0551 (issue 568):
(ADR 0395 §1, §3, as ADR 0548 §1 and §5 change them): a running check ends only at a new head, and a trunk
move carries a verdict by its package digests.
- Placement written only by the controller's `assign` and `unassign` (ADR 0539 §12, as ADR 0549 §1 changes it):
from ADR 0547's third milestone its tables and verbs are a source of nothing (ADR 0549 §6).
from ADR 0547's third milestone its tables and verbs are a source of nothing (ADR 0549 §6). The seat
`mesh-placement` is the only writer of placement (ADR 0567).
- A group's order: `after:` lines, the ranked rules and the order of the repositories' names (ADR 0419, which
supersedes ADR 0249).
- Delivery member by member, a stop of a member after a failed one, and the own path's separate batch for a
@@ -1216,8 +1239,8 @@ release reports the controller's code lines against research 063's baseline (acc
| R4g | The last milestone of release 4 and release 5 as one, with one switch-over and no rehearsal (ADR 0524 §1): builds and checks are asks on streams that each build node pulls when it has a free slot, deliveries first, with nothing holding checks and no controller queue (ADR 0525); the assigner is made with its composition check, writes each node's wants compared with what the controller sends and every difference, and the view answers the asks that need no proof of who answered (ADR 0528, ADR 0437 §10); no module's fingerprint takes the builder (ADR 0530); the scripts of the repositories a module is built from, build-agent with its socket and Dockerfile code, the toolchain images and the controller's build code go, and the builder delivers its own module, the build modules and the controller from its keys (ADR 0453 clauses 5, 8, 10; ADR 0524 §5, §7); the ways to deliver without the builder are recorded (ADR 0526); hq's four checks are Go tests (ADR 0527); each repository's script goes (ADR 0529); the word for the composition check's predecessor is struck. Its pull requests are opened only after five readings, the revert path read, not shown on a test merge (ADR 0524 §3) | 4 (from R4g mesh-build-image, rootless, is the only part that builds an image: ADR 0487 §7), 17, 24 (no `merge-check.sh` in a repository a module is built from) | mesh-sdk, mesh-controller (build-agent removed), mesh-delivery, mesh-catalog, mesh-tools, mesh-tools-go, the node-engine (`mesh-host`), mesh-media-catalog, okioki, hq | 1887 (the flow), 1297 (the assigner), 1295 (`merge-check.sh` goes), 979 (closed with the toolchain images), 1296 and 1303 (hq's check, the old word struck), 1299, 1305, 1294, 1318 (the controller's deletions) |
| R6 | Each node applies from its wants the compositions its own node agent composes (ADR 0547 §2; as released it applied the controller's signed ones), a new version reaches the module's canary first and the rest once it is healthy there, a move a person must allow is asked before its want, the view says what is moving, settled, stuck and delivered, and the plans are deleted from the code; switched over by release 5's last plan (ADR 0539) | 18, 19, 20 | mesh-sdk, the node-engine (`mesh-host`: the node agent), mesh-controller (plans deleted; as released also compositions and credentials, which ADR 0547 moves to the node agents, the grants part and the vault), mesh-delivery (assigner, watchman, view), mesh-catalog (the gate, the messenger, the issue tracker, the backup holder), hq | 1942 (the planner puts the node-engine and the controller in one tier for the switch; delivered first), the bus's user list granting what release 6 uses (delivered first, under L1's issue), 1943 (mesh-sdk: the composition's layout and signature, the want's digest, `mesh-hand-compose`, the verb tables without the plan verbs), 1944 (the node agent), 1946 (the gate keeps the merge's states; the messenger and the issue tracker hear the view), 1906 (the assigner's seventh rule and its asks, the watchman's pulses for wants, the view's wants and events), 1130 (hq) |
| L1 | The controller leaves build and delivery, first milestone: the bus node follows the user list alone, the grants part composes in shadow and the vault holds the bus accounts' hashes, so the operator's take-over can make the grants part's list the one loaded (ADR 0547 §8, §11; ADR 0550 §4) | — | mesh-controller, mesh-delivery, mesh-catalog (the vault, the grants part), the node-engine (`mesh-host`), mesh-sdk | 1945 |
| L2 | Nodes compose: each node agent composes its own modules, and no node applies a composition made elsewhere but the bus's user list (ADR 0547 §11) | — | the node-engine (`mesh-host`), mesh-sdk, mesh-controller | 1126 |
| L3 | Nothing is left with the controller: the vault mints bus accounts and signs, the grants part writes grants, the try is the checker's own verb, placement is read from the catalog alone, and the lease passes to a holder that is no controller (ADR 0547 §11, ADR 0549 §5, §6, ADR 0550 §2); the rest of the controller's code lines is the issue of L2 | — | mesh-controller, mesh-catalog (the vault, the grants part), mesh-delivery | 774 |
| L2 | Nodes compose: each node agent composes its own modules, and no node applies a composition made elsewhere but the bus's user list (ADR 0547 §11); where modules run is bus state held by the seat `mesh-placement` on the assigner, changed by a proposal the nodes answer and the operator approves, and no repository names a node or holds what one installation decides (ADR 0567) | — | the node-engine (`mesh-host`), mesh-sdk, mesh-delivery (the assigner as the seat's holder), mesh-catalog, mesh-controller (its pin only) | 1126 |
| L3 | Nothing is left with the controller: the vault mints bus accounts and signs, the grants part writes grants, the try is the checker's own verb, placement is read from the seat `mesh-placement` alone, and the lease passes to a holder that is no controller (ADR 0547 §11, ADR 0549 §5, §6, ADR 0550 §2); the rest of the controller's code lines is the issue of L2 | — | mesh-controller, mesh-catalog (the vault, the grants part), mesh-delivery | 774 |
| M1 | A milestone is a record of the issue tracker: each row of a design's milestone table is a milestone record its issues are linked to by `issue-tracker.link`, an issue to at most one, and pull requests and commits to issues the same way; the knowledge base's `progress` reads the milestones from the tracker, not from this table (ADR 0493 §1 to §4) | — (ADR 0493 §13 in part) | hq, mesh-catalog (mesh-issues, mesh-knowledge) | 1781 |
| L4 | Health leaves the controller: the condition keeper holds the seat `mesh-conditions` and keeps every open condition from the observers' findings, saying today's condition events; the watchman holds `mesh-watchdogs`, its watchdogs pulses, and keeps the dead letters from the lease write; `mesh-self-check` runs the probes and says `checked`; `mesh-healers` heals on events and acts only once the lease names no controller; `status` is the view's derivation; built dark, compared, then switched by the operator's take-over; the bus module gains a tool that removes one named consumer, which the lease write uses (ADR 0564, ADR 0547 §11) | — | mesh-sdk, mesh-catalog (mesh-conditions, mesh-self-check, mesh-healers, the router, mesh-watcher, nats), mesh-delivery (watchman, view, the grants part) | 1952 |
| L5 | Control records leave the controller, and the controller is not changed (ADR 0565): mesh-config keeps each module's declared config in its state `declared` and publishes the predefined keys, which the indexer reads from the forge bridge's messages with each open pull request's config candidate; config switches to that source only on the operator's take-over, refused while `compare` shows a difference; each node agent writes its node's declared packages (the node seat `node-engine`, verb `packages`); the assigner holds the new seat `mesh-wants` with the upgrade policy a person chose and the bus step; the hand-act log (`hand-act`, `hand-acts`, `warranted`) is the stream of the new seat `mesh-hand-acts`, held by a new module in mesh-catalog, which every recorder calls from this delivery with no switch; the checker and the assigner's composition check read the held values of their pull requests from mesh-config's state `kept`; from the lease write a trusted config value is set only on the operator's Approve; `settings`, `move-config` and `move-placement` end with the lease and nothing replaces them. each person's six policy choices are set through `mesh-wants.upgrade` and recorded as hand acts, `upgrade compare` and the assigner's finding `policy-split` show any split, and the lease write waits for `compare` to be empty (ADR 0565 §8). One delivery group, built before the lease write of L3, which waits for the group delivered, the six choices set, mesh-config's `compare` empty and its take-over done, `mesh-wants.upgrade compare` empty, and the readings of ADR 0565 §12 (ADR 0565 §14; ADR 0348 §9, ADR 0502 §2, ADR 0539 §14, §15, ADR 0547 §11) | — | mesh-sdk, mesh-catalog (mesh-config, mesh-hand-acts, pacman, the forge module, claude-code, mesh-knowledge, mesh-conditions, mesh-healers), mesh-delivery (indexer, assigner, checker, grants part, forge bridge), the node-engine (`mesh-host`); mesh-controller is not a member | 1953 |