Author SHA1 Message Date
jschoubben 94d52534ab Playbooks 07 and 08: name the controller's try too while its verb exists (issue 774) 2026-10-27 23:01:25 +01:00
jschoubben 6808125e1d Playbooks 07 and 08 name the try by the checker's seat: mesh-delivery-checker.try replaces mesh-controller.try (ADR 0547 §10, issue 774) 2026-10-27 22:50:43 +01:00
mesh-tools 32da464794 Merge pull request 'How-we-build: a bus permission is proven under the real permission (ADR 0560); playbook 07 notes the 2026-10-27 failure' (#486) from pb/seed-lessons-2026-10-27 into main 2026-10-27 21:19:59 +00:00
mesh-tools 10bc8d396c Merge pull request 'Design 47: R4d says what it became, three image reworks done and n8n left by ADR 0506' (#487) from design/47-r4d-done into main 2026-10-27 21:16:02 +00:00
jschoubben 8e732e22ae Design 47: R4d is done; record the three reworks, n8n's exit and the issues (ADR 0480, 0487, 0497, 0506)
mesh/delivery merged: source.main moved to novox/hq@10bc8d396ceb
mesh/check pass 8e732e22aeff on 8f8393fa9343 | carried from 2ea3368b76d1 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-27 22:14:06 +01:00
jschoubben 6ef40bbcc7 How-we-build: a bus permission is proven under the real permission (ADR 0560); playbook 07 keeps only the failure note, as the operator asked
mesh/delivery merged: source.main moved to novox/hq@32da46479424
mesh/check pass 6ef40bbcc7d8 on 8f8393fa9343 | carried from 2ea3368b76d1 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-27 20:36:54 +01:00
jschoubben 7ff4c7ce68 Playbook 07: the developer runs the node-permission test locally as a named exception to step 1
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 7ff4c7ce6888 on 420751b214c0 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed
2026-10-27 20:29:11 +01:00
jschoubben 0d1aa93e37 Playbook 07: keep only the node-agent permission lesson; the operator keeps merging before the check by choice (2026-10-27) 2026-10-27 20:28:16 +01:00
jschoubben 1afab9739e Playbook 07: a node-agent change runs under the node's real permissions before review approves, and a pull request whose safety is its check is approved only on its passing verdict (issue 1126, 2026-10-27) 2026-10-27 20:27:41 +01:00
mesh-tools 420751b214 Merge pull request 'Design 47: a placement change is answered by the nodes it places before it merges (ADR 0559, issue 1126)' (#485) from feat/1126-l2b into main 2026-10-27 07:21:07 +00:00
jschoubben 043f8ecf9c Design 47 and the glossary: a placement change is answered by the nodes it places before it merges (ADR 0559, issue 1126)
mesh/delivery merged: source.main moved to novox/hq@420751b214c0
mesh/delivery-group group feat/1126-l2b merged: every member is merged
mesh/check pass 043f8ecf9cfa on 420751b214c0 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed
The seed and every later placement pull request were to be checked as equal to what runs,
which no composition outside the node can show; the nodes now answer a placement candidate.
2026-10-27 02:14:09 +01:00
mesh-tools 132aae8433 Merge pull request 'Design 47 says what a node composes from: placement, its config layers, and the node record (issue 1126)' (#482) from feat/1126-nodes-compose into main 2026-10-26 21:11:12 +00:00
jschoubben 28d84f31bf Merge remote-tracking branch 'origin/main' into feat/1126-nodes-compose
mesh/delivery merged: source.main moved to novox/hq@132aae843383
mesh/delivery-group group feat/1126-nodes-compose merged: every member is merged
mesh/check pass 28d84f31bfcb on 132aae843383 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed in 2ms
2026-10-26 21:56:52 +01:00
mesh-tools 278ca42b3d Merge pull request 'Playbooks 07 and 08: one release, one check, no try, push once after the review (ADR 0548)' (#484) from pb/one-check-per-release into main 2026-10-26 19:55:30 +00:00
jschoubben 1dab64134a Playbooks 07 and 08: the one check is one status, mesh/check; drop the leftover try wording (review fixes)
mesh/delivery merged: source.main moved to novox/hq@278ca42b3df2
mesh/check pass 1dab64134a56 on 132aae843383 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed
2026-10-26 20:52:36 +01:00
jschoubben a0e4c1fe17 Playbooks 07 and 08: one release, one check, no try, push once after the review (ADR 0548)
Build capacity is the mesh's scarcest resource: a try before and after the review plus a check per fix round on an open pull request spent it several times per release.
2026-10-26 20:50:37 +01:00
jschoubben 7c88f297fc Design 47: a settings-form module's values in the placement file, the vault's own, both sources' readers, the switch when placed, and the order of the delivery and the seed (issue 1126)
mesh/delivery-group group feat/1126-nodes-compose checking: 4 of 6 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 7c88f297fc59 on 278ca42b3df2 | novox/hq#482 in group feat/1126-nodes-compose
2026-10-26 19:45:32 +01:00
jschoubben 495d740d5f Design 47: a placed node's want says self and names no composition; a yes names the composition allowed (issue 1126, ADR 0547 §2, §3)
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: ace: novox/mesh-catalog#505 does not merge into the trunk: files in conflict: .gitignore | 495d740d5ffc on d698942b1d56 | unit 1245…
2026-10-26 19:17:48 +01:00
jschoubben df4ff3d039 Design 47: a model-access consumer placed before the vault serves licences is refused by the guard (issue 1126) 2026-10-26 19:09:16 +01:00
mesh-tools d698942b1d Merge pull request 'Design 47's milestone table names each issue once, and design 53 says milestones are records (issue 1781)' (#483) from feat/1781-milestone-records into main 2026-10-26 18:06:44 +00:00
jschoubben a6cc3d2e0b Design 47: a node never applies a stand-in, and composes nothing of its own until the vault seals its secrets to it (issue 1126)
mesh/delivery closed: a newer head of the same pull request
mesh/check checking a6cc3d2e0b68 on 515c405c8939 | novox/hq#482 in group feat/1126-nodes-compose
2026-10-26 18:59:06 +01:00
jschoubben b6572acd72 Design 47: every read a node's account gains to compose, and that a module with an own secret does not move until the vault seals it to the node (issue 1126, ADR 0555)
mesh/delivery-group group feat/1126-nodes-compose checking: 4 of 5 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check checking b6572acd72e5 on 515c405c8939 | novox/hq#482 in group feat/1126-nodes-compose
2026-10-26 18:49:09 +01:00
jschoubben ebc1a84e59 Design 53 §13: the seed closes a milestone whose issues are all done, wontfix or resolved with no live fix included, as the tracker counts it (issue 1781)
mesh/delivery merged: source.main moved to novox/hq@d698942b1d56
mesh/delivery-group group feat/1781-milestone-records merged: every member is merged
mesh/check pass ebc1a84e592e on 132aae843383 | a compiled program is not refused: mesh-build-node reported no programs | unit 39 run, 0 kept, 1 skippe…
2026-10-26 18:40:12 +01:00
jschoubben 5bb109e36e Design 47: the node-record read is the composer copy's one intended grant beyond the controller's, and what a hand-back means for it (issue 1126, ADR 0555)
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 5bb109e36ea7 on 515c405c8939 | novox/hq#482 in group feat/1126-nodes-compose
2026-10-26 18:33:46 +01:00
jschoubben d948774794 Design 47: the node record rides in the node's own have key (issue 1126, ADR 0555 §3) 2026-10-26 18:30:28 +01:00
jschoubben 68f8a8c198 Design 47: the found tunnel is decided at adoption; adoption and enrolled peers are read, not written (issue 1126, ADR 0555) 2026-10-26 18:26:53 +01:00
jschoubben b5b84fafc6 Design 47: config placement, as ADR 0348 words it (issue 1126) 2026-10-26 18:21:31 +01:00
jschoubben 0488bc1801 Design 47 says what a node composes from: placement with the node's decided facts and settings layers, and the node's own record (issue 1126, ADR 0549, ADR 0555) 2026-10-26 18:20:51 +01:00
jschoubben 973182cdf9 Design 53: milestones are records of the tracker, an issue's milestone is its own link, and the seed reads the design's table once (ADR 0493, issue 1781) 2026-10-26 18:00:18 +01:00
jschoubben 97d600650a Design 47: each issue in one milestone row, R4a's range written out, and row M1 for the milestone records, so the tracker's seed takes the table without a guess (issue 1781) 2026-10-26 18:00:18 +01:00
mesh-tools 515c405c89 Merge pull request 'Design 47 names the controller-exit milestones as rows; the glossary and playbook 07 name progress (issue 1947)' (#481) from feat/1947-progress into main 2026-10-26 16:42:26 +00:00
jschoubben a6e27687db Design 47: 1945 is L1's issue alone, and milestone 3 names 774 with 1126's rest carried by its note (issue 1947, review round 1)
mesh/delivery merged: source.main moved to novox/hq@515c405c8939
mesh/delivery-group group feat/1947-progress merged: every member is merged
mesh/check pass a6e27687db42 on 132aae843383 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed in 2ms
An issue belongs to one milestone (ADR 0493 §2); R6 is delivered and named 1945 for parts
L1 now carries.
2026-10-26 17:03:14 +01:00
jschoubben b7dabd8ceb Design 47 gives its running milestones rows; the glossary and playbook 07 name progress (issue 1947)
The three milestones of the controller leaving build and delivery were prose only, so no
reader of the table saw the running work; 1209 and 971 were listed in two milestones
against ADR 0493 §2.
2026-10-26 16:40:12 +01:00
mesh-tools 3a5c4c7c4d Merge pull request 'Designs 47, 24 and 39 and the glossary follow ADR 0548, 0549, 0550, 0551 and ADR 0547's answers' (#480) from amend/0548-0551 into main 2026-10-26 12:34:59 +00:00
jschoubben 250c3ff605 Design 24: the pointer to 'Every secret lives in the vault' says above
mesh/delivery merged: source.main moved to novox/hq@3a5c4c7c4d9c
mesh/check pass 250c3ff605ab on 132aae843383 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed
2026-10-26 13:33:23 +01:00
jschoubben a3f6e967c0 Design 24: the controller hands the vault only the covered accounts' values until the third milestone, and the break-glass recovery is below (review of ADR 0550's amendment) 2026-10-26 13:32:37 +01:00
jschoubben 11b0a450c3 Designs 47, 24 and 39 follow ADR 0548 to 0551: one check per set of heads with carried verdicts and merge turns, placement in the catalogue, every secret in the vault
Design 47 names ADR 0548 (a check runs to its end, a trunk move carries a verdict by its package digests, the hold and the merge turn), ADR 0551 (the hold and turns only while the check gates merges), ADR 0549 (placement files read by the indexer) and ADR 0550 (the vault signs and adopts the loaded hashes), and ADR 0547's answers on placement and signing. Design 24 gains every secret in the vault; design 39 moves grants and tokens into it. The glossary gains carried verdict, merge turn and placement file.
2026-10-26 13:29:30 +01:00
mesh-tools 122cda84b3 Merge pull request 'Playbooks 07 and 08: read the forge branch's head before trusting an agent's push' (#479) from pb/read-the-pushed-head into main 2026-10-26 10:03:59 +00:00
jschoubben be1bbe0a65 Playbooks 07 and 08: read the forge head before trusting a reported push, so a try never runs on a push that did not happen
mesh/delivery merged: source.main moved to novox/hq@122cda84b302
mesh/check pass be1bbe0a652a on 132aae843383 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed
2026-10-26 11:02:44 +01:00
mesh-tools 585551722a Merge pull request 'Design 47 follows ADR 0547: node agents compose, the grants part composes who may say what, the bus node writes the user list, and the controller leaves build and delivery in three milestones (issue 1945)' (#478) from fix/1945-user-list-follows-r into main 2026-10-26 07:51:14 +00:00
jschoubben f1c50d3e01 Design 47: the assigner's acceptance test names its six rules, as ADR 0547 §4 leaves them; the bus node follows the controller's list until the third milestone, for hand-back; placement stays where it is for now (issue 1126)
mesh/delivery merged: source.main moved to novox/hq@585551722a4e
mesh/check pass f1c50d3e018c on 132aae843383 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed in 2ms
2026-10-26 02:59:58 +01:00
jschoubben d70791ddee Design 47: sentences that still named the controller as composer, issuer or reader of grants follow ADR 0547 as amended 2026-10-26 02:58:12 +01:00
jschoubben 5bdeb8db83 Design 47: take-over's test as ADR 0547's insight states it 2026-10-26 02:57:45 +01:00
jschoubben d94f8a05a8 Design 47 follows ADR 0547: node agents compose their own modules, a grants part composes who may say what, the bus node writes the user list alone, and the controller leaves build and delivery in three milestones 2026-10-26 02:57:45 +01:00
mesh-tools fd0d712f7d Merge pull request 'Design 47 and the as-is delivery after release 6: acceptance tests on wants, as-is 04 and 13 as the mesh runs now (issue 1130)' (#477) from fix/1130-after-r6 into main 2026-10-26 00:39:21 +00:00
jschoubben de4c69d476 Name no node in design 47's test 10 proof: hq is public (issue 1130, review round 2)
mesh/delivery merged: source.main moved to novox/hq@fd0d712f7d97
mesh/check pass de4c69d47634 on 132aae843383 | a compiled program is not refused: mesh-build-node reported no programs | unit 39 run, 0 kept, 1 skippe…
2026-10-26 01:28:32 +01:00
jschoubben 9eb963d5f1 Give each restated acceptance test of design 47 its own proof, and add puts, the hand path and the builds bucket to as-is 04 (issue 1130, review round 1)
Tests 18 and 19 do not cover tests 3, 7, 9, 10 and 11 as restated, so each now names the test or live read that proves it, or says it is not yet proved.
2026-10-26 01:27:21 +01:00
jschoubben c255fd071c Describe delivery after release 6: as-is 04 and 13 follow wants, compositions and haves, design 47's acceptance tests restated (issue 1130)
Release 6 is delivered, so the as-is designs still describing the controller's plans were wrong,
and design 47's tests 1 to 12 named batches, plans and the merge window that no longer exist.
2026-10-26 01:21:42 +01:00
12 changed files with 917 additions and 383 deletions
+34 -7
View File
@@ -235,12 +235,24 @@ every other domain has something to run on. **Recorded by** the controller and t
## Placement — what runs on which node
**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, 0411, 0539.
**Recorded by** the controller's `inventory` database; from ADR 0549, by the catalogue's placement files, which
the indexer reads onto the bus. **Upstream of** Provisioning, Connectivity, Data
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261, 0411, 0539, 0549, 0559.
**Uses:** node, machine, module, seat, manifest (Module), config value (Config).
- **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).
- **placement file** — the catalogue's file for one node: the modules placed on it and, for each, its pins, its
host ports and its bindings to the providers it requires. It changes only by a pull request on the catalogue,
and the indexer reads it at each trunk merge into its state `placement`, one key per node, which every reader
of placement watches (ADR 0549 §1 to §3). Until ADR 0547's third milestone the controller's assignments are
kept equal to it.
- **placement candidate** — a placement file of one node as an open catalogue pull request has it at its head,
kept by the indexer in its state `placement-candidates` under `<group>.<node>` while the pull request is open.
Every node composes it in **shadow** (composed and compared, never applied) and answers in its own have whether
what it would run is the same; the composition check passes only when every placed node has answered the same
or differs only where the change intends (ADR 0559). Not the indexer's candidates of a group, which are
fingerprints (Delivery).
- **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).
@@ -365,7 +377,7 @@ and vault (Identity and access).
**Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents
and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault.
**Upstream of** Provisioning, Delivery, and Operator and conversation.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414, 0550.
**Uses:** module, node, seat, tool (Module), operator, consumer and provision (Provisioning), agent and ask (Operator and conversation).
- **credential** — anything that proves an identity: a password, a key, a token. A **pair credential**
@@ -377,7 +389,8 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
and never made by the mesh
(ADR 0283).
- **vault** — the module that owns every secret (`mesh-vault`,
[24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md)).
[24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md)): the mesh's signing key, with which
it signs on request, licence grants and tokens, bus account passwords and module secrets (ADR 0550).
- **identity** — a person, an agent or a module as the mesh knows it: a name, a kind, a role, and whether it
is switched on; a person or an agent also has a login name, the machines it is present on and, for a person,
the login kept on a machine where it differs. Every identity and role is held by the identity seat, in the
@@ -424,7 +437,7 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
back when it fails. The domain is continuous integration and delivery, named **delivery**, and its parts live in
the repository `mesh-delivery` (ADR 0429 §2). **Recorded by** the `mesh-delivery` module (deliveries) and the
controller (the planner, the rollout) until release 6 of ADR 0429; from each release on, by the parts it builds. **Downstream of** Module, Core, Health and repair and Data.
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323, 0401–0408, 0428, 0429, 0444–0447, 0524–0530, 0539.
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323, 0401–0408, 0428, 0429, 0444–0447, 0524–0530, 0539, 0548, 0551.
**Uses:** module, node, declaration and send (Placement), health (Health and repair), person.
- **merge check** — what a pull request carries before it may merge, judging the merge of its head into a
@@ -442,9 +455,19 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
- **kept verdict** — a package's passing verdict at one test level, kept by the checker under the digest of
everything its tests read, written once and read-only; a check takes it instead of running an unchanged
package (ADR 0444).
- **carried verdict** — a delivery's verdict moved to a newer trunk commit, or a newer sibling commit, by its
package digests: the checker merges the head onto the new commit and runs only the packages whose digest the
move changed, keeping the composition while no composed fingerprint changed; the verdict names the commits it
was carried from (ADR 0548 §5, §6). A head git refuses onto the new commit is not carried: its next check is
whole (ADR 0548 §7).
- **trunk commit** — the commit of the trunk a merge check's verdict was judged against: the trunk's head when
the build module cloned it. The verdict holds for that commit only, and goes out of date when the trunk moves
(ADR 0323).
the build module cloned it. The verdict holds for that commit only, goes out of date when the trunk moves, and
is then carried to the new commit (ADR 0323, ADR 0548).
- **merge turn** — an approved merge's key on a repository in the gate's `turns` bucket: the deliveries waiting
to merge there in the order of their Approve, and the check each waits for. It holds new checks off that
repository until the merge is done (ADR 0548 §3). Written only while the check gates merges (the gate's
setting `check-gates-merge`); while it does not, no turn is written and an approved merge goes in at once
(ADR 0551 §1).
- **build seat** — a language's node seat, held on a node by that language's build module: `node-build-go`,
`node-build-node` and `node-build-image` (ADR 0525 §2). Its accepted kinds are the **build streams**, and its
verbs `current`, `pause` and `resume` serve that node's pulls. It replaces the seat `node-build-agent`, retired
@@ -915,6 +938,10 @@ Not a domain of the mesh but of the way it is built, listed because its words me
- **design state** — of a design: proposed, designed, in progress or implemented, worked out by the knowledge base
from the issues with a design link to it and whether their deliveries are done; never written by anyone
(ADR 0424).
- **progress** — of a design, a milestone, an issue or a record: how far it has come, each part done, moving,
waiting or stuck with when it last moved, worked out by the knowledge base's verb `progress` from the issue tracker
and the deliveries and never written by anyone; `minimal` says one line per part, `detailed` the tree of a lead's
report (issue 1947).
- **glossary entry** — one entry of the glossary as a record of the knowledge base: its head word or words, its
domain, its meaning, its *Not:* line and its identifier line (ADR 0423). Until the move, an entry of this page.
- **milestone** — a group of a design's issues delivered together, as one delivery group, or one pull request when
+16
View File
@@ -173,6 +173,22 @@ quietly stop being true, and nobody will learn that from a document.
Not test-driven development as a blanket rule — a test written first against undiscovered
behaviour asserts a guess. The obligation is that every decision has a defender.
### A bus permission is proven under the real permission
A change that adds or widens what a mesh process reads or writes on the bus (a subscription, a publish, a stream or
key-value read, a consumer) carries an integration test that runs it on a bus user with exactly the production
permissions of that account (mesh-sdk's composition of them), against a real bus server, and that fails with the
bus's permissions violation when the widest new access is not granted. It runs in the forge's check like every
integration test. A reviewer reading code does not see a grant, and a fake bus grants everything (ADR 0560).
How it is checked: a review that approves such a change whose pull request names no such test is the rule skipped.
### A bus permission under the real permission — how it was earned
On 2026-10-27 a change widened what every node agent watches; two reviews read its code and its tests ran against a
fake bus. Live, every node's agent was refused that read by its grant and stopped composing, and the same day the
grants part's user list dropped nine seats' subscriptions (issues 1126 and 1945).
### Tests run at the lowest level that proves them, within a time budget
Three levels, by how much real system a test uses (ADR 0391):
+1 -1
View File
@@ -60,7 +60,7 @@ A drawn overview of them, the cycle, a feature's steps, the review loop and an i
| [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces |
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery |
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one try, one review, one delivery |
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one review, one check, one delivery |
| [09](09-cleanup.md) | Cleanup | Merged branches, stale pull requests, finished worktrees and folders: read first, delete only what the operator approves, report the rest |
| [10](10-visual-design.md) | Visual design | A page the operator will use is about to be designed or rebuilt: mockups with fixed data, the operator picks, the pick is the reference |
+85 -59
View File
@@ -35,25 +35,34 @@ A small fix (one repository, one issue, about an hour of work) follows
proved nothing, and dropping the field it was for passed every test. A reader of the changed record that the
survey never listed ended a module's conditions at each sync. The reviewer judged the bus tests by reasoning, and
wrongly, because they ran only after the review.
- **A check asked again and again.** Build capacity is the mesh's scarcest resource ("we don't have more
processing power", the operator, 2026-10-26), and a release that ran a try before its review, a try after it
and a check per fix round on an open pull request spent it several times over: every push to an open pull
request asks a new check, of every member for a group. A release runs one check ("every release should only
run 1 check or 1 try", the operator, 2026-10-26; ADR 0548).
- **A node agent's bus reads, proven only by review.** On 2026-10-27 a change widened what every node agent watches
on the bus; two reviews read the code, no test ran it under a node's real permissions, and once it was live every
node's agent was refused that read and stopped composing (issue 1126). How-we-build's rule "A bus permission is
proven under the real permission" (ADR 0560) is what the review now asks for.
- **A module proven only after it reached the machines.** The mail modules were delivered with unit tests
only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.
## The shape
One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**,
opened **once**, after the review. It runs in five steps, and its time is bounded. A milestone has no phases
opened **once**, after the review, and **one check** (ADR 0548). It runs in five steps, and its time is bounded. A milestone has no phases
(the operator, 2026-10-17): these are its steps, and a report names the step it is in:
| Step | What happens | Bound |
|---|---|---|
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes |
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit; then each branch is pushed and tried | the milestone's issues, 30 minutes each, and one try |
| 2. Review | one reviewer reads all of it with the tries' results; reports go both ways; at most 3 rounds | 15 minutes |
| 3. Check | push the reviewed heads; a head the review changed is tried again; then the pull requests are opened and the forge's merge checks run the integration tests again; no further review | at most one more try and one check run |
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit, the integration tests compiled; nothing is pushed | the milestone's issues, 30 minutes each |
| 2. Review | one reviewer reads all of it, the local commits, judging the integration tests by reading them against the code; reports go both ways; at most 3 rounds | 15 minutes |
| 3. Check | push the reviewed heads once and open the pull requests together; the checker's one check runs the integration tests; no further review unless the check fails | one check run |
| 4. Deliver | merge; the source revision moves, each module is built once and wanted on its nodes, its canary first | until the view says the source revision `delivered` |
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, tried before its review, reviewed once,
opened once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, reviewed once,
pushed once, checked once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
merge check, an Approve and a delivery of waiting, so the fewest cuts is the fastest. Nor is it cut for an
order between its parts (the operator, 2026-10-22: "a milestone is a milestone, we shouldn't cut it"): which module
reaches the machines before which is the assigner's rules' (a build and a composition before the want, a provider
@@ -100,7 +109,10 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
clone stays on `main`** (the operator, 2026-10-17): no work is done in it and no branch is checked out
there, so a session that opens in it reads what holds now; fetch and fast-forward it before reading.
3. **Settle every open question** in the feature's issues before any agent starts: on the issue, through
the knowledge base, or by asking the operator. A fixing agent that meets an undecided design question
the knowledge base, or by asking the operator. **Read what is decided before designing** (`knowledge-base.ask`):
a question about delivery, checks or builds is most likely answered in design 47 and its decisions already (the operator,
2026-10-26: "that is simply our delivery plan and milestones, we've already researched it"); propose new research
only for what no record decides. A fixing agent that meets an undecided design question
stops, and its run is spent again.
4. **Write one context file per repository**, `.work/<slug>/<repo>.context.md`. It is what a fixing agent
reads instead of rediscovering the code, and it is reused by every agent and every round:
@@ -112,7 +124,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
- what not to touch, and what lands beside it in other repositories;
- the local facts: the worktree, the branch, the check commands, a temporary directory for tests outside
the home (`TMPDIR`), and the unit tests to run (never a database server: tests that need one are
integration tests, which the try (`mesh-controller.try`) and the checker's merge check run).
integration tests, which the pull request's one check runs).
When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with
file:line, and the lead writes the file from its answers.
5. **Write the findings into the issues** (the operator, 2026-10-17: the better the content of the issues, the
@@ -165,13 +177,12 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
(one line for every key-value bucket key and stream the work creates or starts writing; "none created" if none)
Trying the switch-over on one node first (b):
- <which node goes first, and what is read there before the rest>
- or: the operator chose no try on one node first, on <date>; what that risks: <one sentence>
Integration level before the pull requests (c):
- <the tries that run it, per repository; a group try if issue 1921 has delivered one>
- what no try covers: <the parts of the group that meet only in the merge check>
- or: the operator chose no first trial on one node, on <date>; what that risks: <one sentence>
Integration level (c):
- <the one check that runs it: the group's pull requests, one check each, none before the review>
- what the check does not cover: <the parts of the group that meet only live>
```
A key or stream with no one who deletes it and no time to live is a leak and is settled before work starts. Until
issue 1921 gives a group try, answer (c) with the single-repository tries and say so.
A key or stream with no one who deletes it and no time to live is a leak and is settled before work starts.
9. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
## Step 1 — Develop (one fixing agent per repository, smaller model)
@@ -189,27 +200,27 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
its helper alone, and dropping the field from the request passed every test); `gofmt`, `go vet` and the whole unit suite stay green. **The tests are written from the issue's
acceptance criteria**: one test or more per criterion at the level it names, each test's comment citing
the criterion it proves by its label (`// 1118/AC2`), and no criterion left without its test; an integration criterion is a test
that compiles and waits for the try at this step's end. The integration tests (those that start a store, a bus
or a process) are run first by that try, and again by the checker's merge check.
- **No pull request is opened in this step. When every issue is committed, the lead brings each feature branch up
to the current trunk (fetch by name, then merge), pushes it and tries it** (`mesh-controller.try`, as step 3 item 1 says), so the review reads the integration tests' real
results and judges none by reasoning (2026-10-19: a reviewer reasoned that a warning goes out at once, the
router holds it 30 s, and the try after the review found it). A failed try goes back to the same fixing agent
with its whole log, read as step 3 item 3 says, before the review starts. Never land on a red check: run the
checks, read the result, then land in a separate step.
that compiles and is run by the pull request's one check. The integration tests (those that start a store, a bus
or a process) are compiled with `go vet -tags integration ./...` while developing and run by that check, nowhere
else.
- **Nothing is pushed and no pull request is opened in this step, and there is no try** (ADR 0548; the operator,
2026-10-26: "every release should only run 1 check or 1 try"). When every issue is committed, the lead brings each
feature branch up to the current trunk (fetch by name, then merge) and the review reads the local commits. Never call
`mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`; one is run only when the operator asks for it. A reviewer
or a session that wants the code on the forge may push a branch that has no pull request: it runs nothing. Never land
on a red check: read the result, then land in a separate step.
- **The fixing agent's brief** (the lead gives it with the context file): name your issue in every step and
message ("issue 805: landing in mesh-catalog"); reach the mesh only through the mesh MCP server, never ssh;
never push, open, merge or close a pull request; never write to a store's database by hand, a schema change
never push, open, merge or close a pull request; never call a try; never write to a store's database by hand, a schema change
is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
move another issue; **leave the integration tests to the try while developing** (no `go test -tags integration`,
no store or bus started by hand): compile them with `go vet -tags integration ./...` and leave the running to
the lead's try at the end of this step, where `mesh-controller.try` runs them on the pushed branch. On 2026-10-17 three fixing agents ran them anyway, one for minutes on end, because the
brief only said they wait for the check (the operator). After a failed try or check it
move another issue; **leave the integration tests to the check** (no store or bus started by hand): compile them
with `go vet -tags integration ./...`. A single integration test may be run locally against an embedded bus when
it is the fastest way to find a cause, said in the review report and the pull request. After a failed check it
usually pays to run that repository's whole integration level locally (`go test -tags integration -count=1 ./...` in each
module), by the lead or by the fixing agent on the lead's word, before another try: a verdict names only the first failed
test (issue 1921), and on 2026-10-23 R4g's faults surfaced one per try until a local run listed them all in a minute (a
module), by the lead or by the fixing agent on the lead's word, before the one more push: a verdict names only the first failed
test (issue 1921), and on 2026-10-23 R4g's faults surfaced one per run until a local run listed them all in a minute (a
guideline, the operator). Report back in under 150 words: commits, tests added and that they failed first, the
check results, anything you stopped on.
local results, anything you stopped on.
- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead
settles it, updates the context file, and continues the agent.
- **Two repositories that depend on each other** keep their order in the context files: the reader before the
@@ -222,11 +233,11 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
## Step 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)
1. **The reviewed tree is the tree that was tried**: every feature branch was brought up to the current trunk
before its push and try (step 1). A try and a merge check each run on the merge with the trunk as it is then, so a
1. **The reviewed tree is the tree that is pushed**: every feature branch was brought up to the current trunk
before the review (step 1). The check runs on the merge with the trunk as it is then, so a
trunk that moves later is met by the forge's merge check, not by another merge here. Every verdict names the
trunk it merged onto ("pass <head> on <trunk>"), and the lead reads that trunk against the repository's main
at once: a trunk behind main means the source revision (source.main) lags, and every check and try in that
at once: a trunk behind main means the source revision (source.main) lags, and every check in that
repository is judged on it until it moves, so the operator's Approve will merge nothing. Say it then, not at the
merge (issue 1726: on 2026-10-24 the gate dropped two merges it heard before it held its seat, and a
reviewed, approved pull request in each of the two repositories was refused at its Approve).
@@ -238,7 +249,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
It says which round it is in ("round 1 of at most 3").
3. **The report goes to the same fixing agents** that wrote the code, continued by their ids, so they fix
with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
4. **Every new integration test is read against the code it drives and against its try.** Fixing agents only compile it, and its one run before the review is the try (step 1), which shows whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
4. **Every new integration test is read against the code it drives.** Fixing agents only compile it, and no run precedes the review (step 1), so the reviewer judges it by reading; the check that follows shows whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. A review that finds a fault only by reading is the cheaper run. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
5. **The reviewer reads the answers and the new commits**, and approves or reports again (step 7 says what a round
may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
6. **The reviewer reads every acceptance criterion against its test**: each criterion has a test at its level
@@ -255,8 +266,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
6a. **The reviewer reads the milestone file's three answers** (step 0, item 8) against the change: it lists every bucket
key and stream the diff creates or writes (search the diff for the bucket and stream names) and finds each in the
lifetime answer with a deleter and event, or a time to live; it reads the switch-over answer and checks it was done as
written or that the operator's choice is recorded; it reads the integration-level answer against the tries named
in the pull request. A missing answer, or a key or stream the diff creates that the answer omits, is a change asked.
written or that the operator's choice is recorded; it reads the integration-level answer against the check the pull requests will ask. A missing answer, or a key or stream the diff creates that the answer omits, is a change asked.
7. **A finding outside the issues' done-when and their acceptance criteria is never a change asked, in any round.** More hardening, a
further case, a nicer form: the reviewer opens it with `issue-tracker.open` and names the number in its
report, and the round does not wait for it. What a round asks is only what makes the change wrong against
@@ -278,23 +288,24 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
## Step 3 — Check (no review)
1. **Push the reviewed heads, try again a head the review changed, then open one pull request per repository,
together.** **The integration tests run before the pull requests are opened**, first before the review (step 1)
and again for a head the review changed since its try: `mesh-controller.try` with the
repository and the pushed branch is a check ask of the checker (ADR 0501 §5): the checker runs the repository's tests on the
merge with the trunk's head, at the unit and the integration level, through the build module of the repository's language and by
that language's convention, as the merge check does, and answers with an id to read back. The try is an ask on the language's check
stream (ADR 0525 §1), so it waits behind the asks before it until a build node has a free slot; no script of the repository decides
it (ADR 0445, ADR 0529). A try that fails goes to step 3 item 3 as a failed check does; the pull requests are opened only
when every head's last try passed (the operator, 2026-10-18: three pushes in a row failed their first merge check on
faults a try finds in minutes). While developing, no `go test -tags integration` or store or bus started
by hand on the machine (the operator, 2026-10-18); after a failed try, a local run of the whole level is the guideline
in step 1's brief. Pull requests in two or
1. **Push the reviewed heads once, then open one pull request per repository, together** (ADR 0548; the operator,
2026-10-26: "every release should only run 1 check or 1 try"). **No pull request, draft or not, exists before the
review approves the heads**, and no fix round is pushed to an open pull request: every push to an open pull request
asks a new check, of every member for a group, and the mesh's build capacity is spent on it ("we don't have more
processing power"). The integration tests run once, in the checker's merge check on the merge with the trunk's head, at the unit
and the integration level, through the build module of the repository's language and by that language's convention
(ADR 0445, ADR 0529); the check waits on the language's check stream until a build node has a free slot
(ADR 0525 §1). **No try:** never call `mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`, except when the
operator asks for one. While developing, no `go test -tags integration` run of a whole level, and no store or bus started
by hand on the machine (the operator, 2026-10-18).
**A group is one check.** Before the push, every member pins the group's own mesh-sdk head exactly (the checker refuses an
older commit of the same branch), then all members are pushed and opened together, so the group's check runs once. A head the review replaces
counts for nothing against a running check; say so, and ask no other until the new head is reviewed.
**Before a session trusts an agent's "pushed", it reads the forge branch's head** (`git ls-remote origin refs/heads/<branch>`) and compares it with the commit reviewed; an agent may commit on a detached HEAD and report a push that never happened (2026-10-26: the first check of a reviewed fix failed "not our ref"). The pull request is opened only on that read head. Pull requests in two or
more repositories that share the branch name are one delivery group (ADR 0239 §2).
**A temporary mode that merges by hand** (the lead merges because the normal Approve and merge path is not
running): before it asks the merge, the lead runs the full integration level with nothing kept, a try with tests
`.` for each repository of the group, and names those tries (ids and verdicts) in the pull request's description.
A merge asked without them is a step skipped (research 071).
running): the pull request's one check still runs the integration level, and the lead names its verdict in the
pull request's description. A try with tests `.` is asked only when the operator asks for one, and then named there with its id and verdict (research 071).
Where a session pushes through the forge tool `gitea_commit` (one commit on the forge's current base), the
reviewer first confirms that commit's tree equals the reviewed one. **A large change is pushed by a pusher agent** on the smaller
model: `gitea_commit` patches of about 30 KB each, a file copied from another path sent as a copy (`copy_from`)
@@ -319,17 +330,20 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
controller's unit tests against the catalogue's branch**, because those tests read the catalogue and no check
of the catalogue runs them: on 2026-10-20 mesh-catalog #381 removed the old delivery module, and every
controller check failed afterwards on a test of the catalogue's subjects until issue 1406 removed the name
mesh-issues still listened to. A controller try does not find it, since a check reads the catalogue's main
mesh-issues still listened to. A controller's check does not find it, since a check reads the catalogue's main
(also in a delivery group that holds the catalogue's pull request); the lead runs mesh-controller's unit
suite with `MESH_CHECK_BESIDE` naming a directory that holds the catalogue's branch, as mesh-controller's
`internal/beside` says.
2. **The checker's merge check runs the integration tests again, as the gate** (its one status, `mesh/check`). No
2. **The checker's merge check runs the integration tests once, as the gate** (its one status, `mesh/check`). No
second review: step 2 reviewed this tree.
3. **A check that fails** goes back to the same reviewer, continued by its id, with the failure, for **one more cycle** of step 2 with
the same fixing agents; then push once again. **The failure is read from the check's whole log**, never
the same fixing agents; then push **once** again (the one more push of ADR 0548). The check is **read, never re-asked to see if it passes**: a
check asked again without a new, reviewed head spends the build capacity for nothing. **The failure is read from the check's whole log**, never
reasoned from its summary: `mesh-delivery.show` names the check's build id, and `mesh-controller.builds` with
`log` set to that id prints everything the builder said. A check's summary picks only some failing lines;
on 2026-10-17 it showed a store test's `FAIL` without its cause, and a fix was first guessed from the code.
**A failure the check hides or loses** (a verdict naming only the first failed test, a log not kept) is a fault of the
checking tool: it is fixed there, the same turn (instruction fix-a-bug-met-twice), not asked around.
**A change to the Go build module's recipe is judged by the build module the nodes run**, not by the changed one, so a
test the change adds must pass under either version. **A change that needs the new recipe to judge it ships the recipe
first, as a small fix of its own**, and the rest waits for its delivery: on 2026-10-17 milestone 3 of design 47 moved its
@@ -349,9 +363,9 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
hand (issue 1226).
5. **The operator's Approve is asked as soon as the checks pass** (ADR 0421). Nobody marks, labels, comments or
rechecks: mesh-delivery asks the Approve on Telegram, the channel that proves who answered today (ADR 0398 §3;
an Approve at the terminal is issue 953), once both checks passed, for a delivery group once for all its
an Approve at the terminal is issue 953), once its one check (`mesh/check`) passed, for a delivery group once for all its
members, naming the review record. On the Approve it merges the approved heads, a group's in one pass
(ADR 0418). A new head ends the Approve and is asked again once its checks pass. A watcher follows the checks,
(ADR 0418). A new head ends the Approve and is asked again once its checks pass, which is why a head is pushed only after its review. A watcher follows the checks,
the Approve and the merge, and wakes the lead. Beyond that
Approve, the operator's word is waited for only when the milestone changes what the operator sees, removes
data, or needs a step only the operator's terminal can take; the lead says which before the milestone starts.
@@ -472,7 +486,7 @@ on, with when it started and its bound.
| a fixing agent's issue | 30 minutes | ask it where it stands; split or take over |
| the review | 15 minutes a round | ask the reviewer; stop at round 3 |
| a merge check | 15 minutes | read `mesh-delivery.show` (its state and verdict), then the check's log; the check stream's backlog and the age of its oldest ask are in the delivery view (ADR 0525 §9) |
| the Approve's ask | 5 minutes after both checks pass | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
| the Approve's ask | 5 minutes after the check (`mesh/check`) passes | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
| a merge after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
| a delivery | its class's budget (3 minutes for a leaf module, 10 for a core module), at most 30 minutes | read the view's wants page, or `assigner_wants` against `node_haves`: a want held back names its rule, a `stuck` module its words |
@@ -529,6 +543,13 @@ Allowance: week <n> % (<+d> since the last report), session <n> %
Next: <the next actions up to the delivery, in order>
```
**The tree is read, not assembled.** `knowledge-base.progress` with `of: "design <nn>"` answers the tree's
milestones, issues, pull requests and held groups in one call, each with when it last moved: `detail: minimal` for the
five-minute report, `detailed` about every thirty minutes and whenever something stops (issue 1947). What it cannot
read for certain from the design's milestone table it says in `warnings`; the lead fixes the table, it does not
correct the tree by hand. How it is checked: a report's tree that disagrees with the verb's answer of the same minute
is a tree assembled by hand.
**The short layout** (on a channel such as Telegram, or when nothing changed but time):
```
@@ -619,8 +640,13 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
- A running milestone's file holds the three answers of step 0 item 8 (`cat .work/<slug>/milestone.md`), and the
review report says it read them (step 2, item 6a): a bucket key or stream in the diff (`git diff main --
| grep -i 'bucket\|stream'`) that the lifetime answer omits is the failure of that item (research 071).
- A pull request merged by hand in a temporary mode names, in its description on the forge (`gitea.gitea_get_pull_request`), a try with
tests `.` for each repository of the group; one without is the step 3 rule skipped.
- A pull request merged by hand in a temporary mode names, in its description on the forge (`gitea.gitea_get_pull_request`), the
verdict of its one check; a try with tests `.` is named only where the operator asked for one.
- **One release, one check** (ADR 0548): `gitea.gitea_get_pull_request` shows each pull request created after its
review record's time, and `mesh-delivery.show` shows one check per head and, for a group, one head per member; a pull
request that existed before its review approved it, a second head with no failed check before it, or a check of a
head asked more than once, is this rule skipped. A call of `mesh-controller.try`, `mesh-delivery-checker.try` or `checker_try` that the operator
did not ask for shows in the checker's asks and is the same breach.
- A milestone that adds or changes a module whose pull request has no integration test that starts the module
whole is the failure of the check step's fourth item.
- A bed run from `.work/<slug>/mesh-lab` that fails naming a `.work/<slug>/<repo>/…` path it cannot find is a
+25 -23
View File
@@ -26,14 +26,15 @@ this moment): it goes alone as a small fix, and the lead's report says why.
## The aim
A small fix spends its time on the fix, and runs the long part once: **one try before the review, one review, one check run,
one delivery**. Everything before the try is local and fast.
A small fix spends its time on the fix, and runs the long part once: **one review, one push, one check run,
one delivery** (ADR 0548; the operator, 2026-10-26: "every release should only run 1 check or 1 try"; "we don't have more
processing power"). Everything before the push is local and fast, and no try is run.
## Steps
1. **Read the issue and the code it names** (the session's own model, or one read-only agent on the smaller
model answering exact questions with file:line). If a design question is open, it is settled before any
code is written: on the issue, through the knowledge base, or by the operator. A fix that turns out to
code is written: on the issue, through the knowledge base (`knowledge-base.ask` first: what is decided is read before anything is designed), or by the operator. A fix that turns out to
need a second repository, or more than about an hour, stops here and becomes a feature; one that needs a
decision stays a small fix. **The session's own model (the larger) then writes the issue's acceptance
criteria** as playbook [07](07-feature-branches.md) step 0 item 5 says: each one sentence, given, when, then,
@@ -50,30 +51,30 @@ one delivery**. Everything before the try is local and fast.
caught only by the review); read `git show --stat` before pushing.
The tests are written from the acceptance criteria, at least one per criterion at its level, each citing the
criterion it proves by its label (`<issue>/AC<n>`, playbook 07 step 0 item 5). Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
`go test ./...` at the root). Then push the branch and run `mesh-controller.try` on it, so the review reads the
integration tests' real results; a failed try goes back to the same agent before the review.
4. **One review of the commit and its try** (a reviewer that did not write it, larger
`go test ./...` at the root). The integration tests are compiled (`go vet -tags integration ./...`) and not run:
nothing is pushed, no pull request is opened and no try (`mesh-controller.try`, `mesh-delivery-checker.try`, `mesh-delivery-checker.checker_try`) is called, unless the operator asks for
one. A single integration test may be run locally against an embedded bus when it is the fastest way to find a cause.
4. **One review of the local commit** (a reviewer that did not write it, larger
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; it reads each acceptance criterion
against its test, as playbook 07 step 2 item 6; a finding outside the issue's done-when and its acceptance
criteria is opened as an issue of its own, never asked as a change). The reviewer's findings go back to the same agent that wrote the fix, continued by
criteria is opened as an issue of its own, never asked as a change; it judges the integration tests by reading them against the code, as playbook 07 step 2 item 4 says). The reviewer's findings go back to the same agent that wrote the fix, continued by
its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the
operator's Approve of its merge is its review, and the review record says so.
5. **Push the reviewed head, try it again if the review changed it, and open the pull request.** The trunk is what runs and nothing holds merged work back
(ADR 0401), so a fix waits for no other change's delivery. `mesh-controller.try` on the pushed branch runs
the integration tests, and the pull request is opened only when the head's last try passed; the checker's merge check runs
them again as the gate (playbook [07](07-feature-branches.md) step 3). A failed try or check is best run locally
first: the repository's whole integration level (`go test -tags integration -count=1 ./...` in each module) shows every
failure at once, where a verdict names only the first (issue 1921) and a try per fault costs a round each (a guideline,
the operator, 2026-10-23). The failures go back to the same agent, fixed in one change where they can be, and the
reviewer reads only the change it makes, before the next try.
5. **Push the reviewed head once and open the pull request, only after the review approves.** The trunk is what runs and nothing holds merged work back
(ADR 0401), so a fix waits for no other change's delivery. No pull request, draft or not, exists before the review
approves the head, and no fix round is pushed to an open pull request: every push to an open pull request asks a new
check. The checker's merge check runs the unit and the integration level once, as the gate (playbook [07](07-feature-branches.md) step 3); the pushed head is read from the forge first, as playbook 07 step 3 item 1 says.
A failed check is read from its whole log (never re-asked to see if it passes), then the repository's whole integration level
is run locally (`go test -tags integration -count=1 ./...` in each module) so every failure shows at once, where a verdict
names only the first (issue 1921); the failures go back to the same agent, fixed in one change, the reviewer reads only the change
it makes, and the head is pushed once more.
**A temporary mode that merges by hand** (the lead merges because the normal Approve and merge path is not
running): before it asks the merge, the lead runs the full integration level with nothing kept, `mesh-controller.try`
with tests `.`, and names that try (id and verdict) in the pull request's description (research 071; playbook
running): the one check still runs, and the lead names its verdict in the pull request's description; a try with tests `.` is asked only when the operator asks for one (research 071; playbook
[07](07-feature-branches.md) step 3).
6. **The pull request's description carries the review record** (`Reviewed-by: <who@node> at <full sha>`,
playbook [07](07-feature-branches.md) step 3) and the session that pushed it (`Pushed-by: <who@node>`, ADR 0505 §5), and mesh-delivery asks the operator's Approve as soon as its
checks pass and merges the approved head (ADR 0421); nobody marks, labels, comments or rechecks. A fix to a module that changes how it runs carries an
integration test starting the module whole against the real bus and store, run by `mesh-controller.try` and the checker's merge check
integration test starting the module whole against the real bus and store, run by the checker's one merge check
(ADR 0391 §1, §6); unit tests alone are not that proof, and the lab is not used. The operator's word is waited for only when the fix changes what the operator sees, removes data,
or needs the operator's terminal.
**A fix that depends on another pull request outside its delivery group opens only once that one is delivered**, not merely merged (members of one group open together, as playbook 07 step 3 item 1 says) (a note
@@ -92,7 +93,7 @@ one delivery**. Everything before the try is local and fast.
A fix that stops before its delivery moves its issue back to `open` with a note saying how far it got
(branch, last commit, what is left), as playbook [07](07-feature-branches.md) says.
**An issue opened along the way is fixed too** (the operator, 2026-10-21). A fix's review, try or delivery that
**An issue opened along the way is fixed too** (the operator, 2026-10-21). A fix's review, check or delivery that
opens an issue (a remark outside the done-when, a fault met live) leaves it to the same session to fix: right after
this fix's delivery, as a small fix of its own, or folded into the next one in the same repository. It waits only
when another running milestone owns it, or it needs a decision or the operator first; the report then says on what.
@@ -116,10 +117,9 @@ code.
| Waiting on | Bound | Past the bound |
|---|---|---|
| building the fix | 1 hour | it is a feature: stop and plan it under playbook 07 |
| the try | 30 minutes | a try is a check ask on the language's check stream and waits behind the asks before it for a build node's free slot (ADR 0525); past the bound, read the stream's backlog and the age of its oldest ask in the delivery view |
| the review | 15 minutes | ask the reviewer where it stands |
| the merge check | 15 minutes | read `mesh-delivery.show`, the check's log and the check stream's backlog in the delivery view |
| the Approve's ask | 5 minutes after both checks pass | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
| the merge check | 30 minutes (it waits behind the asks before it for a build node's free slot, ADR 0525) | read `mesh-delivery.show`, the check's log and the check stream's backlog in the delivery view |
| the Approve's ask | 5 minutes after the check (`mesh/check`) passes | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
| the merge after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
| the delivery | its class's budget (3 minutes for a leaf module, 10 for a core module), at most 30 minutes | read the view's wants page, or `assigner_wants` against `node_haves`: a want held back names its rule, a `stuck` module its words |
@@ -134,8 +134,10 @@ reading itself and uses one subagent only for the review.
- A small fix has one pull request with one or few commits naming its issue, opened after its review: a pull
request with many heads is a fix reviewed after its pull request was opened.
- A pull request merged by hand in a temporary mode names a try with tests `.` at its head in its description
- A pull request merged by hand in a temporary mode names the verdict of its one check in its description
on the forge (`gitea.gitea_get_pull_request`); one without is the step skipped.
- One fix, one check (ADR 0548): a pull request that existed before its review approved the head, or a head whose check was
asked more than once with no new reviewed head, is steps 4 and 5 skipped; so is a `mesh-controller.try` or `mesh-delivery-checker.try` the operator did not ask for.
- Its issue moves to `resolved` within the hour the view says its source revision `delivered`.
- A pull request with no review record at its head (the Approve's ask says so first), a fix to a module's
running pushed with no integration test of the module whole, or a review report
+24 -28
View File
@@ -75,7 +75,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<main>
<button class="chip theme" id="theme" type="button">Theme</button>
<h1>Novox Playbooks</h1>
<p class="sub">How work is done on the mesh, from an idea or a symptom to a delivered change. Drawn from hq's <code>00-META/process/</code> at main <code>f269e83</code>, 2026-10-19, including playbook change hq #433 (try before the review).</p>
<p class="sub">How work is done on the mesh, from an idea or a symptom to a delivered change. Drawn from hq's <code>00-META/process/</code> at main <code>f269e83</code>, 2026-10-19, amended 2026-10-26 for ADR 0548 (one release, one check: no try).</p>
<nav>
<a href="#cycle">The cycle</a>
@@ -189,7 +189,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<!-- ============ FEATURE SWIMLANE ============ -->
<h2 id="feature">A feature, step by step (playbook 07)</h2>
<p class="lead">One milestone is one release: one branch name in every repository it touches, one worktree per repository, one fixing agent per repository kept for the whole milestone, one reviewer, and one pull request per repository, opened once. Since hq #433 the branches are <b>tried before the review</b>, so the reviewer reads real integration results.</p>
<p class="lead">One milestone is one release: one branch name in every repository it touches, one worktree per repository, one fixing agent per repository kept for the whole milestone, one reviewer, and one pull request per repository, opened once. Nothing is pushed or tried before the review (ADR 0548): the branches are pushed <b>once, after the review approves</b>, and the one check runs the integration tests.</p>
<div class="box scroll">
<svg viewBox="0 0 1080 430" role="img" aria-label="Playbook 07 in lanes: lead, fixing agents, reviewer, the mesh, the operator">
<defs>
@@ -199,12 +199,12 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<g class="mute">
<text x="190" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">0 · Prepare</text>
<text x="190" y="34" text-anchor="middle" class="mute">15 min</text>
<text x="410" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">1 · Develop + try</text>
<text x="410" y="34" text-anchor="middle" class="mute">30 min per issue · one try</text>
<text x="410" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">1 · Develop</text>
<text x="410" y="34" text-anchor="middle" class="mute">30 min per issue · local only</text>
<text x="630" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">2 · Review</text>
<text x="630" y="34" text-anchor="middle" class="mute">≤ 3 rounds · 15 min</text>
<text x="830" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">3 · Check</text>
<text x="830" y="34" text-anchor="middle" class="mute">≤ 1 more try · 1 check</text>
<text x="830" y="34" text-anchor="middle" class="mute">push once · 1 check</text>
<text x="1000" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">4 · Deliver</text>
<text x="1000" y="34" text-anchor="middle" class="mute">until delivered</text>
</g>
@@ -216,7 +216,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<text x="10" y="86" class="t">Lead</text><text x="10" y="102" class="mute">larger model</text>
<text x="10" y="158" class="t">Fixing agents</text><text x="10" y="174" class="mute">smaller model</text>
<text x="10" y="230" class="t">Reviewer</text><text x="10" y="246" class="mute">larger model</text>
<text x="10" y="302" class="t">The mesh</text><text x="10" y="318" class="mute">try, checks, wants</text>
<text x="10" y="302" class="t">The mesh</text><text x="10" y="318" class="mute">the one check, wants</text>
<text x="10" y="374" class="t">Operator</text><text x="10" y="390" class="mute">Telegram</text>
</g>
<!-- lead -->
@@ -224,11 +224,11 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<text x="190" y="78" text-anchor="middle" class="t">Survey · criteria</text>
<text x="190" y="96" text-anchor="middle" class="mute">context file per repo</text>
<rect x="340" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<text x="410" y="78" text-anchor="middle" class="t">Up to trunk · push</text>
<text x="410" y="96" text-anchor="middle" class="mute">each branch</text>
<text x="410" y="78" text-anchor="middle" class="t">Up to trunk</text>
<text x="410" y="96" text-anchor="middle" class="mute">each branch, local</text>
<rect x="760" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<text x="830" y="78" text-anchor="middle" class="t">Push reviewed heads</text>
<text x="830" y="96" text-anchor="middle" class="mute">open the PRs together</text>
<text x="830" y="96" text-anchor="middle" class="mute">once, PRs together</text>
<rect x="930" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<text x="1000" y="78" text-anchor="middle" class="t">Follow to done</text>
<text x="1000" y="96" text-anchor="middle" class="mute">resolve the issues</text>
@@ -242,14 +242,14 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<!-- reviewer -->
<rect x="560" y="200" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
<text x="630" y="222" text-anchor="middle" class="t">Read all repos</text>
<text x="630" y="240" text-anchor="middle" class="mute">+ tries · every criterion</text>
<text x="630" y="240" text-anchor="middle" class="mute">reads tests · every criterion</text>
<!-- mesh -->
<rect x="340" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
<text x="410" y="294" text-anchor="middle" class="t">try</text>
<text x="410" y="312" text-anchor="middle" class="mute">integration tests run</text>
<text x="410" y="294" text-anchor="middle" class="t">no try</text>
<text x="410" y="312" text-anchor="middle" class="mute">nothing pushed yet</text>
<rect x="760" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
<text x="830" y="294" text-anchor="middle" class="t">Forge checks</text>
<text x="830" y="312" text-anchor="middle" class="mute">gate · repo · group</text>
<text x="830" y="312" text-anchor="middle" class="mute">one check · mesh/check</text>
<rect x="930" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
<text x="1000" y="294" text-anchor="middle" class="t">Merge · wants</text>
<text x="1000" y="312" text-anchor="middle" class="mute">backups first, canary first</text>
@@ -260,10 +260,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<!-- edges -->
<path class="edge" d="M190,108 C190,140 260,154 298,154" marker-end="url(#ar2)"/>
<path class="edge" d="M410,128 V110" marker-end="url(#ar2)"/>
<path class="edge" d="M480,90 C545,110 545,250 462,270" marker-end="url(#ar2)"/>
<path class="edge" d="M480,298 C530,298 540,226 558,226" marker-end="url(#ar2)"/>
<text x="490" y="262" class="mute">results</text>
<path class="edge" d="M630,200 V182" marker-end="url(#ar2)"/>
<path class="edge" d="M630,200 V182" marker-end="url(#ar2)"/>
<path class="edge dash" d="M610,182 V198" marker-end="url(#ar2)"/>
<text x="514" y="196" class="mute">round n+1</text>
<path class="edge" d="M700,226 C740,226 740,82 758,82" marker-end="url(#ar2)"/>
@@ -302,7 +299,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<!-- ============ TESTS ============ -->
<h2 id="tests">When tests run</h2>
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) never run there: <code>mesh-controller.try</code> runs them on the pushed branch (a check ask on the language's stream), and the checker's merge check runs them again as the gate.</p>
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) are compiled there and never run: the checker's one merge check, asked by the one push after the review, runs them as the gate. There is no try (ADR 0548).</p>
<div class="box scroll">
<svg viewBox="0 0 1080 170" role="img" aria-label="Test levels over a feature's timeline">
<defs><marker id="ar3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
@@ -318,8 +315,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<g>
<rect x="120" y="96" width="340" height="16" rx="8" fill="var(--mute)" opacity=".18"/>
<text x="290" y="108" text-anchor="middle" class="mute">compiled only: go vet -tags integration</text>
<rect x="470" y="90" width="110" height="28" rx="6" fill="var(--mesh)"/><text x="525" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">try</text>
<rect x="770" y="90" width="120" height="28" rx="6" fill="none" stroke="var(--mesh)" stroke-width="2" stroke-dasharray="5 4"/><text x="830" y="109" text-anchor="middle" class="mute">try again if changed</text>
<rect x="770" y="90" width="120" height="28" rx="6" fill="none" stroke="var(--mesh)" stroke-width="2" stroke-dasharray="5 4"/><text x="830" y="109" text-anchor="middle" class="mute">push once</text>
<rect x="920" y="90" width="120" height="28" rx="6" fill="var(--mesh)"/><text x="980" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">merge check</text>
</g>
</svg>
@@ -332,7 +328,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<svg viewBox="0 0 520 300" role="img" aria-label="The review loop: at most three rounds">
<defs><marker id="ar4" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
<rect x="20" y="20" width="150" height="54" rx="8" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
<text x="95" y="44" text-anchor="middle" class="t">Round n of ≤ 3</text><text x="95" y="62" text-anchor="middle" class="mute">code + tries + criteria</text>
<text x="95" y="44" text-anchor="middle" class="t">Round n of ≤ 3</text><text x="95" y="62" text-anchor="middle" class="mute">code + criteria</text>
<polygon points="300,20 380,47 300,74 220,47" fill="var(--card)" stroke="var(--mute)"/>
<text x="300" y="51" text-anchor="middle" class="t">Wrong?</text>
<rect x="400" y="20" width="110" height="54" rx="8" fill="var(--okbg)" stroke="var(--ok)" stroke-width="2"/>
@@ -357,10 +353,10 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<h3 style="margin-top:12px">What every round reads</h3>
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
<li>Every criterion against its test: a test at its level that would fail without the change.</li>
<li>Every new integration test traced through the code, and its try's result.</li>
<li>Every new integration test traced through the code by reading it: no run exists before the check.</li>
<li>That each integration test enters by the path the live trigger takes.</li>
</ul>
<div class="note" style="margin-top:10px">Why reviews took more than one round (R3b, 2026-10-19): a test that "failed" only by not compiling, a reader of the changed record the survey missed, and bus tests judged by reasoning before they ran. hq #433 changes all three.</div>
<div class="note" style="margin-top:10px">Why reviews took more than one round (R3b, 2026-10-19): a test that "failed" only by not compiling, a reader of the changed record the survey missed, and bus tests judged by reasoning before they ran. ADR 0548 and playbook 07 step 2 item 4 change the last: bus tests are judged by reading them against the code, and the one check runs them after the review.</div>
</div>
</div>
@@ -465,17 +461,17 @@ const P = [
{n:"07",name:"Features",kind:"build",c:"--build",
trig:"Work that changes code across issues or repositories, too large for a small fix.",
who:"A lead (larger model), one fixing agent per repository (smaller), one reviewer (larger).",
steps:["Prepare: name the branch, make the worktrees, settle every question.","Write one context file per repository.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, how the integration level runs before the pull requests. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per issue.","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk, push, and try it. (new)","Review all of it with the tries' results; at most 3 rounds.","Push the reviewed heads; try again only what changed.","A merge by hand in a temporary mode first runs a try with tests \".\" and names it in the pull request. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by.","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
steps:["Prepare: name the branch, make the worktrees, settle every question.","Write one context file per repository.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, what the one check does not cover. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per issue.","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk; nothing is pushed, no try runs. (new)","Review all of it, reading the integration tests against the code; at most 3 rounds.","A group pins the group's own mesh-sdk head in every member. (new)","Push the reviewed heads once and read each forge head against the reviewed commit; no draft pull request, no fix round on an open one. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by; the one check runs. A failed check is read, fixed, reviewed, pushed once more. (new)","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
prod:"One pull request per repository, one source revision delivered, resolved issues, no branch or worktree left.",
next:"02 (when it ships)",bounds:"Prepare 15 min · 30 min per issue · review 15 min, ≤ 3 rounds · one try and one check",
prev:["Pull requests as scratchpads","Context bought again and again","Review rounds spent on what a run would show"],
next:"02 (when it ships)",bounds:"Prepare 15 min · 30 min per issue · review 15 min, ≤ 3 rounds · one check, pushed once",
prev:["Pull requests as scratchpads","Context bought again and again","Review rounds spent on what a run would show","A node agent's bus reads, proven only by review"],
check:"The checker's one status, mesh/check; the review record; the tracker's move."},
{n:"08",name:"Small fixes",kind:"build",c:"--build",
trig:"One issue in one repository, about an hour of work.",
who:"The session, with one reviewer on the larger model.",
steps:["Read the issue and the code; settle any open question.","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Push and try before the review. (new)","One review of the commit and its try.","Push the reviewed head; open the pull request.","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
steps:["Read the issue and the code; settle any open question.","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Nothing is pushed and no try runs before the review. (new)","One review of the local commit.","Push the reviewed head once; only then open the pull request. (new)","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
prod:"One pull request, one delivery, a resolved issue.",
next:"—",bounds:"About an hour · one try before the review, one review, one check",
next:"—",bounds:"About an hour · one review, one push, one check",
prev:["A fix reviewed after its pull request opened","A long part run twice","A fix waiting on someone else's delivery","Ten SDK merges and eight controller merges in a day, one per fix"],
check:"The checker's status; the review record; the tracker's move; one delivery group and one source revision per milestone."},
{n:"09",name:"Cleanup",kind:"side",c:"--side",
+97 -49
View File
@@ -1,12 +1,11 @@
---
layer: as-is
status: implemented
code: [mesh-controller, mesh-delivery, mesh-catalog modules/gitea, mesh-catalog modules/mesh-build-go]
updated: 2026-10-24
code: [mesh-delivery, mesh-catalog modules/mesh-delivery-gate, mesh-catalog modules/gitea, mesh-catalog modules/mesh-build-go, mesh-catalog modules/messenger, mesh-catalog modules/mesh-issues, mesh-controller, mesh-host, mesh-sdk]
updated: 2026-10-26
decisions:
- "ADR 0239"
- "ADR 0267"
- "ADR 0276"
- "ADR 0282"
- "ADR 0289"
- "ADR 0304"
@@ -17,71 +16,120 @@ decisions:
- "ADR 0382"
- "ADR 0395"
- "ADR 0398"
- "ADR 0429"
- "ADR 0524"
- "ADR 0525"
- "ADR 0527"
- "ADR 0528"
- "ADR 0529"
- "ADR 0539"
- "ADR 0546"
- "ADR 0547"
---
# Delivery — from a merge to the nodes, as it runs
What runs on 2026-10-24, after the delivery of release 4 and release 5's first half (milestone R4g, design 47),
read from the live controller (`mesh-controller.modules`, `.seats`, `.plans`), the delivery checker's `checker_status`
and the machines' module lists. It is what [to-be 47](../01-to-be/47-delivery-from-commit-to-delivered.md) has not yet
finished: the node agent applying wants and the deletion of plans are release 6, and the controller still plans,
orders and sends a delivery's builds to the machines. Until 2026-10-23 this document described the pipeline with the
build agent, the build queue and the two statuses `mesh/merge-gate` and `mesh/repo-check`, which no longer run.
What runs on 2026-10-26, after the delivery of release 6 (milestone R6 of
[to-be 47](../01-to-be/47-delivery-from-commit-to-delivered.md), ADR 0539), read from the code on each repository's
main (`mesh-delivery`, the gate in `mesh-catalog`, the node agent in `mesh-host`, `mesh-controller`) and from the live
mesh: the controller's seat verbs, `mesh-controller.modules`, the gate's `table`, and the view's `assigner_wants` and
`node_haves`. Until 2026-10-26 this document described release 5, in which the controller still made a delivery plan
for each merge, ordered it in tiers and sent whole declarations to the machines; none of that runs any more.
## Who does what
- **The parts of `mesh-delivery`** are modules of one repository, each a process with its own bus credential that
watches keys and writes keys (design 47, ADR 0429). Running now: the forge bridge (`mesh-delivery-forge-bridge`),
the indexer, the checker, the gate (`mesh-delivery-gate`, which holds the seat `mesh-delivery`), the teller and
the watchman on the control-node; the builder (`mesh-delivery-builder`) and the status view on the workstation;
and the assigner (`mesh-delivery-assigner`) on the home server (ADR 0524, ADR 0528).
- **The forge bridge** turns the forge's events into stream messages and is the only part that calls the forge. The
forge module (`gitea`, on the control-node) still announces them through the forge's system webhook.
- **The indexer** computes each module's fingerprint at the merge preview; **the builder** claims a build once per
fingerprint and orders it as an ask on the stream of the artifact's language.
- **The build modules** (`mesh-build-go` and `mesh-build-node` on three machines, `mesh-build-image` on one; ADR 0525) hold the
node seats `node-build-go`, `node-build-node` and `node-build-image`. Each pulls asks from the bus's work-queue
streams (deliveries first, then checks, then re-checks) while its node has a free slot of build capacity; no part
chooses the node. The seat `node-build-agent` and the module `build-agent` have no holder any more, and the seat
`mesh-build-machine` has none either.
- **The checker** (`mesh-delivery-checker`) runs the merge check: a change's tests through the build module of each
language, running only packages whose inputs changed, and writes the one status `mesh/check`. It also serves a
**try** (`checker_try`, `checker_try_read`): the same check on a pushed branch, before a pull request. `checker_status`
shows the steps in flight by node and the candidates still without a verdict.
- **The gate** merges a delivery group's pull requests once the check, the review and the operator's Approve agree on
the same heads. The forge requires no status on any repository (ADR 0534). The scripts `merge-check.sh` are gone from every repository (ADR 0529).
- **The assigner** composes the machines a change touches (the composition check the checker reads) and writes which
fingerprint each node should want, by its six rules; it only compares wants with what the controller sends.
- **The controller** (`mesh-controller`, on the control-node) plans, orders, sends and judges. It computes a delivery plan for the
files a merge changes, moves only modules whose build source changed with their build dependents, and writes the
chain's links it owns. On 2026-10-24 the seat still lists 64 verbs, `queue` and `replay` among them; whether
those still do work was not read here, and the build agent they served has no holder.
watches keys and writes keys (ADR 0429). Running now: the forge bridge, the indexer, the checker, the teller and the
watchman on the control-node; the builder and the status view (`mesh-delivery-view`) on the workstation; and the
assigner on the home server. The gate (`mesh-delivery-gate`, holding the seat `mesh-delivery`) is a module of
`mesh-catalog` and runs on the control-node.
- **The forge bridge** turns the forge's events into stream messages and is the only part that calls the forge; the
forge module (`gitea`) still announces them through the forge's system webhook.
- **The indexer** writes each module's fingerprint at the merge preview and at a trunk merge; **the builder** claims a
build once per fingerprint and orders it as an ask on the stream of the artifact's language.
- **The build modules** (`mesh-build-go` on all four machines, `mesh-build-node` on three, `mesh-build-image` on one;
ADR 0525) hold the node seats `node-build-go`, `node-build-node` and `node-build-image` and pull asks from the bus's
work-queue streams (deliveries first, then checks, then re-checks) while their node has a free slot of build
capacity.
- **The checker** runs the merge check and writes the one status `mesh/check`, and serves a **try** on a pushed
branch. The forge requires no status on any repository (ADR 0534), and no repository holds a `merge-check.sh`
(ADR 0529).
- **The gate** keeps one key per pull request and the merge's states only: `proposed`, `ready`, `rejected`, `held`,
`merged` and `closed`, read from its `table` verb. On the operator's Approve it merges a delivery group's pull
requests in one pass and writes their merged heads as one revision of `source.main`. It sends nothing to a
machine and asks the controller nothing.
- **The controller** (`mesh-controller`, on the control-node) keeps registration, placement in its assignments
bucket and every node's inputs, and composes: on events it writes each module's signed composition for each node at
each fingerprint into the node's bucket `composed-<node>`, with its digest and its judgement of the move in
`mesh-controller_composed`, and it issues a module's credential and grant when its build is done. It plans,
orders and sends nothing: of its seat's 65 verbs, 20 are refused by name with their replacement, among them
`plans`, `plan`, `push`, `deliver`, `delivery-stop`, `delivery-order` and `delivery-walks` (read 2026-10-26 01:20).
- **The assigner** writes each node's want of each module, `want.<node>.<module>`, naming a fingerprint and the
digest of the one composition it allows, when its seven rules hold: the build is done, the fingerprint is not
held, the node is the module's canary or the canary runs it healthy, at most `step` others ahead, every provider
runs healthy, the grant exists and the composition exists. In the code the canary is the first of the module's
nodes by name that is online (one ring), and rule 4 always holds, because the mesh keeps no `step`. It also runs
the composition check the checker reads. Its differences bucket, from release 5's comparison with what the
controller sent, is no longer written.
- **Two ways around the rules, both by hand.** A **put** (ADR 0546), written by the assigner's tool `assigner_put`,
puts one version of a module on one node: while a put owns a node's want of a module, from its ask until it ends,
the rules pass over that want. And the operator's credential may write a want itself, with a composition the
operator signed and wrote through mesh-sdk's `mesh-hand-compose` (ADR 0539 §10).
- **The node agent**, a process of the node-engine (`mesh-host`) on every node, watches its node's wants and its
bucket of compositions, and applies a module only when the want's fingerprint or digest differs from what runs,
and only the composition whose digest the want names, after checking its signature. It asks its node's
`node-backup` seat to back up a data module first and waits for the backup's end. It writes the have,
`have.<node>.<module>` in `node-engine_have`, with the fingerprint, the composition's digest, the phase and the
health. A failed apply is retried by the next change of the want or the composition; an apply that failed only
because the bus did not answer for a moment is retried with a bounded backoff, and at once when the bus is back.
- **The watchman** mirrors every want as a pulse owed by that node's agent until its have names the want's
fingerprint, so a node that does not move shows as a stall.
- **The status view** derives from `source.main`, the wants and the haves whether the source revision is
delivered, and says the event `delivered` once per revision, and `moving`, `settled` and `stuck` once for a
module on a node at one composition. The messenger holds the operator's notices on those events, and the issue
tracker hears `delivered`.
- **The grants part** (`mesh-grants`, on the control-node; ADR 0547) publishes each assignment's membership on the
assignments stream when it differs from the one last published. The bus's user list, assertions and grants it
composes only in its own state, compared with what the bus holds, and they take no effect; the controller still
composes the user list the bus loads.
## From a merge to a delivery plan
## From a merge to the nodes
- A pull request's head is built and checked as it comes in. One status, `mesh/check`, carries the verdict; a delivery
group has one verdict for every member. A move of the trunk asks the checks again, and a running check stops itself
when its head or trunk commit moves.
- A merge is made by the gate on the operator's Approve; the controller then makes a delivery plan, tiered along the
module graph, and sends it. `mesh-controller.plans` lists the plans and the delivery plans' tiers; a plan that
fails on a node says so and is retried by hand.
- A pull request's head is built and checked as it comes in; one status, `mesh/check`, carries the verdict, and a
delivery group has one verdict for every member. A move of the trunk asks the checks again, and a running check
stops itself when its head or trunk commit moves.
- The gate merges on the operator's Approve and moves the source revision in one write. Nothing collects merges or
waits for others: the indexer writes the new fingerprints, the builder claims each new one once, the controller
composes and grants on the done build, and the assigner writes each want once its rules hold, the canary first.
- Each node applies its own wants one module at a time and writes its haves; the source revision is delivered when
every want equals its module's fingerprint and every have equals its want, running and healthy. Read on
2026-10-26 at 01:20: 303 wants, every one equal to its node's have in fingerprint and composition digest, and 12
fingerprints held.
- **hq** builds no module: the checker runs hq's Go tests (`go test ./...`) as its check (ADR 0527).
## Where the record of what runs lives
Fingerprints, builds, verdicts and wants are keys in the bus's key-value buckets of the parts; builds' files are in
the artifact store by digest; packages are versions in the package registry. Plans, rollouts with their tiers,
assignments and settings are still rows in the controller's store. An assignment names its module and node but not
the build, artifact or plan that put it there.
Fingerprints, builds, verdicts, wants and haves are keys in the parts' buckets on the bus; compositions are in each
node's `composed-<node>` bucket, readable only by that node, with a file over 64 KiB in the object store; builds'
files are in the artifact store by digest; packages are versions in the package registry. The controller
still records every build it takes in, in its builds bucket (`RecordBuild` in `cmd/mesh-controller/key_takein.go`); its
assignments bucket keeps placement alone, and its rollouts bucket keeps the record of releases 1 to 5 read-only, and
nothing writes it now.
## What is not covered
The node agent does not yet apply wants: a node runs what the controller sent. The checker's left-running keys of
build modules that stopped (`checker_status`'s `left_running`) are listed and hold no place. The integration tests run
in each repository's check; there are no end-to-end tests of delivery yet, and the lab is not used (the operator,
2026-10-16).
Nothing checks the delivery budget: the over-budget condition and the gate's `times` went with release 6, and no
reader of the view's `settled` and `delivered` events measures it yet (issue 1945, note 10). The bus's own move stays
a person's step through the controller's `bus` verb (ADR 0539 §15). The checker's left-running keys of build modules
that stopped (`checker_status`'s `left_running`) are listed and hold no place. There are no end-to-end tests of
delivery, and the lab is not used (the operator, 2026-10-16).
## How it is checked
- `mesh-delivery-view.assigner_wants` and `mesh-delivery-view.node_haves`: each want beside its have; `indexer_modules`
and `builder_builds` for the fingerprints and builds they name.
- `mesh-delivery.table`: the gate's states and transitions; `mesh-delivery.deliveries` and `show` for one delivery.
- `mesh-controller.seats` and `mesh_overview`: the controller's verbs, and which are refused by name.
- The code: `mesh-delivery` (`modules/mesh-delivery-assigner/internal/assign`, `modules/mesh-delivery-view/internal/view`,
`modules/mesh-delivery-watchman/internal/watch`, `modules/mesh-grants`), `mesh-host` (`internal/agent`,
`cmd/mesh-host/nodeagent.go`), and the gate in `mesh-catalog` (`modules/mesh-delivery-gate`).
+16 -9
View File
@@ -2,8 +2,10 @@
layer: as-is
status: implemented
code: [mesh-tools, mesh-host, mesh-controller]
updated: 2026-10-24
updated: 2026-10-26
decisions:
- "ADR 0539"
- "ADR 0360"
- "ADR 0524"
- "ADR 0525"
- "ADR 0152"
@@ -21,17 +23,20 @@ decisions:
through `mesh_overview`, `mesh_machine`, `mesh_runtimes` and `mesh_describe`. Checked: the
catalogue (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a
module of that name.
module of that name. How the tool runner reaches a node, its count of modules and tools and the
controller's verbs were read again on 2026-10-26, after the delivery of release 6 (ADR 0539).
## What it is now
**The tool runner is one of the mesh's core components**, named `node-tools` beside
`mesh-controller`, `mesh-host` (the node-engine) and `nats` (the bus) — the mesh's own repositories'
check treats the four the same way, as the core whose health the mesh itself defines rather than a
module's own. `mesh-host` and `mesh-controller` supervise, compose and account for it as a core
process rather than as an ordinary assignment (`ComponentNodeTools`/`BusNodeTools` in both, the
witness and lease code that restarts and rolls it back, the account named `<node>.node-tools` on
the bus).
module's own. Since release 6 it reaches each node the way every module does: the controller composes
and signs its composition for that node, the assigner writes the node's want of it, and the
node-engine's node agent applies it and writes its have. What stays particular to it is the account
named `<node>.node-tools` on the bus (`BusNodeTools` in `mesh-controller`) and the node-engine's
witness and lease code that puts back a tool runner that does not come up (`ComponentNodeTools` in
`mesh-host`, ADR 0360 §9).
**It still carries a manifest, `node-tools/module.json` in `mesh-tools`,** and that manifest is
where its grants live: `invokes` names `*`, so its account may call every tool on the mesh;
@@ -60,7 +65,7 @@ body per request, no session and no event stream (`node-tools/internal/console/m
`mesh_machine`, `mesh_search`, `mesh_describe`, `mesh_call`, `mesh_runtimes` and `mesh_wait`. The
handshake's `instructions` text names all seven and the four address forms. `mesh_overview` is
where the mesh-wide count of modules and tools is read live — a number copied into this document
would drift; asked on 2026-10-17 it answered 103 modules and 645 tools across the whole mesh, with
would drift; asked on 2026-10-26 it answered 115 modules and 714 tools across the whole mesh, with
two module instances (`route-proxy` on two of the nodes) named as not answering rather than silently
absent.
@@ -94,8 +99,10 @@ command, not inline, because one call blocks until it answers or times out.
**These already answer directly, not as a stated future.** Beside every assigned module's tools,
the tool runner asks the `mesh-controller` seat for its own verbs the same way (ADR 0154): measured
live, the mesh-controller runtime is one instance, mesh-wide, and it answers 64 verbs for that one
seat (`status`, `push`, `assign`, `build`, `artifacts` and the rest), and the `mesh-controller`
live on 2026-10-26, the mesh-controller runtime is one instance, mesh-wide, and it answers 65 verbs for
that one seat (`status`, `assign`, `modules`, `artifacts` and the rest), of which 20 are refused by
name with what replaces them: the delivery verbs release 6 removed (`plans`, `plan`, `push`,
`deliver` among them) and the build verbs of release 4 (`build`, `queue` among them). The `mesh-controller`
*module* answers none of its own — it serves no module tools, only its seat's, exactly as design 33
described while it was still only designed. The 56-to-93 seat-verb counts each node's tool runner
answers (below) are a different thing: the tool runner serves the verbs of whichever node-scoped
+62 -8
View File
@@ -1,9 +1,10 @@
---
layer: to-be
status: implemented
code: [mesh-catalog, mesh-controller, mesh-host, mesh-media-catalog]
updated: 2026-10-12
status: in-progress
code: [mesh-catalog, mesh-controller, mesh-host, mesh-media-catalog, mesh-vault]
updated: 2026-10-26
decisions:
- "ADR 0550"
- "ADR 0305"
- "ADR 0158"
- "ADR 0094"
@@ -31,9 +32,10 @@ wrong.
- **A provisioned credential** is the login one module uses against another — a database password,
a broker account. The mesh mints it per consumer↔provider pair and seals it to the machines that
must hold it (ADR 0048),
and its provider makes it true. **This is not the vault's**, and the vault does not change it. It
already has an owner and a rotation
([13](13-credentials-and-their-rotation.md)); disturbing it would buy nothing.
and its provider makes it true. This design first left it out of the vault, since it already has an owner
and a rotation ([13](13-credentials-and-their-rotation.md)). **It is the vault's now** (ADR 0550 §5, from
ADR 0113): the vault keeps the value and hands it sealed; its provider still makes it true, and its
rotation is the same machinery.
- **A module's own secret** is a value a single module needs for itself — the password of a store
it runs privately, an internal signing token. Today the mesh generates this as a value nothing
owns, and so it cannot be rotated. **This is the vault's.**
@@ -45,6 +47,56 @@ The vault, then, is the provider a module turns to for a secret that is **not**
some other provision. It is the answer to *"this module needs a password, and there is no provider
whose job it is to give it one."*
## Every secret lives in the vault
*Decided 2026-10-26 (ADR 0550), on the operator's answers; built in the milestones of ADR 0547.*
**The seat `mesh-vault` holds every secret the mesh keeps.** That is every shared secret, the enrolment tokens
among them; the mesh's signing key; every licence's grants and tokens; every bus account's password or, where
the vault has no value, its hash; and every module's own secrets and credentials. A holder gets a value only
sealed to its own key, on request and reply, or asks the vault to act with it, as for a signature. No secret is
kept in a bucket, a store database or a module's own rows. The private keys a node or the operator makes for
itself stay where they are made (ADR 0550 §1).
- **The signing key.** The vault makes a new signing key for the mesh, with the mesh's authority beside it; the
controller's key is never copied out of its database. The vault signs enrolments, node certificates and the
authority's own on request, through its verb `sign`, for the callers its manifest names, and says `signed`
naming the caller, the kind and the digest, never the key. The public halves are in a state of the vault that
every node reads. The key and the authority leave the vault only as the one copy sealed to the operator key,
opened only in the break-glass recovery below, so losing the vault does not lose the key every signature
depends on. Each node trusts the vault's key beside the controller's, one node first, and the controller's no
more once it loses its lease (ADR 0550 §2).
- **Licence grants and tokens.** The refresh tokens, the API key and the access tokens of every licence are vault
secrets; the licence manager keeps which licence goes to which holder, and nothing secret
([39](39-the-anthropic-licence-manager.md), ADR 0550 §3).
- **Bus account passwords.** A bus account's value is the vault's: until ADR 0547's third milestone the controller
hands the vault the value of each account ADR 0374 §1 covers and keeps the others' values as today (ADR 0374
§4), whose hashes the vault adopts; from then the vault mints every one. The agent of the node holding `mesh-broker` hands the vault,
sealed to it, the bcrypt hash of every user of the list the bus loaded; the vault adopts the hash of each user
it holds no value for (the controller's own, the node and device accounts, the module and process accounts its
values do not cover), writes it under `bus-accounts.<user>` and says `account-hashed` with `adopted: true`. It
never touches a key that holds the hash of its own value, and writes an adopted key again when the loaded list
carries another hash for that user. Its tool `secret_own` lists the adopted users and each adoption, and
`account_adopt` adopts now (ADR 0550 §4).
- **Module secrets** stay as the sections below describe them: a module's own secrets, the credentials between a
consumer and its provider, and the values an operator or a backend delivers. A node composing its own modules
asks the vault for them sealed to its key, and no composition carries one in the clear (ADR 0550 §5).
- **Three exceptions until ADR 0547's third milestone, each ending there:** the controller's own signing key in
its identity database; the bus account values the controller mints, handed to the vault where the accounts are
covered and kept by the controller for the others; and the enrolment tokens and new node accounts the
controller makes and keeps in its store. So **no machine is enrolled before the third milestone** (ADR 0550 §1,
§6).
*How it is checked:* before the third milestone's lease write, the controller's licence and identity databases and
the licence manager hold no secret the vault does not hold, and no exception is left that the vault has not
replaced, read with the vault's `secret_holders` and the store's reader. A unit test that `sign` refuses a caller
its manifest does not name and that no answer or event carries the key; `secret_holders` showing the
operator-sealed copy of the key and no other holder; and live, a node certificate the vault signed that another
node verifies. After the delivery of the hash hand-over, `mesh-grants.grants_user_list` shows no user without a
hash and `secret_own` lists the adopted users; unit tests in the vault (adopt; never touch its own value or a
covered account; rewrite on a new loaded hash) and in the node agent (every loaded user handed, sealed). The
hand-act log shows no enrolment before the third milestone.
## A secret as a provision
A module that needs a secret for its own use requires a `secret` provision, exactly as it requires
@@ -77,8 +129,10 @@ the foundation: installed at genesis, the way the store and broker are raised fi
adopted as modules (ADR 0078). A
mesh has root secrets, so a mesh has a vault; it is not something a mesh opts into
(ADR 0085, amended). It is provisioned the
ordinary way, and it does **not** mint the mesh's delivery credentials — the controller does that,
because the controller must mint in order to deliver any provision, the vault's own included.
ordinary way. It does not yet mint the mesh's bus accounts: until the third milestone of ADR 0547 the
controller mints them, hands the vault each covered account's value (ADR 0374 §1) and keeps the others as
today, and from then the vault mints every one
(*Every secret lives in the vault*, above).
There is **one vault per mesh**, on the control-node, reached from any node the way identity is.
Node scoping (ADR 0084,
@@ -1,9 +1,10 @@
---
layer: to-be
status: implemented
code: [mesh-catalog modules/claude-licence-manager]
updated: 2026-10-04
status: in-progress
code: [mesh-catalog modules/claude-licence-manager, mesh-vault]
updated: 2026-10-26
decisions:
- "ADR 0550"
- "ADR 0209"
- "ADR 0206"
- "ADR 0183"
@@ -18,13 +19,35 @@ decisions:
# 39 — The Anthropic licence manager
**One module knows every Anthropic licence the mesh has, keeps each alive, decides which consumer gets
which, and hands every node's agent its token over the bus.**
**One module knows every Anthropic licence the mesh has, keeps each alive, and decides which consumer gets
which; the vault keeps every grant and token and hands every node's agent its token over the bus**
(ADR 0550 §3, amending the first shape, in which the manager kept the grants and handed the tokens itself).
ADR 0183
decides it; this is the shape. It is the successor of the predecessor's manager module, built from what
that module learned the hard way, and the counterpart of [36 — The operator's agent on a machine](36-the-operators-agent-on-a-machine.md),
which is the consumer on every node.
## Every grant in the vault
*Decided 2026-10-26 (ADR 0550 §3), on the operator's answer that every secret lives in the vault; built in
ADR 0547's second milestone (issue 1126). Until it is delivered, the sections below hold as first written.*
- **The vault keeps every licence's secrets**: the refresh tokens, the API key and the access tokens are vault
secrets ([24](24-the-secrets-vault.md)). The manager keeps its bindings, its licence records without a secret
(account, kind, health, usage, audit), and nothing at rest that is secret.
- **The manager stays the one rotation source.** It alone calls the vendor's token endpoint: to refresh or adopt,
it asks the vault for the grant sealed to itself and hands what it gets back to the vault. A grant offered at
login, sealed to the manager's key (its `public-key` verb is kept), is handed on to the vault the same way.
- **A holder's token comes from the vault.** The agent module on a node registers its public half with the vault,
not the seat, and asks the vault for its token sealed to it. The vault serves only the licence the manager's
binding names for that holder, and says `secret-changed`, never a value. The seat's `current` verb answers which
licence a consumer holds and its health, never the token.
- **It waits for accounts of their own.** The vault hands a value only to a module running under a bus account of
its own (issue 569), so the move waits until the manager and each agent module run under one.
*How it is checked:* a unit test that the vault refuses a token to a holder no binding names or to one without an
account of its own, and a scan after the move that the manager's tables hold no grant.
## 1. What it is
A module, `claude-licence-manager`, holding the mesh-scoped seat **`anthropic-licence-manager`**. One
@@ -43,7 +66,8 @@ Its store holds four things:
**The grants are encrypted with a key the vault made for the manager** — its one `secret` requirement.
The vault keeps that key; the manager keeps the grants. That is ADR 0050's carve-out, one module, one
node, the long-lived grants only.
node, the long-lived grants only. ADR 0550 §3 ends the carve-out: the grants move into the vault, and the
`licences` table keeps no grant (*Every grant in the vault*, above).
## 2. The licences it manages today
@@ -77,7 +101,9 @@ Carried from the predecessor, where each rule was earned by an incident:
## 4. Handing a token to a node
*Amended 2026-10-04 by ADR 0206*, replacing the manager's visits: **what each consumer
should hold is the manager's state, and the token is fetched when it changes.**
should hold is the manager's state, and the token is fetched when it changes.** *Amended 2026-10-26 by
ADR 0550 §3:* the token is fetched from the vault, not the seat; the `bindings` state and its generation stay
the manager's, and the vault serves the licence a binding names.
- **The manager keeps a `bindings` state**, one key per consumer: the licence, its kind, and a
**generation** that increases with every rotation and every switch. Nothing in it is secret.
@@ -162,7 +188,8 @@ gone; a rotation or a switch is a new generation in the `bindings` state.
identity, expiry, failures, who is bound), `bindings`, `bind`, `switch`, `release`, `refresh` (now, one
or all), `usage` (current and history), `adopt` (a file on the manager's node, or a key sealed to its
`public-key` — ADR 0209), `public-key`, and `current` (a consumer's token, sealed to the key the
consumer sends — ADR 0206). The manager asks a node for a candidate grant by the agent module's own tool.
consumer sends — ADR 0206; from ADR 0550 §3, which licence a consumer holds and its health, never the
token, which the vault hands). The manager asks a node for a candidate grant by the agent module's own tool.
## 8. Settings
@@ -179,6 +206,8 @@ one definition serves and one mesh may differ.
| a grant offered with a mismatching identity is refused and one notification emitted | ADR 0183, attribution |
| a failing licence notifies once, and once a day after, not once per tick | §3 |
| the mesh MCP server lists the seat's verbs and `switch` changes a workstation's token end to end | ADR 0132, the exit of the build |
| the vault refuses a token to a holder no binding names, or to one without a bus account of its own | ADR 0550 §3 |
| after the move, the manager's tables hold no grant | ADR 0550 §3 |
## What this does not settle
@@ -1,9 +1,16 @@
---
layer: to-be
status: in-progress
code: [mesh-controller, mesh-catalog modules/mesh-delivery-gate, mesh-delivery, mesh-host, mesh-sdk, hq 00-META/checks]
updated: 2026-10-24
code: [mesh-controller, mesh-catalog modules/mesh-delivery-gate, mesh-delivery, mesh-host, mesh-sdk, mesh-vault, hq 00-META/checks]
updated: 2026-10-27
decisions:
- "ADR 0559"
- "ADR 0555"
- "ADR 0551"
- "ADR 0550"
- "ADR 0549"
- "ADR 0548"
- "ADR 0547"
- "ADR 0539"
- "ADR 0536"
- "ADR 0534"
@@ -18,6 +25,8 @@ decisions:
- "ADR 0493"
- "ADR 0491"
- "ADR 0488"
- "ADR 0506"
- "ADR 0497"
- "ADR 0487"
- "ADR 0486"
- "ADR 0485"
@@ -120,7 +129,14 @@ 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.
group and the modules of the trunk. At each trunk merge of the catalogue it also reads the catalogue'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 catalogue 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).
- **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
@@ -133,29 +149,52 @@ source revision is derived from the keys: every **want** equals its module's **f
- **The checker** runs the merge check on built artifacts: a change's tests, through the build module of each
module's language and by that language's convention, running only the packages whose inputs changed and
taking the kept verdict of the rest; it writes the change's one status (ADR 0444, ADR 0445, ADR 0447); it runs dark in R4c-5 (ADR 0484).
It judges a delivery once per set of heads, and carries a verdict to a newer trunk commit by its package
digests instead of judging it again (ADR 0548 §1, §5; *A move of the trunk carries every verdict*, below).
- **The gate** merges a group's pull requests once check, review and the operator's Approve agree on the same
heads, and then moves the mesh's source revision in one write.
heads, and then moves the mesh's source revision in one write. While the check gates merges, it holds an
approved merge for another delivery's running check of the repositories the merge goes into, and keeps the
merge's **merge turn** there (ADR 0548 §2, §3, as ADR 0551 §1 bounds them; *Nothing merges into a repository
under check*, below).
- **The teller** carries conditions and asks to the operator's phone and is the only writer of an ask's answer.
- **The assigner** writes which fingerprint each node should run, by seven rules: the build is done, the
- **The assigner** writes which fingerprint each node should run, by six rules: the build is done, the
fingerprint is not held, the node is the module's **canary** or the canary runs it healthy, at most `step`
other nodes are ahead when `step` is above zero, every provider runs healthy, the module's credential for that
fingerprint exists, and the controller's **composition** of the module for that node at that fingerprint exists
(ADR 0429 §6, ADR 0539 §6). Each want `want.<node>.<module>` names the fingerprint and the digest of the one
composition it allows; the assigner also writes the node's own want `want.<node>.node` once the node's
composition exists, and never deletes a want (ADR 0539 §1, §6; ADR 0408 §4). A move a person must allow waits
for the operator's yes before its want is written (ADR 0539 §14).
other nodes are ahead when `step` is above zero, every provider runs healthy, and the module's grant
`grants.<module>.<fingerprint>` exists in the grants part's state (ADR 0429 §6, ADR 0547 §4). No rule waits on a
composition: the node composes its own (ADR 0547 §2). Each want `want.<node>.<module>` names the fingerprint,
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).
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 what the controller keeps of machines, settings and
grants, with mesh-sdk's composition, the one the controller composes with (ADR 0446). Its module was made in
R4g with release 5, when its wants were only compared with what the controller sent; from release 6 it writes
no difference, and the controller writes `grants.<module>.<fingerprint>` when a module's build is done
(ADR 0524 §4, ADR 0528, ADR 0539 §7, §8).
- **The node agent**, the node-engine's own process, follows its node's wants and its own bucket of
compositions, applies one module at a time and only what differs from what runs, verifies each composition's
signature, backs up a data module first, and writes its haves with the fingerprint and the composition's
digest it runs (ADR 0539 §2, §3, §9, §16). A composition is not bounded by one bus message: a file over 64 KiB
travels by digest in the bus's object store, in a bucket of that node's own that no other node can read, and a
condition warns before the limit (ADR 0486, ADR 0488, ADR 0539 §4).
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
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
mesh-config, its secrets and bus credential asked of the vault sealed to the node's key, its licences and what
its providers contribute, applies one module at a time and only what differs from what runs, backs up a data
module first, and writes its haves with the fingerprint and the digest of the composition it runs (ADR 0547 §2,
§3; ADR 0539 §16). Nothing composed crosses the bus, so nothing is signed; of other nodes it reads only their
public records and the manifests wanted on them. On the node holding `mesh-broker` it also writes the bus's user
list, alone and with no want (*The bus's user list*, below).
- **The grants part** (module `mesh-grants`, holding the seat `mesh-grants`) composes who may say what on the bus
with mesh-sdk's bus composer: each module's membership, the buckets, logs and streams a manifest declares, the
bus's user list, and from ADR 0547's third milestone `grants.<module>.<fingerprint>` (ADR 0547 §5, §6). It publishes
memberships from the first milestone; until the operator's `take-over` it keeps the bus's user list, the
assertions and the grants in shadow, composing and comparing them with what the bus loaded and writing nothing
of them that takes effect; its tools read all it holds and redo all it does.
- **The watchman** turns the bus's connections into presence and mirrors every key whose next move a machine
owes into a **pulse**, whose expiry is a stall.
- **The status view** is the operator's command `mesh-delivery-cli` and a web view, whose wants page sets each
@@ -164,42 +203,80 @@ source revision is derived from the keys: every **want** equals its module's **f
`moving`, `settled` and `stuck` once for a module on a node at one composition (ADR 0539 §17; *A delivery*
below).
Nine of these are modules of the repository `mesh-delivery`, prefixed `mesh-delivery-` (ADR 0429 §2); the
Ten of these are modules of the repository `mesh-delivery`, prefixed `mesh-delivery-` (ADR 0429 §2); the
repository is made by the forge module from its config (ADR 0428; issue 1128 makes the config value reach the
node at once). The reviewer stays the review agent's session.
node at once); the grants part is one of them. The reviewer stays the review agent's session.
**The controller leaves build and delivery** (ADR 0547, accepted 2026-10-25). The drawing above is the target, and
no act of build and delivery is the controller's in it: no part takes in done keys, composes centrally, signs a
composition, mints a bus account, writes a grant, publishes a membership, composes the user list or forwards a try
on the controller's behalf. Its code is not changed for this; it stops by losing its lease (ADR 0547 §1). The way
there is three milestones, one issuer of each key at every moment (ADR 0547 §11):
1. **The list follows, the grants part in shadow** (issue 1945): the bus node applies the user list alone, with
no want; mesh-sdk carries a copy of the bus composer, held byte for byte to the one release 6 runs; the grants
part publishes memberships (by the one grant the operator allowed in the controller's composer and its
mesh-sdk copy, publishing the assignment subjects and nothing more) and composes and compares the user list,
assertions and grants in shadow with its tools; the vault hashes the bus accounts it is handed into its key
`bus-accounts.<user>`. The agent of the node holding `mesh-broker` hands the vault, sealed to it, the hash of
every user of the list the bus loaded, and the vault adopts the hash of each user it holds no value for, and
again when the loaded list carries a new hash for that user (ADR 0550 §4). The
operator's `take-over` makes the grants part's list the one loaded; it is allowed only when the users of both
lists are equal but for users placement no longer names, so it waits until the enrolment users and the persons
have records on the bus, and the second milestone waits for it (ADR 0547 §8, §11).
2. **Nodes compose** (issue 1126): each node agent composes its own modules; no node applies a composition made
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 catalogue alone (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).
No machine is enrolled before the third milestone: until the vault mints enrolment tokens and node accounts,
those are the controller's alone (ADR 0550 §6).
Until each milestone is delivered, release 6's text below holds for what that milestone replaces (ADR 0547 §11).
It lands in six releases, each leaving the mesh working, each one milestone below (ADR 0429 §7); releases 4 and 5 end in one milestone, R4g,
with one switch-over and no rehearsal (ADR 0524 §1). Until the
release that replaces it is delivered, each section from here to *The bootstrap* stays this design and its rule
(ADR 0429 §21); each is rewritten when the release that replaces it is prepared (issue 1130). Release 6's were
rewritten when it was prepared, from ADR 0539: until its delivery the plans of release 5 run, as the as-is
design says, and ADR 0539's clauses hold from that delivery (ADR 0539 §20).
rewritten when it was prepared, from ADR 0539, and its acceptance tests restated after its delivery; release 6
is delivered, so ADR 0539's clauses hold (ADR 0539 §20), and the as-is design describes the delivery as it runs.
## The parts
From release 6 (ADR 0539):
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: each module's fingerprint
controller: signed compositions, ◄┤ ├── assigner: each node's wants, by seven rules
credentials and grants │ ├── watchman: presence, pulses, stuck keys
│ └── status view: delivered, moving, settled, stuck
gate: the merge, the source revision ◄┤ ├── indexer: fingerprints, manifests, placement
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
▼
node agent in the node-engine on every node
(reads its wants and its compositions; backs up first; writes its haves)
(reads its wants; composes its own modules; backs up first; applies; writes its haves;
on the bus node, writes the bus's user list)
```
- **The gate** (module `mesh-delivery-gate`, holding the seat `mesh-delivery`) owns hotfixes, features and
milestones up to the merge: it keeps one key per pull request, asks the operator's Approve, merges a group in
one pass and moves the source revision in one write. It asks the controller nothing, consumes no event of a
plan, never sends to a machine and writes no composition (ADR 0539 §17).
- **The controller** keeps registration, the inputs of every node (settings and given values, secrets,
identities and memberships, credentials, the facts), placement in its assignments bucket, and composing: it
writes each module's signed composition for each node, its digest and its judgement of the move, by
compare-and-set while it holds the lease, and issues a module's credential and grant when its build is done.
It plans nothing, orders nothing and sends nothing (ADR 0539 §1, §2, §5, §7, §12).
- **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 catalogue (ADR 0549), 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
vault to act with it. It makes the mesh's signing key and signs enrolments and node certificates on request, by
its verb `sign` (ADR 0550 §2). From ADR 0547's third milestone it mints each bus account a membership names,
seals it to its node and writes its bcrypt hash for the grants part alone; until then it hashes the accounts it
is handed, and adopts from the bus node's agent the hashes of the accounts it holds no value for (ADR 0547 §9,
ADR 0550 §4).
- **The build modules** build and check with toolchains installed on their node, each as an account of its own, and pull
their asks from the bus's streams, deliveries first (ADR 0405, ADR 0525).
- **The forge module** (`gitea`) publishes every forge event on the bus through the forge's system webhook
@@ -266,9 +343,9 @@ ended, as ADR 0505 §3 has it for a delivery in motion (issue 1946).
**One key per pull request.** Beside the delivery, the gate keeps one key per open pull request on the
bus: its head, the trunk commit its current check is asked against, and its verdict, written by
compare-and-set (ADR 0406 §4). It writes a new head when the forge says one, and a new trunk commit only when it
asks the pull request's check again (ADR 0395 §1). A running check watches that key and stops when the head or
the trunk commit in it moves.
compare-and-set (ADR 0406 §4). It writes a new head when the forge says one, and a new trunk commit when the
pull request's verdict is carried to it or a whole check is asked against it (ADR 0548 §5, §7). A running check
watches that key and stops only when the head in it moves; a trunk move never ends it (ADR 0548 §1).
## The state table
@@ -283,17 +360,21 @@ state has a **bound**: how long it may be held before `stalled` lists it, for a
| proposed | a head was announced and is being checked | 1 hour from when its check is asked; none while its verdict is out of date and no check is asked |
| ready | it passed; its Approve is asked (a group member's once the group is ready), and it waits for it and the merge | none |
| rejected | it failed its check | none |
| held | it waits for a person or a fresh check: an approved merge unchecked at the trunk's head, a conflict, a member the forge refused | 1 day |
| held | it waits for a person, a carry or a check: an approved merge whose verdict is being carried to the trunk's head, a conflict, a member the forge refused, and, while the check gates merges, an approved merge waiting on its merge turn for another delivery's running check (ADR 0548 §2, §8, ADR 0551 §1) | 1 day; a merge turn 30 minutes |
| merged | merged on its trunk; its source revision moved | final |
| closed | closed without a merge | final |
**A verdict holds for one trunk commit** (ADR 0323). The check judges the head merged into the trunk's head
when the build module clones it, and the verdict names both. When another merge moves the trunk, a `ready`
delivery goes back to `proposed` on the event `trunk moved`, and the gate asks the check of every active,
cleanly merging open pull request into that trunk at once (ADR 0395, *A move of the trunk asks every check
again* below). An Approve already asked stays; the merge waits for the fresh check, and the Approve is not asked
again for a trunk move (ADR 0421 §1, §3). The row from `proposed` to `ready` or `rejected` takes only a verdict
that names the trunk's head.
**A verdict holds for one trunk commit, and is carried when the trunk moves** (ADR 0323, ADR 0548 §5). The
check judges the head merged into the trunk's head when the build module clones it, and the verdict names both,
and the commit of each sibling it read at its trunk (ADR 0548 §6). When another merge moves the trunk, or a
sibling's trunk, a `ready` delivery goes back to `proposed` on the event `trunk moved`, its `mesh/check` is
pending and says it is being carried, and the checker carries the verdict to the new commit once any check
running on it has ended: it merges the head onto the new trunk head, computes every package's digest, and runs
only the packages whose digest the move changed; the composition is kept while no fingerprint of a module it
composed changed. A carried pass names the commits it was carried from (`carried-from`). An Approve already
asked stays; the merge waits for the carried verdict, and the Approve is not asked again for a trunk move
(ADR 0421 §1, §3, as ADR 0548 §8 changes them). The row from `proposed` to `ready` or `rejected` takes only a
verdict that names the trunk's head (*A move of the trunk carries every verdict*, below).
**Nothing of a merged delivery is kept moving in the gate.** The delivery states of the plans (delivering, the
backup hold, superseded, covered, stopped, retired and failed) and the machine steps went at release 6: a
@@ -304,7 +385,7 @@ module's progress on each node is its want against its have, shown by the view (
*ADR 0419 §1, §2, ADR 0429 §6, ADR 0539 §6, §7.*
A delivery group has no order among its members, and nobody writes one (ADR 0419 §1). From release 6 there are
no tiers: no plan orders modules into steps. What reaches a node, and when, is the assigner's seven rules, read
no tiers: no plan orders modules into steps. What reaches a node, and when, is the assigner's six rules, read
per module and node from the keys:
- **A build before its want.** No want names a fingerprint whose build is not done; a module whose build waits
@@ -334,14 +415,14 @@ commit on the SDK's trunk or the head of an SDK pull request the forge holds, op
## Checks
### One check per pull request, kept by the bus
### One check per set of heads, kept by the bus
*ADR 0323, ADR 0350, ADR 0395, ADR 0406.*
*ADR 0323, ADR 0350, ADR 0395, ADR 0406, ADR 0548 §1.*
- **Asks on streams, taken in order.** A build or a check step is an ask, a message on one of the work-queue streams of
the artifact's language, which the build modules of that language claim as the accepted kinds of their node seat: `delivery` for the
build of a trunk fingerprint, `check` for the build of a candidate's fingerprint, a new head's check and the checker's steps (a try is a
check ask), and `recheck` for the checker's re-judges after a move of the trunk (ADR 0525 §1, §2, ADR 0536 §4). A build module pulls
check ask), and `recheck` for the checker's carries after a move of a trunk or a sibling (ADR 0525 §1, §2, ADR 0536 §4, ADR 0548 §5). A build module pulls
`delivery`, then `check`, then `recheck`, while it has a free slot of its node's build capacity, and no part chooses the node (ADR 0525 §4).
Each check stream has one subject per pull request and keeps only the newest request: a newer request replaces one still waiting, by the
stream's rule; a request left behind for a head and trunk commit already judged or running is acknowledged and dropped when taken, by
@@ -351,26 +432,50 @@ commit on the SDK's trunk or the head of an SDK pull request the forge holds, op
- **Nothing holds a check while a delivery builds.** The order of the streams is the only priority deliveries have: the controller's
key saying that a delivery builds, and the let-through of a check that waited 30 minutes, are gone, so during a large delivery an
Approve ask may come later (ADR 0525 §7, which supersedes ADR 0483).
- **A running check stops itself.** It watches its pull request's key and stops when the head or the trunk
commit in it moves, without asking the forge (issue 726). An ended check delivers no verdict, so none of its
builds is reused.
- **A check runs to its end.** A delivery, a lone pull request or a delivery group, is judged by one check per
set of heads: the checker claims its verdict key once, and the check runs to its verdict (ADR 0548 §1). Only a
new head ends a running check: a push, or a member joining or leaving its group. The check watches its pull
request's key and stops when the head in it moves, without asking the forge (issue 726); an ended check
delivers no verdict, so none of its builds is reused. A trunk move never ends a check: its verdict goes out of
date when it ends, and is carried. A check that failed because a part of the mesh was not heard is still asked
again when the nodes change, which judges heads no check judged.
- **A failed build node loses nothing.** An ask a build module took and never acknowledged goes to another node by the
stream's own acknowledgement wait, with no timer of a part; a step's end is written on the build module's step key named
by the ask's id (ADR 0525 §6).
- **A change is built once** (ADR 0350). A build is keyed by its module's fingerprint, so the merge commit's
fingerprints find the builds the check already made, and the merge builds nothing again (ADR 0453 clause 5).
### A move of the trunk asks every check again
### A move of the trunk carries every verdict
*ADR 0395.*
*ADR 0548 §5 to §8, which change ADR 0395 §1 to §3 and ADR 0323 §3, §4; ADR 0551 §1 keeps them whether or not
the check gates merges.*
- A trunk move asks the check of every active open pull request into that trunk that merges cleanly, against the
new head; a conflict is told once on the pull request, not checked.
- **A carry, not a check.** A trunk move puts every verdict judged on the old commit out of date and carries it,
once any check running on it has ended: one ask on the build seats' `recheck` stream that merges the head onto
the new trunk head and computes every package's digest. A package whose digest has a kept verdict does not run;
a move that changes nothing a package's tests read runs nothing; the composition check is kept while no
fingerprint of a module it composed changed, and runs again otherwise (ADR 0548 §5). A carried pass is
rewritten under its key by compare-and-set with the new trunk heads and names the commits it was carried from;
a failure fails it, naming the package and the move. This bends the operator's "only a conflict asks a new
check" once, on purpose: a package whose inputs the move changed runs, so the trunk never gets a combination no
test ran.
- **A sibling's move is carried too.** A verdict names the commit of each sibling it read at its trunk, beside
the repositories it merged (ADR 0484 §4); a move of one is carried as a trunk move is, and only the packages
whose tests read siblings can change digest and run (ADR 0548 §6).
- **Only a head git refused gets a whole check.** A carry whose merge conflicts, or a merge the forge refuses for
a conflict, fails the verdict naming the files in conflict, and its Approve's ask is withdrawn; its next check
is a whole check of its head, at its next push or at the first later trunk move onto which it merges cleanly
(ADR 0548 §7). That is the one check a trunk move asks; a verdict that can be carried is never checked again
for a trunk move.
- **The line says the carry.** A carried verdict's line names the head on the new trunk commit first, as the gate
and the merge tool read a line, then per level the trunk commit it was carried from and the packages run and
kept (ADR 0548 §8). While a verdict is carried, `mesh/check` on the heads is pending and says so (ADR 0548 §6).
- **A group carries one verdict.** For a group, a move carries the group's one verdict on every member's head;
the group's own merges, made in one pass on its Approve in its order, carry nothing (ADR 0418 §4, ADR 0323 §6).
- A pull request with no push for 7 days and no Approve asked or given is left out; its statuses say out of date
and that a push or a recheck asks its check (ADR 0421 §10).
- For a group, a move asks each member's own check, and the composed check runs again through those checks; the
group's own merges, made in one pass on its Approve, ask its members nothing again (ADR 0418 §4).
- Its cost is at most one check per active, cleanly merging pull request per trunk move; issue 944 measures it.
- Its cost is at most one carry per open verdict per trunk move, each running only the packages whose digest the
move changed; issue 944 measures it.
### A delivery group's checks
@@ -393,6 +498,43 @@ commit on the SDK's trunk or the head of an SDK pull request the forge holds, op
merged again on its fresh check while the group waits in the gate, `held`, and its source revision does not move
(ADR 0539 §17).
### Nothing merges into a repository under check
*ADR 0548 §2 to §4, §9, §10, as ADR 0551 bounds them.*
**Only while the check gates merges.** The hold, the merge turns and their stalled line hold only while the
setting `check-gates-merge` of mesh-delivery-gate, set together with the forge module's, is true. While it is
false (the operator's setting since 2026-10-25: the check runs, but gates no merge), the gate writes no turn and
holds no approved merge for another delivery's running check: the merge is handed to the forge bridge at once,
the running check ends on the commit it read, and its verdict is carried (ADR 0551 §1, §2). The checker does not
read the setting: it reads `turns`, finds none, and claims its checks at once.
- **The hold.** The gate hands no approved merge to the forge bridge while a verdict of the checker's verdicts
bucket in the state `running` names, among its trunk heads, a repository the merge goes into; the verdict of
the merging delivery's own heads excepted. The gate already watches that bucket, so the verdict leaving
`running` (a pass, a failure, its key deleted) gives the waiting merge its turn at once, with no timer and no
new event. Siblings a check reads beside are not held: the catalogue is read beside nearly every check. A merge
made at the forge by hand is not held; the check it lands under ends on the commit it read and is then carried
(ADR 0548 §2).
- **The merge turn.** Before it reads the checker's verdicts for an approved merge, the gate writes one key per
repository the merge goes into in its bucket `turns` (`turns.<owner>.<name>`): the deliveries that wait there in
the order of their Approve, the verdict key each waits for, and since when; for a delivery group, on every
member's repository from its first member handed until its last merged, so the group merges whole. The gate is
the key's one writer; the checker reads the bucket, reads it absent as empty, claims no check of a repository a
turn names, and claims on the key's delete, so an approved merge goes before a check not yet started. The key
goes when the last delivery in it merged or left (a Decline, a push, a refusal). A check claimed in the moment
between the gate's read and the merge is judged on the commit it read and carried once after, never restarted
(ADR 0548 §3).
- **The bound and the tools.** A turn waiting longer than 30 minutes is a stalled line naming the check it waits
for, when that check started and the step the checker says it is at, raised as the condition
`delivery.<id>.stalled` (ADR 0421 §7). A tool of the gate reads every turn with the check it waits for; another,
on the operator's word, hands a waiting merge at once, the check it waited for then carried (ADR 0548 §4).
- **What the operator sees.** While the check gates merges, an approved merge into a repository under a running
check merges when that check ends, at most one check's length later; `deliveries` and `show` say it is approved
and waits for the check of the named delivery on the named repository, running since a given time. While it
does not, an approved merge goes in at once and no merge waits for a check. Either way no check is thrown away
for another merge, and the Approve is asked once per set of heads (ADR 0548 §9, ADR 0551 §2).
### The check from release 4
*ADR 0444, ADR 0445, ADR 0446, ADR 0447; research 058.*
@@ -434,20 +576,18 @@ From its delivery, a check is this:
and the check stops at their first failure, before the rest and before the composition. A kept verdict is
written once, by the checker's credential alone, from the result it read, and is addressed by its digest;
each check builds with a cache of its own, so one pull request cannot poison another's verdict (ADR 0444,
issue 482). With every check asked again on a trunk move (ADR 0395), such a re-ask runs only what the move
changed.
issue 482). A trunk move asks no check but a carry, which runs only what the move changed (ADR 0548 §5).
- **The composition check is the assigner's.** Whether the machines still compose with a change is checked by
the part that owns what each machine should run. The assigner reads the modules, fingerprints and manifests
the indexer holds and the builds the builder holds; the machines, their roles, assignments and settings, and
the grants and credentials it reads from the controller's facts snapshot and from the controller's buckets it
names in its manifest's `reads` (ADR 0237 §1, ADR 0408 §3). It clones no repository but at most the change's
the grants and credentials it reads from the grants part's state and the vault's accounts, and the rest from the buckets it
names in its manifest's `reads` (ADR 0237 §1, ADR 0408 §3, ADR 0547 §4). It clones no repository but at most the change's
own, and
composes only the machines that run or could run a module the change moves or adds, or whose grants or
providers it alters; for a group, once over every member's change, read from the indexer's candidates of the
group, and while the indexer holds none the group's check waits and says why. It composes with the function the
controller composes with, moved into mesh-sdk, so the two agree for one mesh-sdk version; its verdict names the
mesh-sdk version it composed with, and while the assigner and the controller run different versions the
controller's self-check raises a condition until they agree. No judge is built from a change: a change to the
group, and while the indexer holds none the group's check waits and says why. It composes with the function
every node agent composes with, mesh-sdk's composition, so the two agree for one mesh-sdk version; its verdict
names the mesh-sdk version it composed with. No judge is built from a change: a change to the
composition is proved by its own tests, a field only a newer composition reads goes out as optional first, and
the running composition refuses a manifest that uses a field it does not read, naming the field. What it fails on, and its warning past twelve rebuilt modules, are the composition check's, as they were the controller's composition check's (ADR 0446, ADR 0237 §2).
The assigner writes the verdict under the checker's hash of the heads it judged, in its own bucket, and the checker reads it into the
@@ -461,8 +601,8 @@ From its delivery, a check is this:
`mesh/repo-check` passed at its head against its trunk's head), mesh-delivery asks the operator's Approve of its
merge into main at once, on a channel that proves who answered (ADR 0274; Telegram today). The ask names the
pull request, its issues, the reviewer's verdict, the modules it moves and the trunk commit the checks judged. A
verdict that names a trunk commit the trunk had already left raises no ask: mesh-delivery asks that head's check
against the trunk's head, and its passing verdict raises the ask (ADR 0421 §1).
verdict that names a trunk commit the trunk had already left raises no ask: the checker carries it to the
trunk's head, and the carried verdict raises the ask (ADR 0421 §1, as ADR 0548 §8 changes it).
- **A group is asked once.** One ask names every member and its head, once every member is `ready` and the
composed check passed at those heads; one Approve covers them all (ADR 0421 §2). From the delivery of issues 1781 to
1783 the ask is the milestone's, made once when every one of its issues is ready, and a pull request linked to no
@@ -475,9 +615,12 @@ From its delivery, a check is this:
description is read (ADR 0493 §5, §6). The ask carries it; with none, or one naming another commit, the ask says so in its first line and
the operator decides (ADR 0421 §5).
- **The merge.** mesh-delivery merges through the forge's merge tool at the heads the Approve names, only while
they are still the heads and both checks passed against the trunk's head. When the trunk moved, the merge tool
asks the fresh check, and the event of its passing verdict asks the merge again; no timer asks it, and no person
rechecks (ADR 0421 §3, §12).
they are still the heads, their check passed against the trunk's head, and, while the check gates merges, no
check of other heads runs on a repository the merge goes into (*Nothing merges into a repository under check*,
above). When the trunk moved, the merge waits for the carried verdict, and the event of its line asks the merge
again; no timer asks it, no person rechecks, and the Approve is not asked again (ADR 0421 §3, §12, as ADR 0548
§8 changes them). The merge tool still refuses a verdict naming a trunk commit other than the trunk's head and
asks nothing (ADR 0534 §4); the carry makes the verdict current.
- **A push or a Decline ends the Approve.** A new head ends its ask or its Approve, for a group every member's, and
is said once on the pull request; the new head is asked again once its checks pass. A Decline merges nothing and
is said once (ADR 0421 §6).
@@ -493,7 +636,7 @@ From its delivery, a check is this:
- **A merge moves the source revision, and nothing collects it.** The gate writes a group's merged heads as one
revision of `source.main` in one write (ADR 0429 §11). There is no merge window, no batch and no plan: the
indexer writes each module's fingerprint at the merge, the builder claims each new fingerprint once, and the
assigner writes each want when its seven rules hold. A merge is never refused, held or dropped once it is made.
assigner writes each want when its six rules hold. A merge is never refused, held or dropped once it is made.
- **Two merges close together are two revisions.** The second's fingerprints replace the first's in the
indexer's modules; a node gets the newer fingerprint's want once its rules hold, and a fingerprint nobody wants
any more is never applied. Nothing waits for a window to close.
@@ -536,57 +679,146 @@ backup holder backs up nothing, and the have says so.
## Compositions, wants and the apply
*ADR 0360 §8, §9, ADR 0377, ADR 0429 §4, §6, ADR 0539 §1 to §7, §9, §14, §15.*
*ADR 0360 §8, §9, ADR 0377, ADR 0429 §4, §6, ADR 0539 §9, §14 to §16, ADR 0547 §2 to §9, ADR 0549, ADR 0555.*
- **A composition per module, node and fingerprint.** The controller composes one module's resources for one
node at one fingerprint with mesh-sdk's composition, the one the node-engine uses too, and writes it as
`<node>.<module>.<fingerprint>` in the bucket `composed-<node>`, which only that node's account reads, beside
the node's own composition `<node>.node` (adoption, firewall openings, accounts, the resolver, identities and
groups). Under the same key in `mesh-controller_composed`, which the assigner reads, it writes the
composition's digest and its judgement of the move from the composition the node runs now, as its have names
it: recreates a container, moves data, neither, or cannot be judged (ADR 0377 §3), rewritten when that have
changes. No composition's content is readable outside its node (ADR 0539 §1, §2).
- **Signed.** Each composition carries the signature of the controller's key or, made by hand, of the
operator's key; the node agent trusts exactly these two and applies nothing of a composition whose signature
fails or whose signer is neither. The operator's public key reaches a node in the switch's last declaration,
or at its adoption for a node adopted later (ADR 0539 §3).
- **Written on events, never on a timer.** A done build of an assigned module, an assignment written or ended,
a setting or given value, a secret rotated or moved, a credential issued, an outside acceptance, a node
adopted, taken or converged, a peer added to the hub and a change of the announced reads each make the
controller compose what they touch and write the compositions that differ. A change that keeps the
fingerprint is a new revision with a new digest, and reaches the node only when the assigner writes the want
again naming it, by the same rules as a new fingerprint (ADR 0539 §5).
- **Kept only while it can be wanted.** A composition and its digest are deleted when the fingerprint is no
longer among the module's five newest and no want names it; each bucket keeps five revisions of a key; a file
over 64 KiB travels by digest in `declaration-files-<node>`, removed a day after no composition of that node
names it. Neither bucket is a link of the chain: a composition is derived and can be composed again (ADR 0539 §4).
- **Credentials and grants on a done build.** When the builder's done key names a new fingerprint of an
assigned module, the controller issues the module's bus credential and writes `grants.<module>.<fingerprint>`
before any want can name it. The bus's user list is part of the bus module's composition: a revision that
changes only the user list is wanted at once, outside the person's hold, and the node agent applies it as a
file change and a reload (ADR 0539 §7).
- **The want names the composition.** A want `want.<node>.<module>` names a fingerprint and the digest of the
one composition the assigner allows for it; the node's own want `want.<node>.node` names the node's
composition (ADR 0539 §1, §6).
- **Each node composes its own.** For each want `want.<node>.<module>` the node agent composes that module for
its own node with mesh-sdk's composition from the manifest by digest the indexer wrote, the module's resolved
values read from mesh-config's resolved state filtered to its node, its secrets and bus credential asked of the
vault sealed to the node's key (ADR 0349 §11), its licences, and what its providers contribute; and the node's
own composition (adoption, firewall openings, accounts, the resolver, identities and groups) from the same
inputs. Of other nodes it reads only their public records and the manifests wanted on them, which its firewall
and resolver need. Nothing composed crosses the bus, so nothing composed is signed (ADR 0547 §2).
- **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 catalogue merge from
the node's placement file and said by `placement-changed` (ADR 0549 §2);
- **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;
- **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 catalogue, 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:
- 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
adopted with the modules taken on it, the tunnel found on it at adoption (no longer there to observe once taken
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
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);
- its pins by provision, and the seats its modules hold.
The catalogue'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
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
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).
- **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.
- **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 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>.>`.
Since the take-over the grants part's list is the one the bus loads; after a `hand-back` the controller's list
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 catalogue 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
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
follows it apart. A secret or credential the vault does not hold is such a gap: the vault names it missing, and
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 catalogue 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.
- **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).
- **Grants by the grants part.** A module's grant `grants.<module>.<fingerprint>`, which the assigner's sixth
rule reads, is written by the grants part from ADR 0547's third milestone; its account is minted by the vault
(ADR 0547 §4, §5, §9). Until that milestone the issuer ADR 0539 §7 names writes both, so a want never waits on a
key nobody writes.
- **The bus's user list.** The grants part composes the list with mesh-sdk's bus composer when an input changes,
writes it as an object sealed to the bus node's key and writes the key `user-list` with its digest, revision,
user count and the users left out; it says `user-list-changed`. The node agent of the bus node writes the bus
module's user-list file alone, mode 0600, only when its digest differs, and the bus reloads it in place; no want
is needed, no fingerprint moves and the bus is not moved. It says the list it wrote in the bus module's have:
where it was taken from, the digest wanted and the digest on the machine, the users and any refusal. A list
without the controller's own user, or one that drops more than half the users of the file on the machine, is
refused and the file kept. The watchman mirrors `user-list` as owed by the bus node, with a pulse of 60
seconds, so a list written and not loaded within a minute is stuck (ADR 0547 §7). Until the operator's
`take-over` the grants part runs in shadow, and the bus node takes the list from the bus module's composition
the running system writes for it, the user-list resource alone (ADR 0547 §8).
- **Memberships and assertions.** The grants part publishes each module's membership on the assignments stream
only when it differs from the one last published, and asserts every bucket, log and stream a manifest declares
when a new manifest digest is registered, with its limits; memberships are published from the first milestone
(the operator's one grant, 22:31 on 2026-10-25), while before `take-over` assertions are composed and compared
and nothing is asserted (ADR 0547 §5).
- **The want names the build.** A want `want.<node>.<module>` names the fingerprint, the artifact's digest, the
source revision and whether the module holds data; the node's own want `want.<node>.node` names the node
(ADR 0547 §2). A want to a node the catalogue places says so: it carries `self` and names no composition,
since the node composes it; every other want still names the one composition it allows, so nothing written
before (the controller's bus upgrade among it) reads a want differently. A person's yes to a move the node
judged is written into the want as `yes`, the digest of the composition allowed (ADR 0547 §3).
- **The apply, one module at a time.** At its first start the node agent takes the declaration it kept last as
what each module runs and writes each have. Then it applies a module only when its want's fingerprint or
digest differs from what runs, and only the composition whose digest the want names, removing only that
module's own resources: a shared path stays while any composition declares it. It writes the have with the
what each module runs and writes each have. Then it applies a module only when its want's fingerprint
differs from what runs or the composition it composed differs from the one it runs, and only that module,
removing only that module's own resources: a shared path stays while any composition declares it. It writes the have with the
fingerprint, the digest, the phase and the health after the module's start window. A failed apply is loud,
retried by the next change of the want or the composition, never by a clock, and fixed forward (ADR 0360 §8,
ADR 0539 §9).
- **Every want is owed.** The watchman mirrors every want as a pulse owed by that node's agent until its have
names the want's fingerprint, so a node that does not move is a stall (ADR 0539 §19).
- **A move a person must allow is asked before the want.** For a module of policy `record`, the assigner writes
no want naming a composition judged to recreate a container or move data, at a new fingerprint or a new
revision alike, until the operator answers its ask yes, on the phone or in the view; a move judged neither is
written as any want, and so is a module the node never ran; one that cannot be judged waits for the ask, which
says why. One exception, right after the switch: with no readable want, and a have from the old node-engine at
the module's newest fingerprint naming no composition, the want is written with no record hold, because the
node agent applies nothing when the composition equals the declaration it kept (ADR 0539 §9). Each judgement and the way it went are shown in the view and named in the ask (ADR 0539 §14).
- **The bus is never moved by the assigner.** The controller's `bus upgrade` checks as before and then asks the
operator to write the bus node's want with the operator's credential, recorded, until issue 1125 settles the
bus's replacement (ADR 0539 §15).
- **A move a person must allow is judged by the node and asked by the assigner.** The node agent applies ADR 0377
§3's tests to the composition it runs and the one it composed. For a module of policy `record`, a move judged to
recreate a container or move data, at a new fingerprint or a revision alike, is not applied: the node agent
writes its have with phase `waiting-yes` and the judgement, the assigner raises the ask, and on the operator's
yes, on the phone or in the view, writes the want again naming the yes, which the node agent reads from its own
want. A composition equal to what runs is no move and needs no ask. Each judgement and the way it went are shown
in the view and named in the ask (ADR 0547 §3).
- **The bus is never moved by the assigner.** A planned move of the bus is asked of the operator, who writes the
bus node's want with the operator's credential, recorded, until issue 1125 settles the bus's replacement (ADR
0539 §15). A new user list is no move of the bus (ADR 0547 §7).
- **The witness stays.** The node-engine's witness still puts back a controller or tool runner that does not
come up (ADR 0360 §9). A new node-engine runs on trial under its launcher until it writes its first have under
its own build (ADR 0539 §9).
@@ -648,20 +880,26 @@ 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). The assignments bucket keeps placement alone, and the rollouts bucket is kept read-only with its
history: no rollout is written after release 5's last plan (ADR 0539 §12).
(ADR 0429 §17). Placement is the catalogue'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
5's last plan (ADR 0539 §12).
| Link | Names | Written by | Bucket |
|---|---|---|---|
| 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` |
| assignment | the module and node, pinned or not, ended or not: placement alone, written by `assign` and `unassign` | the controller | `mesh-controller_assignments` |
| placement | each node's modules with their pins, host ports and bindings, from the catalogue's placement file of that node at each trunk merge of the catalogue | the indexer | the indexer's state `placement` |
| placement candidate | while a catalogue 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 catalogue (ADR 0549 §5) | the controller | `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
key per open pull request (the gate), and the compositions with their digests (`composed-<node>` and
`mesh-controller_composed`), which are derived state and no link of the chain (ADR 0539 §4). The buckets of plan
key per open pull request (the gate), and the grants part's state (`user-list`, the memberships it last
published, and from ADR 0547's third milestone the grants). A composition is no key of the bus once nodes compose
their own (ADR 0547 §2); until then the compositions with their digests are derived state and no link of the chain
(ADR 0539 §4). The buckets of plan
state, the merge window's keys, the silent sends' keys and a building plan's hold, are deleted by the controller
at its first start of release 6 (ADR 0539 §13).
@@ -669,8 +907,10 @@ at its first start of release 6 (ADR 0539 §13).
reads the buckets it names to decide; a part resumes from its keys after a restart. No table of the
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`; mesh-config reads the
assignments bucket.
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
milestone no part but the controller reads the assignments bucket, and the assigner until the third
(ADR 0549 §3).
- **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.
@@ -727,36 +967,34 @@ the operator for the bus node's want (ADR 0539 §15).
*ADR 0526, ADR 0539 §9, §10, §11, §15.*
- **The controller, the node-engine, the tool runner and the gate are ordinary modules.** Each new fingerprint
gets its composition from the running controller, its credential and grant when its build is done, and its
want from the assigner; the control-node is the controller's only node and so its canary. The node-engine's
- **The node-engine, the tool runner, the grants part and the gate are ordinary modules.** Each new fingerprint
gets its grant from the grants part, its account from the vault and its want from the assigner, and its node
composes it (ADR 0547 §2, §4, §5, §9). The node-engine's
witness puts back a controller or tool runner that does not come up (ADR 0360 §9), and a new node-engine is
rolled back by its launcher unless it writes its first have under its own build (ADR 0539 §9). The bus stays
outside the assigner (ADR 0539 §15).
- **A replaced controller or part carries on from the bus**: the keys, the streams and the durable consumers hold
every step, so nothing is lost or repeated (ADR 0406 §6). A controller composes, at its start, every assigned
module and the fingerprint of the controller it replaced, so the way back to that controller is one want
(ADR 0539 §10).
- **A build before its want.** The controller takes every module, the builder's own and the build modules among
them, from the builder's done key whose artifacts all name a reference; a module whose key is not yet done
every step, so nothing is lost or repeated (ADR 0406 §6). The way back to a replaced version is one want
naming its fingerprint, which its node composes again (ADR 0547 §2).
- **A build before its want.** Every module, the builder's own and the build modules among them, is wanted only
from the builder's done key whose artifacts all name a reference; a module whose key is not yet done
waits under the assigner's first rule and fails nothing (ADR 0524 §7, ADR 0539 §18).
- **The switch to release 6 is one plan, release 5's last.** Release 5's controller runs the group's plan with
the new node-engine and the new controller in one tier (issue 1942), so every node's last declaration names
the new node-engine and carries the operator's public key, and the control-node's names the new controller
too. Each node applies it with the node-engine it runs; the new controller writes every composition at its
start, and the assigner's wants equal what runs. For that plan one machine at a time yields: each new
node-engine is judged by its launcher, and the new controller by the node-engine's witness. A node away during
- **History: the switch to release 6 was one plan, release 5's last** (it is delivered; ADR 0547 takes the composing, signing and issuing it gave the controller away again). Release 5's controller ran
the group's plan with the new node-engine and the new controller in one tier (issue 1942), so every node's last
declaration named the new node-engine and carried the operator's public key, and the control-node's named the new
controller too. Each node applied it with the node-engine it ran; the new controller wrote every composition at
its start, and the assigner's wants equalled what ran. For that plan one machine at a time yielded: each new
node-engine was judged by its launcher, and the new controller by the node-engine's witness. A node away during
the switch finds its last declaration on the bus's stream of declarations when it returns; the controller
deletes that stream and the nodes' consumers only once every node's have names the new node-engine. The
merge waits until every node is present (ADR 0539 §11). The switch works only because the bus's user list that
release 5's controller composes already grants what release 6 uses: the controller writing compositions and
deletes that stream and the nodes' consumers only once every node's have names the new node-engine. The merge
waited until every node was present (ADR 0539 §11). The switch worked only because the bus's user list that
release 5's controller composed already granted what release 6 uses: the controller writing compositions and
their digests, each node-engine reading its own compositions and wants, asking its backup holder and hearing the
backup's end, the assigner reading the digests, and the operator's account writing the hand path. That grant is
delivered first by release 5's controller, like issue 1942, as issue 1945's fix before the switch. The
node-engine's setting `operator-signers` must hold the operator's public key before the switch, or no operator
key is trusted and the hand path cannot work (ADR 0539 §3). At its first start the new controller deletes the
buckets of plan state (the merge window's, the silent sends' and the building plan's hold) and says each
deletion as its event (ADR 0539 §13).
backup's end, the assigner reading the digests, and the operator's account writing the hand path; that grant was
delivered first, as issue 1945's fix before the switch. The node-engine's setting `operator-signers` holds the
operator's public key, or no operator key is trusted and the hand path cannot work (ADR 0539 §3). At its first
start the new controller deleted the buckets of plan state (the merge window's, the silent sends' and the
building plan's hold) and said each deletion as its event (ADR 0539 §13).
### Delivering the controller, the builder or a build module without the builder
@@ -765,8 +1003,8 @@ way is written down before the switch (ADR 0526, which ADR 0522 §6 asked of R4g
1. **A revert, with no build.** A broken builder or build module is undone by reverting its change: the earlier source's fingerprint has a done
key naming references, so the delivery of the revert builds nothing. It needs the earlier artifact in the artifact store, which keeps a
recorded build's artifact while it is among its module's five newest or a definition names it (ADR 0526 §1); from release 6 its
composition is kept while it is among the five newest too, so the assigner's want delivers it (ADR 0539 §4).
recorded build's artifact while it is among its module's five newest or a definition names it (ADR 0526 §1); the assigner's want
names it and its node composes it (ADR 0547 §2).
2. **A build by a session's call.** A session calls the build module's fetch and build tools on one named build node (for Go, a fetch and then
a build with publishing on); the build module pushes the artifact to the artifact store and answers its reference, as it does for the
builder (ADR 0526 §2, ADR 0525 §8). `mesh-pack`, in mesh-sdk, builds and packs a Go bundle with the Go build module's recipe, so a hand
@@ -774,13 +1012,10 @@ way is written down before the switch (ADR 0526, which ADR 0522 §6 asked of R4g
3. **A registration by reference, at the controller's terminal only.** The operator names the module, its repository, its path and each
artifact's reference at the controller's terminal, and the controller registers it with no builder key, recording the act with its why;
no verb reaches it (ADR 0526 §3).
4. **A controller that cannot take its own fix** is put back by keys written at the terminal, never by a declaration sent by hand: the
operator's credential writes `want.<control-node>.mesh-controller` to a fingerprint whose composition is kept, the replaced
controller's among them, since a controller composes that one at its start. For a hand build never composed, `mesh-hand-compose`, a
command of mesh-sdk beside `mesh-pack`, composes the module for the node from the controller's kept inputs with mesh-sdk's
composition, signs it with the operator's key and, with the operator's credential, writes it into `composed-<node>` and its digest
into `mesh-controller_composed`; the operator then writes the want naming that digest. Each act is recorded in the hand-act log, and
the node-engine keeps no path that applies a whole declaration (ADR 0539 §10, which changes ADR 0526 §4).
4. **A part that cannot take its own fix** is put back by a want written at the terminal, never by a declaration sent by hand: the
operator's credential writes `want.<node>.<module>` to a fingerprint whose build is done, and the node agent composes it for its node
as any want (ADR 0547 §2). Each act is recorded in the hand-act log, and the node-engine keeps no path that applies a whole
declaration (ADR 0526 §4).
5. **Copies kept outside the store.** Before a switch that moves the controller, the builder or a build module, the artifacts of the
running versions are kept outside the artifact store on two nodes, each by digest with its module, artifact, commit, Go version and the
steps that put it back, because the store's nightly collection removes a blob the controller did not record (ADR 0526 §6).
@@ -790,28 +1025,62 @@ way is written down before the switch (ADR 0526, which ADR 0522 §6 asked of R4g
What the build proves before this design is implemented (research 055's direction):
1. A feature across the controller and the catalogue with a new seat verb is delivered as one group, with no
`after:` line, one composed check, one Approve and one delivery plan: its merges are one batch, each module is
built once and sent once, its commits are carried by that one plan, and the plan names the edge that placed
each tier.
`after:` line, one composed check and one Approve: its merges move one source revision, each module it changes
is built once, and every want naming one of those fingerprints is written by the assigner's six rules, with
no order written by anyone.
2. An SDK change and a Go module using it are delivered as one group.
3. A data module is backed up before a delivery moves it, and a failed backup holds the delivery.
3. A data module is backed up by its node's backup seat before the node agent applies a new fingerprint of it,
and a failed backup holds that module on that node, its have in phase `held`, while every other want moves.
4. A build on any builder uses the installed toolchain with no container, and a toolchain update rebuilds only
modules of that language when its version changes.
5. Two merges into one repository within a minute leave each open pull request with one queued check, by the
stream's rule.
6. The operator's Approve is asked within seconds of the event of a pull request's checks passing, with no ready
mark, label or comment written by anyone.
7. A controller replaced in the middle of a rollout resumes it with no step lost or repeated.
8. The merge window closes by its key's expiry.
9. A hotfix to a module the running feature's delivery does not touch reaches the machines in minutes, beside it.
10. For any module on any node, one read of its assignment names the commit, build, artifact or package and the
rollout that put it there, and one read of that rollout lists every assignment it changed.
11. Two merges into one repository while a delivery of that repository runs are both delivered by the next plan,
with no person's release and nothing held in mesh-delivery.
12. No table in the controller's store holds builds, rollouts or assignments.
7. A controller, an assigner or a node agent replaced while a source revision is being delivered carries on from
its keys: no composition, want or have is lost, and no module is applied again whose have already equals its
want.
8. Nothing collects merges: a merge moves the source revision in one write, and the assigner writes the wants it
allows with no window, no key's expiry and no timer between them.
9. A hotfix to a module that the source revision being delivered does not touch reaches the machines in minutes,
beside it: its wants move on its own build, canary and providers.
10. For any module on any node, one read of its have names the fingerprint and composition it runs, one read of
its want the same or what it should run, and the fingerprint names the source revision, the build and its
artifact or package.
11. Two merges into one repository while its modules are moving are two source revisions: a node is wanted the
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 catalogue'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).
Tests 1, 3, 7, 8, 9, 10 and 11 were proved by the milestones that built them, on the plans and rollouts of their
time; from release 6, tests 18 and 19 prove what of them still holds without a plan.
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
it). Restated, each has its own proof, or says it has none yet:
- **Tests 1 and 8, and the assigner's half of test 7:** test 18 (scenario A, three repositories through the keys
alone; scenario D, the assigner replaced, in the assigner's integration test
`TestTheAssignerReplacedResumesWithNothingLostOrRepeated`). **Tests 8 and 12:** test 19 (the window, the batches,
the plans and their verbs deleted from the code).
- **Test 3:** the node-engine's integration test `TestNatsADataModuleAsksItsBackupFirstAndIsHeldWhenItFails`
(`cmd/mesh-host/nodeagent_integration_test.go`, R6-A AC5) proves the backup first and the hold with phase `held`.
That every other want moves meanwhile is not yet proved by a test; it shows on the live mesh at the first failed
backup, in the view's wants beside the haves.
- **Test 7, the node agent's and the controller's halves:** a node agent restarted on its own haves applies nothing
(`internal/agent/agent_test.go`, `TestStartingOnItsOwnHaveAppliesNothing`, at the unit level), and a controller
writes a composition once and again only when it changes (`composed_writer_test.go`,
`TestACompositionIsWrittenOnceAndAgainOnlyWhenItChanges`). A controller replaced while a source revision is being
delivered is not yet proved; it shows at the next delivery of the controller, in the view.
- **Test 9:** not yet proved under release 6. Each rule holding a want back on its own is a unit test of the assigner
(`internal/assign/wants_test.go`, `TestEachRuleHoldsTheWantBack`); the hotfix beside a moving source revision shows
live at the first such pair, read in the view's wants and haves.
- **Test 10:** a live read on 2026-10-26 at 01:20: one node's have of `avahi` named its fingerprint and composition digest,
its want named the same, and the builder's build of that fingerprint (`builder_build`) named its artifact by digest.
In all, 303 wants equalled their haves in fingerprint and digest.
- **Test 11:** half proved. The node agent applies only the composition its current want names
(`internal/agent/agent_test.go`, `TestTheRevisionAWantNamesIsAppliedFromHistory`); two merges of one repository
while its modules move are not yet proved live, and show at the first such pair in the view.
From ADR 0429 (research 063's done-when, and the live proof each of its releases names):
@@ -821,8 +1090,9 @@ From ADR 0429 (research 063's done-when, and the live proof each of its releases
15. A group of two repositories merges as one source revision, and every module's fingerprint the indexer
writes equals the controller's source fingerprint.
16. A merge builds nothing its check built, and a hotfix's build waits for no other build.
17. A day of deliveries shows no difference between the assigner's wants and what the controller sent that the
six rules do not explain.
17. A day of deliveries in release 5 showed no difference between the assigner's wants and what the controller
sent that the six rules then in force did not explain. That was proved in R4g; the differences bucket is kept
read-only as its evidence (ADR 0539 §8).
18. On the live mesh a milestone of three repositories reaches every node through the keys alone (research 063's
scenario A), and the assigner replaced in the middle of it resumes with nothing lost or repeated (scenario
D, once).
@@ -849,6 +1119,17 @@ development, the check's share about a minute):
line naming what was proved and each part's time; no repository a module is built from holds a
`merge-check.sh`.
From ADR 0548 and ADR 0551 (issue 568):
25. **One check per delivery.** A delivery group of two repositories is under check, and a lone pull request into
one of them is approved during the run. While the check gates merges, it waits, `mesh-delivery.show` says
what it waits for, and it merges when the group's verdict is written; while it does not, it merges at once and
the group's running check ends on the commit it read. Either way the group's verdict is then carried to the
new trunk commit with no test run for an unchanged digest, and the group merges on its Approve with no second
check. Read from `mesh-delivery.show`, the checker's verdict (`carried-from`, the levels' run and kept counts)
and the forge's statuses; issue 568 is resolved only once this has passed on the mesh (ADR 0548 §10, ADR 0551
§2).
## What retires
- The "release branches" and the controller's judge of them (ADR 0358, superseded by ADR 0401).
@@ -859,6 +1140,11 @@ development, the check's share about a minute):
- The ready mark: the label `mesh/ready`, the `ready at` comment, `mark-ready`, the forge module's
`ready-authors` setting, and mesh-delivery's subscriptions to labeled, unlabeled and commented events
(ADR 0421 §4, §11); a recheck by hand as a step of the flow (ADR 0421 §12).
- A check ended or asked again for a trunk move, and the re-ask of every open pull request's check at each move
(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).
- 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
@@ -906,7 +1192,7 @@ release reports the controller's code lines against research 063's baseline (acc
| 3 fix | A group approved together merges in one pass and goes out in one delivery plan, also when its members' tests read each other's repositories | 1 | mesh-catalog (mesh-delivery, gitea) | 1082, 1049, 1047, 1083, 1079 (1084 closed as its duplicate) |
| 5 | One read of a module on a node names the commit, build and rollout that put it there | 10, 12 | mesh-controller, mesh-catalog (mesh-config, nats) | 981 |
| 5b | Decided: building and delivering leave the controller for parts that watch and write keys in the repository `mesh-delivery`, landing in six releases (ADR 0429); the forge module makes that repository from its config, protected before any team reaches it (ADR 0428) | — (ADR 0429 names them: 13 to 20) | hq, mesh-catalog (gitea) | 1117, 1124, 1128 |
| 6 | Folded into release 4 by ADR 0429 §10 (the fetch over SSH in R4c, the cut-over in R4e): installed toolchains, builders fetching over SSH with their own key, and an SDK change shipping with its users are built in the builder's new home, so the builder is changed once | (2: proved by R4e; 4: in part by R4e, wholly by R4g) | — | 979, 971 (moved to release 4) |
| 6 | Folded into release 4 by ADR 0429 §10 (the fetch over SSH in R4c, the cut-over in R4e): installed toolchains, builders fetching over SSH with their own key, and an SDK change shipping with its users are built in the builder's new home, so the builder is changed once | (2: proved by R4e; 4: in part by R4e, wholly by R4g) | — | its issue is R4g's (the installed toolchains); the builder's own forge key moved to release 4 (R4c) |
| R1a | The node-engine, the controller and the parts can read one layout of the have, presence, pulse and stuck keys: mesh-sdk ships it before its users | — (13, 14 need it) | mesh-sdk | 1131 (key layouts) |
| R1b | Every node writes what it runs of each module, on its own keys and nobody else's, and the bus says to one part when a connection opens and closes | 13 in part | the node-engine (`mesh-host`), mesh-controller | 1132 (have keys), 1142 (the node-engine's check, its tests half), 1136 (the have bucket and its grants), 1145 (a module and a build per resource), 1146 (a new repository's first module registered at its merge's Approve), 1147 (the bus's connect events for the watchman), 1160 (a plan passes a machine silent at a middle tier), 1162 (the have bucket takes a node's facts) |
| R1c | The operator sees every module on every node, with its build, phase and health, in `mesh-delivery-cli` (aliased `nox-delivery`) and its web view | 13 | mesh-delivery (view) | 1134 (the view), 1137 (the alias, in the view's manifest) |
@@ -915,20 +1201,32 @@ release reports the controller's code lines against research 063's baseline (acc
| R2c | The controller says when it last heard each machine from presence alone, and no longer hears the alive message | 14 | mesh-controller | 1183 (every reader of last heard takes presence; the controller's alive side deleted) |
| R2d | The node-engine sends no alive message | 14 | the node-engine (`mesh-host`) | 1158 (the node-engine's alive side deleted) |
| R3a | The gate, the bridge, the indexer and the teller can read one layout of the source revision, candidates, modules, merges, asks, answers, owed acts, forge messages and conditions, and one function computes the source fingerprint: mesh-sdk ships them before their users (ADR 0437) | — (15 needs it) | mesh-sdk | 1200 (source revision, candidates, modules, forge messages), 1201 (merges, asks, owed acts, answers), 1202 (a condition on its one subject), 1203 (the source fingerprint, proven against three fingerprints the live builder recorded); ahead of it in mesh-controller, so those could be read: 1204 and 1208 (`builds` says each build's source fingerprint) |
| R3b | The controller finds the delivery seat's holder by its claim and records each build's fingerprint inputs; the router takes conditions on their one subject; the operator sees no change (ADR 0437) | — | mesh-controller, mesh-catalog (messenger) | 1210 (the controller's own path and the composition check's rule by the seat's claim), 1211 (a merge's announcement heard from the seat's holder), 1212 (the pull requests' keys read from the holder's bucket), 1213 (a merge's warrant read under the asker the announcement names: the teller, or mesh-delivery until R3d), 1214 (the builder computes the source fingerprint with mesh-sdk's function), 1215 (a build record carries its fingerprint's inputs), 1216 (a module may say a condition only under its own name, proven on the controller's grant); in the messenger: 1217 (the router takes a condition on its one subject), 1218 (the same over the bus); one delivery group. Its prepare step found that the controller hears no `transition` or `group` event, and that the router cannot refuse a condition under another module's name, the grant does: both go into R3c's insight on ADR 0437 with 1209 |
| R3c | The forge bridge, the indexer and the teller run beside today's mesh-delivery and write their keys; the view shows modules against the controller's source fingerprints; nothing is merged by them yet (ADR 0437) | 15 in part | mesh-delivery (forge bridge, indexer, teller, view), mesh-catalog (the forge module) | 1235 (the forge bridge writes pull messages on FORGE), 1236 (its merged message, with the trees of the merge commit), 1237 (its messages for the pull requests open at its start and each reconnect), 1238 (the indexer writes the candidate groups), 1239 (the indexer writes the modules' fingerprints at a trunk merge, and 1209: none for an image that compiles a Go program from a context), 1240 (the teller runs and carries nothing yet), 1241 (the view shows the candidate groups and each module's fingerprint against the controller's); in the forge module: 1234 (a merge commit's trees in one call, and the head branch in the open pull requests); one delivery group, mesh-catalog first. Its prepare step found three orders: the forge had no tool for a commit's trees, so the forge module joins the milestone as a member of its group; and mesh-sdk shipped two layouts before it, the controller's build record (1223) and a build's directory and a pull request's module directories (1233). It also found that the bridge's FORGE is a stream the others hear by `consumes`, not a bucket they `read`; that the broker test of the three credentials goes into R3d with the gate's (1243); and that the parts hand-write their bus connection until mesh-sdk gives one (1242): facts for ADR 0437's next insight, with R3d's |
| R3b | The controller finds the delivery seat's holder by its claim and records each build's fingerprint inputs; the router takes conditions on their one subject; the operator sees no change (ADR 0437) | — | mesh-controller, mesh-catalog (messenger) | 1210 (the controller's own path and the composition check's rule by the seat's claim), 1211 (a merge's announcement heard from the seat's holder), 1212 (the pull requests' keys read from the holder's bucket), 1213 (a merge's warrant read under the asker the announcement names: the teller, or mesh-delivery until R3d), 1214 (the builder computes the source fingerprint with mesh-sdk's function), 1215 (a build record carries its fingerprint's inputs), 1216 (a module may say a condition only under its own name, proven on the controller's grant); in the messenger: 1217 (the router takes a condition on its one subject), 1218 (the same over the bus); one delivery group. Its prepare step found that the controller hears no `transition` or `group` event, and that the router cannot refuse a condition under another module's name, the grant does: both go into R3c's insight on ADR 0437 with R3c's fingerprint issue |
| R3c | The forge bridge, the indexer and the teller run beside today's mesh-delivery and write their keys; the view shows modules against the controller's source fingerprints; nothing is merged by them yet (ADR 0437) | 15 in part | mesh-delivery (forge bridge, indexer, teller, view), mesh-catalog (the forge module) | 1235 (the forge bridge writes pull messages on FORGE), 1236 (its merged message, with the trees of the merge commit), 1237 (its messages for the pull requests open at its start and each reconnect), 1238 (the indexer writes the candidate groups), 1239 (the indexer writes the modules' fingerprints at a trunk merge, and 1209: none for an image that compiles a Go program from a context), 1240 (the teller runs and carries nothing yet), 1241 (the view shows the candidate groups and each module's fingerprint against the controller's); in the forge module: 1234 (a merge commit's trees in one call, and the head branch in the open pull requests); one delivery group, mesh-catalog first. Its prepare step found three orders: the forge had no tool for a commit's trees, so the forge module joins the milestone as a member of its group; and mesh-sdk shipped two layouts before it, the controller's build record (1223) and a build's directory and a pull request's module directories (1233). It also found that the bridge's FORGE is a stream the others hear by `consumes`, not a bucket they `read`; that the broker test of the three credentials goes into R3d with the gate's (its issue is R3d's); and that the parts hand-write their bus connection until mesh-sdk gives one (1242): facts for ADR 0437's next insight, with R3d's |
| R3d | The gate's keys can carry what the forge needs and the controller answers a what-if as data, so the gate has what it reads before it runs; a failed delivery plan asks the operator Retry on the phone, and a seat whose holder is assigned nowhere passes to its only claimant; the operator sees only the Retry notice (ADR 0450, ADR 0449) | — | mesh-sdk, mesh-controller | in mesh-sdk: 1321 (a merge key carries the Approve's heads), 1322 (an owed act carries a status's target, a commit without a pull request, two comments), 1323 (a name a key cannot hold is escaped); in mesh-controller: 1324 (what-if as data), 1325 (the build seat reads the gate's pull keys), 1326 (the gate's buckets writable by its runner only), 1243 (the three parts' credentials); and, delivered first as feature no-terminal (ADR 0449): 1317 (the seat passes to its only claimant), 1314 (a failed delivery plan raises a condition), 1315 (Retry offered and performed), 1316 (a refused answer is told). Its prepare step found four things the gate reads that ship before it, which cut the former R3d in two (ADR 0450), and two steps on the path that needed the operator's terminal (ADR 0449) |
| R3e | A group reaches the mesh as one source revision written by the gate; the merge's Approve is asked by the teller and answered on the phone until issue 953; the view shows the open asks; today's module mesh-delivery is carried over into the gate when it first holds the seat and ends; no step needs the operator's terminal (ADR 0437, ADR 0450) | 15 | mesh-delivery (gate's parts: forge bridge, teller, indexer, view), mesh-catalog (the gate, mesh-delivery removed, mesh-issues), mesh-tools (the tool runner's waits) | opened when it is prepared, from R3d's survey; the controller's deletions it makes possible go to release 4 (1318) |
| R4a | The shared layouts of release 4 are on mesh-sdk's main before their users pin them: the catalogue, the validator and the composition over a plain input, the fingerprint's new form, and the key layouts of the builder, the checker, the kept verdicts, the manifests and the candidates; the node-engine reports the user namespaces capability (ADR 0453 clauses 1, 2, 3) | — (2 needs it) | mesh-sdk, the node-engine (`mesh-host`) | in mesh-sdk: 1353 to 1364, 1298 (the composition over a plain input); in the node-engine: 1308 (the user namespaces capability) |
| R3e | A group reaches the mesh as one source revision written by the gate; the merge's Approve is asked by the teller and answered on the phone until issue 953; the view shows the open asks; today's module mesh-delivery is carried over into the gate when it first holds the seat and ends; no step needs the operator's terminal (ADR 0437, ADR 0450) | 15 | mesh-delivery (gate's parts: forge bridge, teller, indexer, view), mesh-catalog (the gate, mesh-delivery removed, mesh-issues), mesh-tools (the tool runner's waits) | opened when it is prepared, from R3d's survey; the controller's deletions it makes possible go to release 4 (their issue is R4g's) |
| R4a | The shared layouts of release 4 are on mesh-sdk's main before their users pin them: the catalogue, the validator and the composition over a plain input, the fingerprint's new form, and the key layouts of the builder, the checker, the kept verdicts, the manifests and the candidates; the node-engine reports the user namespaces capability (ADR 0453 clauses 1, 2, 3) | — (2 needs it) | mesh-sdk, the node-engine (`mesh-host`) | in mesh-sdk: 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1298 (the composition over a plain input); in the node-engine: 1308 (the user namespaces capability) |
| R4b | mesh-controller and the node-engine use the catalogue, the validator and the composition from mesh-sdk and delete their copies; the controller composes every node both ways on the live facts and the two agree before this is delivered; it writes the grants and credential bindings the assigner reads; its unit level meets its bound (ADR 0453 clauses 1, 3) | — | mesh-controller, the node-engine (`mesh-host`) | 1375 (to be split when it is prepared), 1337 (the unit level's bound) |
| R4c | The new parts run dark beside today's: the indexer writes the manifests by digest and the fingerprints in the new form beside today's (its own, first delivery), then the build modules (the Go build module, the Node and TypeScript build module: toolchain, sandbox, fetch, digest, test, build), the builder, the checker writing `mesh/check` as a status the forge does not require, and the assigner's composition check writing into the checker's report; the build seat's accounts are decided by a record before the build modules' pull requests are opened (ADR 0453 clauses 4, 6, 7, 8, 11, 12, 16; ADR 0465 §4); the builder is ADR 0478's. It is delivered in ADR 0465 §5's order: mesh-sdk's layouts; the forge bridge and the indexer with the controller's facts in format 2; the build modules; the builder; the checker; the bus user list's composer moved into mesh-sdk; the assigner's composition check; the fetch and run steps. The build modules each run as an account of their own with a forge key known read-only on one forge user (ADR 0466); each builds with the node's installed toolchain at the agreed version and does not refuse on a node with another (ADR 0477, issue 1572), answers a long step with its id and finishes it on a key, is called on a named machine, and runs on the four nodes that run the build agent today (ADR 0469) | 22, 23 (measured on the dark run, proved in R4f) | mesh-delivery (indexer, builder, checker, assigner's composition check), mesh-catalog (build modules, ssh-client, gitea), mesh-controller (the build agent's whole-check tests, the script's steps as tests), mesh-sdk | 971 (its record first), 1297 (the assigner's composition check), 1309 (the fetch and run steps), 1287, 1288, 1289, 1290, 1291, 1292 (the checker; the build modules run the tests), 1251; the steps of 1293 and 1294 that are no test become tests in R4c to R4e; the rest opened when it is prepared |
| R4d | Four modules stop being built from a Dockerfile: route-proxy, nats, n8n and mail's autoconfiguration module (automx, replaced by a Go program), each rework its own release step, nats in a delivery of its own; free between R4a and R4e and never in the same delivery as another release (ADR 0452, ADR 0453 clauses 13, 14, 16) | — | mesh-catalog, mesh-controller (route-proxy), the node-engine (`mesh-host`: n8n's supplementary group) | the four image reworks, opened when it is prepared |
| R4e | The controller asks no build: a build is keyed by its fingerprint, the new form becomes the only one as one group, and a merge builds nothing its check built; builders fetch over SSH, so an SDK change ships with its users; the vendored copies leave mesh-controller and mesh-sdk; delivered only once the builder's artifact digests equal build-agent's (ADR 0453 clauses 2, 4, 5, 6) | 16, 2, 4 in part | mesh-controller (builds are no longer asked), mesh-delivery (builder), mesh-sdk | 979 (the installed toolchains; closed with the toolchain images in R4g), 1304, the controller asking no build (opened when it is prepared) |
| R4f | A pull request carries one required status that says what it proved and how long each part took; the controller's composition check and its replays are deleted with the two old statuses; the controller's deletions R3e makes possible go; each level's times are measured, and a bound fails a repository from the first check that meets it (ADR 0447, ADR 0450 §8, ADR 0453 clauses 4, 9, 10) | 21, 22, 23, 24 (the status; the scripts' absence is proved in R4g) | mesh-delivery (checker, assigner), mesh-controller (composition check, replays, `delivery-plan`, `forget-checks`, the asker `mesh-delivery` deleted), mesh-catalog (gitea) | 1299 (the composition check deleted; closes issue 709, the replays' socket), 1300, 1301, 1302 (one status), 1305 (ADR 0482 §8), 1318 |
| R4c | The new parts run dark beside today's: the indexer writes the manifests by digest and the fingerprints in the new form beside today's (its own, first delivery), then the build modules (the Go build module, the Node and TypeScript build module: toolchain, sandbox, fetch, digest, test, build), the builder, the checker writing `mesh/check` as a status the forge does not require, and the assigner's composition check writing into the checker's report; the build seat's accounts are decided by a record before the build modules' pull requests are opened (ADR 0453 clauses 4, 6, 7, 8, 11, 12, 16; ADR 0465 §4); the builder is ADR 0478's. It is delivered in ADR 0465 §5's order: mesh-sdk's layouts; the forge bridge and the indexer with the controller's facts in format 2; the build modules; the builder; the checker; the bus user list's composer moved into mesh-sdk; the assigner's composition check; the fetch and run steps. The build modules each run as an account of their own with a forge key known read-only on one forge user (ADR 0466); each builds with the node's installed toolchain at the agreed version and does not refuse on a node with another (ADR 0477, issue 1572), answers a long step with its id and finishes it on a key, is called on a named machine, and runs on the four nodes that run the build agent today (ADR 0469) | 22, 23 (measured on the dark run, proved in R4f) | mesh-delivery (indexer, builder, checker, assigner's composition check), mesh-catalog (build modules, ssh-client, gitea), mesh-controller (the build agent's whole-check tests, the script's steps as tests), mesh-sdk | 971 (its record first), the assigner's composition check (its issue is R4g's), 1309 (the fetch and run steps), 1287, 1288, 1289, 1290, 1291, 1292 (the checker; the build modules run the tests), 1251; the steps of 1293, and of R4g's issue on the old checks, that are no test become tests in R4c to R4e; the rest opened when it is prepared |
| R4d | Done (2026-10-21 to 2026-10-23): three modules stopped being built from a Dockerfile, each its own release step: route-proxy, nats and mail's autoconfiguration module (automx, now a Go program) run a pinned stock image with their Go program mounted as an archive (ADR 0480); the image kind is allowed again, built rootless by the image build module (ADR 0487); route-proxy and nats were one delivery group, nats upgraded once on the bus at the operator's moment after a verified snapshot, automx went first alone (ADR 0497). n8n left R4d by the operator's word and keeps its own Dockerfile until R4g (ADR 0506) | — | mesh-catalog, mesh-controller (route-proxy), the node-engine (`mesh-host`: the container groups field, kept unused) | issues 1632 (automx), 1633 to 1635 (route-proxy), 1636 to 1638 (the container groups field), 1640 to 1642 (nats), all resolved and delivered; 1639 and 1755 (n8n) wontfix |
| R4e | The controller asks no build: a build is keyed by its fingerprint, the new form becomes the only one as one group, and a merge builds nothing its check built; builders fetch over SSH, so an SDK change ships with its users; the vendored copies leave mesh-controller and mesh-sdk; delivered only once the builder's artifact digests equal build-agent's (ADR 0453 clauses 2, 4, 5, 6) | 16, 2, 4 in part | mesh-controller (builds are no longer asked), mesh-delivery (builder), mesh-sdk | the installed toolchains (their issue is R4g's), 1304, the controller asking no build (opened when it is prepared) |
| R4f | A pull request carries one required status that says what it proved and how long each part took; the controller's composition check and its replays are deleted with the two old statuses; the controller's deletions R3e makes possible go; each level's times are measured, and a bound fails a repository from the first check that meets it (ADR 0447, ADR 0450 §8, ADR 0453 clauses 4, 9, 10) | 21, 22, 23, 24 (the status; the scripts' absence is proved in R4g) | mesh-delivery (checker, assigner), mesh-controller (composition check, replays, `delivery-plan`, `forget-checks`, the asker `mesh-delivery` deleted), mesh-catalog (gitea) | 1300, 1301, 1302 (one status); the composition check deleted (closing issue 709, the replays' socket), ADR 0482 §8 and the controller's deletions are R4g's issues |
| 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 the controller signs for it, 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 (compositions, credentials on a done build; plans deleted), 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), 1945's fix before the switch (the bus's user list grants what release 6 uses; delivered first), 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), 1945 (the controller writes compositions and grants; the plans and the push deleted), 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) |
| 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 catalogue 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 |
Release 5 (the assigner's wants, compared only) is not a milestone of its own: ADR 0524 §1 folds it into R4g, whose row above holds
An issue is in one row only (ADR 0493 §2): where a row named an issue another row names too, the last row keeps it and the
earlier row says so in words (the operator, 2026-10-26). Milestone M1 makes each row a record of the issue tracker
(issue 1781): once its seed has run, the tracker is the only list of a milestone's issues, `issue-tracker.milestones`
reads them, and this table keeps its rows without the Issues column (the operator, 2026-10-26).
Milestones L1 to L3 are the three of "The controller leaves build and delivery" above (ADR 0547 §11), given rows on
2026-10-26 so that a reader of this table, the knowledge base's `progress` among them (issue 1947), sees the work that
runs; 1945 is L1's and 1126 L2's only, as an issue belongs to at most one milestone (ADR 0493 §2); R6, delivered,
names the parts of 1945 it carried in words. Release 5 (the assigner's wants, compared only) is not a milestone of its own: ADR 0524 §1 folds it into R4g, whose row above holds
it. Release 6 is milestone R6, prepared on 2026-10-24 with ADR 0539. Its prepare step found six repositories where its row first
named three: the gate's code is in mesh-catalog, mesh-sdk holds the composition's layout and the seat verb tables, and hq the design
and the glossary.
@@ -941,7 +1239,7 @@ is issue 1055. Acceptance test 6 spoke of a ready mark when milestone 2 was deli
the Approve, it is proved again by milestone 3.
R4's prepare step on 2026-10-20 cut release 4 by its orders into R4a to R4g, each one release (ADR 0453, the operator's Approve): the SDK ships before its users, the new parts run dark before anything relies on them, the status switch follows a measured run, and the scripts and build-agent go last;
R4d, the four image reworks, has no order of its own and may be delivered at any point between R4a and R4e. Where a record dates a rule by release 4, it reads the R4 release that builds the rule (ADR 0453 clause 15).
R4d, the three image reworks (route-proxy, nats, automx; n8n left by ADR 0506), had no order of its own and was delivered between R4a and R4e. Where a record dates a rule by release 4, it reads the R4 release that builds the rule (ADR 0453 clause 15).
## What is not decided here
+35 -4
View File
@@ -2,7 +2,7 @@
layer: to-be
status: in-progress
code: [mesh-catalog modules/mesh-issues, mesh-catalog modules/claude-code, mesh-catalog modules/messenger, mesh-sdk]
updated: 2026-10-11
updated: 2026-10-26
decisions:
- "ADR 0275"
- "ADR 0278"
@@ -13,6 +13,7 @@ decisions:
- "ADR 0291"
- "ADR 0294"
- "ADR 0300"
- "ADR 0493"
---
# 53 — The issue tracker
@@ -81,7 +82,10 @@ agent finds them with `mesh_search` ("open an issue") and calls them as `issue-t
| `set` | a number and any of `severity`, `needs`, `suggested-owner` | changes them as a person decides (§ 10) |
| `tracking` | optionally condition keys | for every condition key an issue links, the issues that link it and where each stands: status, needs, worker, fix (§ 12) |
| `groom` | optionally `confirm` and a pass's id | the duplicate check over the backlog (§ 9): without `confirm`, starts a dry run in the background and answers its id; with the id alone, its progress and findings; with `confirm` and the id, applies exactly that pass's findings, each checked again |
| `import` (slice 3) | optionally `dry-run`, a commit | reads every issue folder of hq through the records module and writes each as an issue at its own number; creates what is absent, fills a status the tracker holds empty, never overwrites, never advances the counter; run again, changes nothing and says so |
| `import` (slice 3) | optionally `dry-run`, a commit, and `milestones` with a design's number | reads every issue folder of hq through the records module and writes each as an issue at its own number; creates what is absent, fills a status the tracker holds empty, never overwrites, never advances the counter; run again, changes nothing and says so. With `milestones`, it seeds that design's milestone records from its table instead (§ 13) |
| `milestones` | optionally a design, a status, or one milestone as design/id | the milestone records, each with its issues and their count by status; one milestone with its issues' rows and its history (§ 13) |
| `milestone-set` | a design, an id, an outcome, optionally an order, or `announce` | makes a milestone record, or changes its outcome or order; with `announce`, says the record again as its event (§ 13) |
| `milestone-move` | a milestone as design/id, a status and a reason | opens or closes a milestone; closing is refused while an issue linked to it is neither resolved nor wontfix (§ 13) |
Each verb's description says in plain words what it does and what it refuses, without the mesh's internal names, so
an agent picks the right one from `mesh_search` alone. A refusal says what was wrong and what would be accepted.
@@ -100,8 +104,8 @@ the issue's worker, who works on it, as the worker states it
- `located-in`: entries of the registered list of owners (repositories and modules), never free text;
- `fixed-by` (pull requests or commits), `amended-design`, `replay` or `replay-none`, and a reason for `wontfix`;
- `notes`: appended, each with who wrote it, when, and the text;
- `links`, typed: to another issue (`duplicates`, `relates-to`), a pull request, a decision record, a design, a
condition key (the issue **tracks** that condition, § 12) or a delivery;
- `links`, typed: to another issue (`duplicates`, `relates-to`), a pull request, a commit, a decision record, a design,
a condition key (the issue **tracks** that condition, § 12), a delivery, or the one milestone it belongs to (§ 13);
- `documents`: the report, and a diagnosis and a resolution when written, each with its author and date, revised in
place with its history kept (ADR 0291);
- `record`: the path of the issue's folder in hq, while one exists; none is opened any more, and the folders are
@@ -203,6 +207,7 @@ worker; one that stops moves the issue on, or is stale a day later.
| The triage fills only what is empty, links no cycle, and an issue is ready only when its dependencies' fixes are delivered | the rows of ADR 0279's "How it is checked" |
| The duplicate check acts only on a clear duplicate, never on an older or a resolved issue, once per issue, loudly when it cannot judge | the rows of ADR 0278's "How it is checked" |
| An issue's documents are kept whole, with author, date and history, and the import of hq's folders loses nothing (ADR 0291) | the module's tests of `documents` and of the import, one case per document kind; the comparison of every issue with its folder before the folders are removed, written on issue 427. Tracked by issue 427 |
| An issue belongs to at most one milestone, linked only by the verb, and only to an open one (ADR 0493 §2 to §4) | the module's table test of the `milestone` and `commit` link kinds, one case per refusal; the test that the triage and the duplicate check never add a `milestone`, `pull-request` or `commit` link; one integration test that starts the module whole on a real bus and makes, links and refuses |
| Live | on the mesh, an agent calls `issue-tracker.open` and gets the next number; two sessions on two nodes open at the same moment and get two different numbers; `list` shows both |
## 8. The slices after the first
@@ -312,6 +317,32 @@ with no pull request open is **abandoned**.
**tracking line** in every condition's message; the words and the verdict it changes are its own (to-be 45 § 5).
The controller learns nothing of issues.
## 13. Milestones
**A milestone is a record of the tracker, made from its design's table** (ADR 0493 §1). The state `milestones`, which the
manifest declares beside `issues`, keeps one value per milestone, keyed by its design's number and its id: the id as the
design's table writes it, the outcome, its order in the table, its status (`open` or `closed`, with a reason), who made
and who last moved it and when, and the hq commit and path it was seeded from. It is written by compare-and-set at the
revision read, and its history is the key's kept values with the events `milestone-set` and `milestone-moved`.
**An issue's milestone is the issue's own link, not a list on the milestone.** The link kind `milestone` (its target
design/id, such as 47/R6) is written in the issue's log like any link, so the rule that an issue belongs to at most one
milestone (ADR 0493 §2) is checked inside that issue's one write, refused naming the milestone it is in; a link to a
milestone that does not exist or is closed is refused (§3: a fix found on the way joins the open milestone). A milestone's
issues are read from the issues in memory. Pull requests and commits are linked to the issue they fix the same way, with
the kinds `pull-request` and `commit` (§4); nothing reads them from a description, and the triage never adds one.
**The seed** is `import` with `milestones` and a design's number: it reads the table through the records module, makes
each record that is absent in the table's order, links each issue it names that has no milestone, refuses (and names)
an issue another milestone holds or two rows name, closes a milestone whose issues are all done (wontfix, or resolved
with every live fix delivered, or resolved with no live fix) when it refused none of its row's issues, and run again
changes nothing. Once a design is seeded, the tracker is the only list of its milestones'
issues: the design's table keeps its rows without an Issues column, and the knowledge base's `progress` reads
`milestones` (issue 1781).
Readiness, the one ask per milestone and its release are the delivery seat's (ADR 0493 §7 to §11, issue 1783); the
tracker gives them the records and links they read.
## What it leaves out
Priorities beyond severity, notifications beyond the `ready` and `ready-to-verify` events and the stuck-fix warning, full-text or semantic search, the forge's own issue lists, and an index of any kind: