Author SHA1 Message Date
mesh-tools d5d8212204 Merge pull request 'Playbooks 07 and 08: the session that opens an issue along the way fixes it' (#459) from playbook/fix-what-you-open into main 2026-10-21 12:47:52 +00:00
jochen aa56e9b9e7 Playbook 07: the fixed scope and fixing what you open agree, a blocking issue still goes in
mesh/merge-gate head aa56e9b9e7c5 merged into trunk abdd364f091d — pass: the change touches no module of the mesh's graph
mesh/repo-check head aa56e9b9e7c5 merged into trunk abdd364f091d — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 14:42:27 +02:00
jochen 4f8c469329 Playbooks 07 and 08: the session that opens an issue along the way fixes it
The operator, 2026-10-21: issues opened along the way were only listed and left. Small ones are fixed in between as small fixes, the rest right after the delivery, outside the running milestone's branch, so its fixed scope holds.
2026-10-21 14:40:56 +02:00
mesh-tools abdd364f09 Merge pull request 'Playbook 07: name a pull request's own issues as 'issue <n>', and no other' (#458) from playbook/pr-issue-words into main 2026-10-21 12:17:55 +00:00
jochen ad0a95dc0c Playbook 07: the plural 'issues <n>' is read too; 1654 was another milestone's issue
mesh/merge-gate head ad0a95dc0c1b merged into trunk ab3ac686dcdd — pass: the change touches no module of the mesh's graph
mesh/repo-check head ad0a95dc0c1b merged into trunk ab3ac686dcdd — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 14:06:36 +02:00
jochen c770bc1c50 Playbook 07: name a pull request's own issues as 'issue <n>', and no other
mesh-delivery reads a pull request's issues from the words 'issue <n>' in its description; okioki #9 listed its own bare and named two follow-ups that way, and the Approve's ask named the follow-ups.
2026-10-21 14:05:28 +02:00
mesh-tools ab3ac686dc Merge pull request 'Correct design 47's R4c row (installed toolchain) and move 1305 from R4e to R4f' (#457) from design/47-r4c-row into main 2026-10-21 11:40:39 +00:00
jschoubben 373b7e7dd1 Correct design 47's R4c row (installed toolchain, ADR 0477/0478) and move 1305 from R4e to R4f
mesh/delivery delivered
mesh/merge-gate out of date: judged against trunk 6eda56470131, the trunk is now ab3ac686dcdd; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 6eda56470131, the trunk is now ab3ac686dcdd; judged again on a recheck or when it is merged
2026-10-21 13:35:58 +02:00
mesh-tools 6eda564701 Merge pull request 'Add milestone F2b to design 60, and make every strict reader of manifests a release order in playbook 07' (#456) from design/60-milestone-f2b into main 2026-10-21 11:00:57 +00:00
jschoubben 7c8e1f521d Add milestone F2b to design 60, and write into playbook 07's prepare step that every strict reader of manifests ships a new word first
mesh/merge-gate head 7c8e1f521db9 merged into trunk a1188b64a431 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 7c8e1f521db9 merged into trunk a1188b64a431 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 12:53:37 +02:00
mesh-tools a1188b64a4 Merge pull request 'Mark design 61 designed now that ADR 0479 is accepted, and follow its last version' (#455) from design/61-adr-0479-accepted into main 2026-10-21 10:40:20 +00:00
mesh-tools 6db031f486 Merge pull request 'Design 62: name ADR 0481 accepted, and say the page reaches OkiOki only over the bus' (#454) from design/62-review-fixes into main 2026-10-21 10:39:21 +00:00
jschoubben be30b63976 Mark design 61 designed now that ADR 0479 is accepted, and follow its last version: PDF and JPEG only, the printer asks its own link
mesh/merge-gate head be30b63976de merged into trunk 6db031f486f4 — pass: the change touches no module of the mesh's graph
mesh/repo-check head be30b63976de merged into trunk 6db031f486f4 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 12:36:24 +02:00
jochen 993c7def3a Design 62: name ADR 0481 accepted, and say the page reaches OkiOki only over the bus
mesh/merge-gate head 993c7def3ae3 merged into trunk a2918df0aab2 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 993c7def3ae3 merged into trunk a2918df0aab2 — pass: its merge-check.sh passed
mesh/delivery delivered
The review of hq#453 after its merge found the design calling ADR 0481 proposed, and the operator's rule (the page uses the bus and makes no OkiOki call, 2026-10-21) only implied. A document crosses the bus in parts, as scanner.document does.
2026-10-21 12:32:31 +02:00
mesh-tools a2918df0aa Merge pull request 'Design 62: the bookkeeping portal (ADR 0481)' (#453) from design/62-bookkeeping-portal into main 2026-10-21 10:31:02 +00:00
jochen 46d6840c67 Design 62: the page is read-only and its reach internal before assignment (ADR 0481 v4)
mesh/merge-gate head 46d6840c67f2 merged into trunk 82343be78f51 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 46d6840c67f2 merged into trunk 82343be78f51 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 11:56:03 +02:00
jochen b717900700 Design 62: the bookkeeping portal (ADR 0481)
The operator asked for a page showing the business's income, expenses and to-do as the
bookkeeping service holds them now, on their own hardware: okioki moves whole to the home
server and okioki-web runs beside it, behind Keycloak, never changing what the accountant did.
2026-10-21 11:55:05 +02:00
mesh-tools 82343be78f Merge pull request 'Write design 61, the print and scan page, as the home of ADR 0479' (#452) from design/61-print-and-scan into main 2026-10-21 09:32:15 +00:00
mesh-tools 75f2cbb6d6 Merge pull request 'Name the accepted ADR 0468 in design 60 and break its building into three milestones' (#451) from design/60-adr-0468 into main 2026-10-21 09:27:16 +00:00
jschoubben 467a2e6c97 Credit restart and the settings read to ADR 0467, and only configure to ADR 0474
mesh/merge-gate head 467a2e6c9785 merged into trunk 75f2cbb6d6ba — pass: the change touches no module of the mesh's graph
mesh/repo-check head 467a2e6c9785 merged into trunk 75f2cbb6d6ba — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 11:18:56 +02:00
jschoubben 115df9fd69 Follow ADR 0479 version 6: the glass only until the feeder is decided, and how the upload bound holds 2026-10-21 11:18:13 +02:00
jschoubben c7bebf53d4 Answer the review: the page follows ADR 0474, and records who managed the printer 2026-10-21 11:16:48 +02:00
jschoubben e0118ca67e Write design 61, the print and scan page, as the home of ADR 0479 2026-10-21 11:15:36 +02:00
jschoubben 64e64ac69d Answer the review: credit the one-call print to ADR 0468, name F3's dependency and the parts verbs' exception
mesh/merge-gate head 64e64ac69dff merged into trunk a0ab91dcae1d — pass: the change touches no module of the mesh's graph
mesh/repo-check head 64e64ac69dff merged into trunk a0ab91dcae1d — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 11:12:24 +02:00
jschoubben e111fb875c Name the accepted ADR 0468 in design 60 and break its building into three milestones, cut by release order 2026-10-21 11:10:31 +02:00
mesh-tools a0ab91dcae Merge pull request 'Playbook 08: check a module compiles with go vet, never go build in its folder (issue 1577)' (#450) from fix/1577-no-build-in-module into main 2026-10-21 08:02:23 +00:00
jochen 9f0b0215ae Playbook 08: check a module compiles with go vet, never go build in its folder (issue 1577)
mesh/merge-gate head 9f0b0215ae6a merged into trunk 86fd4adf9756 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 9f0b0215ae6a merged into trunk 86fd4adf9756 — pass: its merge-check.sh passed
mesh/delivery delivered
go build in a module's folder writes the program beside go.mod, and two such programs were
committed on 2026-10-21, caught only by the review.
2026-10-21 09:58:44 +02:00
mesh-tools 86fd4adf97 Merge pull request 'Correct design 60: status proposed, each brother feature tied to the record that decides it (ADR 0474, ADR 0475)' (#449) from design/60-the-brother-module into main 2026-10-20 23:57:54 +00:00
jschoubben 0b21bd0b26 Name ADR 0475 in design 60, now its number is reserved
mesh/merge-gate head 0b21bd0b268f merged into trunk fa165392b12d — pass: the change touches no module of the mesh's graph
mesh/repo-check head 0b21bd0b268f merged into trunk fa165392b12d — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-21 01:55:22 +02:00
jschoubben e212e3841d Follow the scanning record's settled shape in design 60: a file under the home folder, the certificate as health 2026-10-21 01:54:01 +02:00
jschoubben 8d3a9011ac Mark design 60 proposed and tie each brother feature to the record that decides it
The review found features stated as in force that rest on records still proposed
(ADR 0468, ADR 0474 and the scanning record to be reserved), and rules without
the test that checks them.
2026-10-21 01:47:59 +02:00
mesh-tools fa165392b1 Merge pull request 'Design the brother module whole: scanning, the feeder, printing, settings read and configure' (#448) from design/60-the-brother-module into main 2026-10-20 23:46:03 +00:00
jschoubben b6e01318cf Design the brother module whole, so printer settings can be changed through the mesh without opening the device
mesh/merge-gate head b6e01318cff2 merged into trunk 80653c078de5 — pass: the change touches no module of the mesh's graph
mesh/repo-check head b6e01318cff2 merged into trunk 80653c078de5 — pass: its merge-check.sh passed
mesh/delivery delivered
The operator asked on 2026-10-21 to configure the printer and scanner easily, or through
the mesh's tools; the design names the configure verb, its deny list and the proposed
decision it rests on (ADR 0474, number to be reserved).
2026-10-21 01:43:14 +02:00
mesh-tools 80653c078d Merge pull request 'Playbook 07: read a hand push's plan first, and assign a new module to one machine first' (#447) from playbook-07/hand-push-and-first-assignment into main 2026-10-20 23:38:01 +00:00
mesh-tools ba9f063c77 Read a hand push's plan first, and assign a new module to one machine first, from the night's build-module faults
mesh/merge-gate head ba9f063c7761 merged into trunk 70eff93d0130 — pass: the change touches no module of the mesh's graph
mesh/repo-check head ba9f063c7761 merged into trunk 70eff93d0130 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-20 23:34:42 +00:00
mesh-tools 70eff93d01 Merge pull request 'Design 59: no grant split (ADR 0472), and milestone 2's issues as split at its prepare step' (#446) from design-59-no-grant-split into main 2026-10-20 21:43:16 +00:00
jochen 9a9934d47e Design 59: say each milestone 2 issue's order, as the tracker holds it
mesh/merge-gate head 9a9934d47ecc merged into trunk 34e3321c7fcf — pass: the change touches no module of the mesh's graph
mesh/repo-check head 9a9934d47ecc merged into trunk 34e3321c7fcf — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-20 23:37:14 +02:00
jochen 7f3a3113aa Design 59: no grant split (ADR 0472), and milestone 2's issues as split at its prepare step 2026-10-20 23:35:53 +02:00
mesh-tools 34e3321c7f Merge pull request 'Name ADR 0465, 0466 and 0469 in design 47, and write the day's lessons on watchers, pushes and what a merge moves into playbook 07' (#445) from design/47-adr-0465-0466-0469 into main 2026-10-20 19:48:09 +00:00
mesh-tools 2713e131b4 Name ADR 0465, 0466 and 0469 in design 47, and write the day's lessons on watchers, pushes and what a merge moves into playbook 07
mesh/merge-gate head 2713e131b437 merged into trunk def2c09308cd — pass: the change touches no module of the mesh's graph
mesh/repo-check head 2713e131b437 merged into trunk def2c09308cd — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-20 19:44:57 +00:00
mesh-tools def2c09308 Merge pull request 'Name ADR 0457 and ADR 0463 in design 47, and write the day's delivery lessons into playbook 07' (#444) from design/47-adr-0457-0463-and-lessons into main 2026-10-20 18:03:06 +00:00
mesh-tools 5705da30c0 Judge a catalogue removal by the controller's unit tests against the catalogue's branch, not by a try
mesh/merge-gate head 5705da30c006 merged into trunk a5c6c98ecca0 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 5705da30c006 merged into trunk a5c6c98ecca0 — pass: its merge-check.sh passed
mesh/delivery delivered
A controller try reads the catalogue's main, so it would have passed for mesh-catalog #381 (review round 1).
2026-10-20 17:59:47 +00:00
mesh-tools 1d7cfed47a Name ADR 0457 and ADR 0463 in design 47, and write the day's delivery lessons into playbook 07
Design 47's catch-up of merges nobody heard is decided by ADR 0457 and bounded by ADR 0463, so other sessions read them from its frontmatter. Playbook 07 gains four lessons from 2026-10-20: a Retry is never asked beside an Approve (issue 1408), an ask is named only after it is read on the router, a check's wait is read from its delivery and not the build queue, and a catalogue change removing a module is tried against the controller (issue 1406).
2026-10-20 17:57:57 +00:00
mesh-tools a5c6c98ecc Merge pull request 'Design 59: milestones 2 to 4 confirmed with their issues, and the mail modules' as-is design' (#443) from design-59-milestone-issues into main 2026-10-20 17:45:08 +00:00
jochen d2dccddc27 Say the notifier's bundle is its own process under its own account, as main.go says
mesh/merge-gate head d2dccddc2726 merged into trunk 0738fa1405dd — pass: the change touches no module of the mesh's graph
mesh/repo-check head d2dccddc2726 merged into trunk 0738fa1405dd — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-20 19:12:04 +02:00
jochen dcc6f185d4 Answer the review: the mail modules are separate modules, the notifier has no process, nothing future in the as-is; 1433 depends on none 2026-10-20 19:11:06 +02:00
jochen e925a843ce Write the mail modules' as-is design, so what design 59's milestone 2 replaces is recorded before it is built
Playbook 04 step 3: the one grants list that gives reading and sending at once, and mesh-mail-notifier's pop-up for every new mail, read from mesh-catalog main a67f0196 and the live mesh.
2026-10-20 19:08:13 +02:00
jochen 66bb013b85 Write milestones 2 to 4's issues into design 59, as the operator confirmed them
Playbook 04 step 6. Also names ADR 0378 §5 beside ADR 0440 §4 for the page's use of mail-reading, its writes with correct and undo, and issue 1419 in §8's last sentence, as the graduation's review noted.
2026-10-20 19:02:56 +02:00
mesh-tools 0738fa1405 Merge pull request 'Graduate research 066 into design 59: mail reading, mail signals, reading and sending granted apart, the mail conversation; Mail as the thirteenth domain' (#442) from research-066-graduate into main 2026-10-20 14:34:18 +00:00
jochen a0a4edabc9 Space mail reading's batches 160 s apart, as ADR 0459 §15 now says
mesh/merge-gate head a0a4edabc9df merged into trunk ae9768c2c115 — pass: the change touches no module of the mesh's graph
mesh/repo-check head a0a4edabc9df merged into trunk ae9768c2c115 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-20 16:25:02 +02:00
jochen ebc7665b2a Say that milestone 4 waits for the decision on its thread's caller 2026-10-20 16:22:53 +02:00
jochen f96f964a2d Answer the review: carry test 4 over as the direction words it, make milestone 4 wait for milestone 2, fix ADR 0461's clause numbers in the glossary 2026-10-20 16:21:49 +02:00
jochen 4a2c15f725 Graduate research 066 into design 59: mail reading, mail signals, granted reading apart and the mail conversation, with the Mail domain in the glossary
Research 066's confirmed direction becomes ADR 0458 to 0462. Design 59 gains the sections and milestones 2 to 4 they decide, the glossary gains Mail as the thirteenth domain with four homonym rows, and design 49 lists Config and Mail so its count matches the glossary.
2026-10-20 16:17:50 +02:00
mesh-tools ae9768c2c1 Merge pull request 'Cut design 47's release 4 into R4a to R4g, and date each 'release 4' rule by the R4 release that builds it (ADR 0453)' (#441) from design/47-release-4-cut into main 2026-10-20 06:14:18 +00:00
mesh-tools 440279fec4 Date the SDK-before-users order of design 47's release 1-2 note by R4e
mesh/merge-gate head 440279fec472 merged into trunk f09cc22d9915 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 440279fec472 merged into trunk f09cc22d9915 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-20 06:07:29 +00:00
mesh-tools 3439d19d06 Answer review round 1 of design 47's release 4 cut: dates of the status, the builder, test 4 and the issues 2026-10-20 06:07:17 +00:00
mesh-tools 89c4295f6d Name release 4's seven releases R4a to R4g in design 47's table (ADR 0453) 2026-10-20 06:00:37 +00:00
mesh-tools 3e061bbd99 Cut design 47's release 4 into R4a to R4g by the orders it hides (ADR 0453) 2026-10-20 06:00:11 +00:00
mesh-tools f09cc22d99 Merge pull request 'Cut design 47's R3d into R3d and R3e, and name the terminal-free fix in R3d (ADR 0450, ADR 0449)' (#440) from design/47-r3d-and-r3e into main 2026-10-19 21:45:15 +00:00
mesh-tools 6ffd3709d8 Cut design 47's R3d into R3d and R3e, and name the terminal-free fix in R3d (ADR 0450, ADR 0449)
mesh/merge-gate head 6ffd3709d8bf merged into trunk 990a77c7605e — pass: the change touches no module of the mesh's graph
mesh/repo-check head 6ffd3709d8bf merged into trunk 990a77c7605e — pass: its merge-check.sh passed
mesh/delivery delivered
R3d's prepare step found four things the gate reads that must ship before it, and two steps that needed the operator's terminal; R4 gains the controller's deletions (1318).
2026-10-19 21:27:20 +00:00
mesh-tools 990a77c760 Merge pull request 'Answer design 47's review for release 4's check (follow-up to #438)' (#439) from design/47-the-check-in-r4-review into main 2026-10-19 20:50:51 +00:00
jschoubben cc605e3b13 Answer design 47's review for release 4's check: the judge's account ends at release 4, the assigner's reads and version skew and field refusal stated, where acceptance tests 22 and 23 are measured, and what release 4 retires
mesh/delivery delivered
mesh/merge-gate out of date: judged against trunk 023ad0116034, the trunk is now 990a77c7605e; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 023ad0116034, the trunk is now 990a77c7605e; judged again on a recheck or when it is merged
2026-10-19 22:44:18 +02:00
mesh-tools 023ad01160 Merge pull request 'Design 47's release 4 gains the check: only what changed runs, tests by the language's build module, the composition by the assigner, one status (ADR 0444 to ADR 0447)' (#438) from design/47-the-check-in-r4 into main 2026-10-19 20:12:07 +00:00
jschoubben 9ffa60924e Graduate research 058 into design 47's release 4: a check runs only what changed, its tests by the language's build module, its composition by the assigner, in one status
mesh/merge-gate head 9ffa60924ee9 merged into trunk c6d36b2f32c3 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 9ffa60924ee9 merged into trunk c6d36b2f32c3 — pass: its merge-check.sh passed
mesh/delivery delivered
The check took 3 to 4.5 minutes on every change and paid the whole suite again on every re-ask; the operator's target leaves it about a minute. ADRs 0444 to 0447 decide how; the glossary gains composition check and kept verdict, and merge gate is struck once release 4 is delivered (issue 1303).
2026-10-19 22:03:44 +02:00
mesh-tools c6d36b2f32 Merge pull request 'Design the mail web client: mesh-mail-web on the private network behind Keycloak (design 59, ADR 0440)' (#437) from design/59-the-mail-web-client into main 2026-10-19 18:44:29 +00:00
jochen f6311a66bf Answer design 59's review: mesh-mail's caller check, tests for the send state and the seat's verbs, no tracker state in the design; milestone 1 starts
mesh/merge-gate head f6311a66bf66 merged into trunk bea864af8bcd — pass: the change touches no module of the mesh's graph
mesh/repo-check head f6311a66bf66 merged into trunk bea864af8bcd — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 20:36:39 +02:00
jochen c04c0ebb76 Name design 59's milestone issues, confirmed by the operator 2026-10-19 16:41:32 +02:00
jochen 491de157da Design the mail web client: mesh-mail-web on the private network behind Keycloak, reading and sending through mesh-mail (ADR 0440); cite ADR 0439 in the as-is of a module's state 2026-10-19 16:38:55 +02:00
mesh-tools bea864af8b Merge pull request 'Keep three lessons of feature mail-sender in playbook 07: a rule keyed on a shared property, a manifest word's first push, one watcher per wait' (#436) from docs/playbook-07-lessons-mail-sender into main 2026-10-19 13:30:09 +00:00
jochen 7ed83f4965 Keep three lessons of feature mail-sender in playbook 07: a rule keyed on a shared property, a manifest word's first push, one watcher per wait
mesh/merge-gate head 7ed83f4965de merged into trunk 71759e83f873 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 7ed83f4965de merged into trunk 71759e83f873 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 15:28:33 +02:00
mesh-tools 71759e83f8 Merge pull request 'Name R3c's issues and the forge module it takes in, and the orders its prepare step found (design 47)' (#435) from docs/r3c-row into main 2026-10-19 12:48:05 +00:00
jschoubben 3174d5aa6e Name R3c's issues and the forge module it takes in, and the orders its prepare step found (design 47)
mesh/merge-gate head 3174d5aa6ef5 merged into trunk e7c8ce274060 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 3174d5aa6ef5 merged into trunk e7c8ce274060 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 14:46:33 +02:00
mesh-tools e7c8ce2740 Merge pull request 'Keep the drawn overview of the playbooks in hq, for a later docs site' (#434) from docs/playbooks-page into main 2026-10-19 11:31:12 +00:00
jschoubben 5816ca669f Say the page's freshness is not yet checked (issue 1222), and fix 08's leftover 'reviewed after its push' (review round 1)
mesh/merge-gate head 5816ca669f20 merged into trunk f269e83f5d1a — pass: the change touches no module of the mesh's graph
mesh/repo-check head 5816ca669f20 merged into trunk f269e83f5d1a — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 13:29:22 +02:00
jschoubben afe83b9124 Keep the drawn overview of the playbooks in hq, for a later docs site 2026-10-19 13:27:55 +02:00
mesh-tools f269e83f5d Merge pull request 'Try before the review, fail first on behaviour, survey every reader of a changed value (playbooks 07 and 08)' (#433) from docs/review-speed into main 2026-10-19 11:08:50 +00:00
jschoubben 6c607504de Say a moved trunk is met by the merge check, not promised to step 3 (review round 2)
mesh/merge-gate head 6c607504de1b merged into trunk 7e1c0f4c1564 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 6c607504de1b merged into trunk 7e1c0f4c1564 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 13:05:09 +02:00
jschoubben 20c857443c Bring the branches up to the trunk before the try, and say the try at step 1's end runs the integration tests (review round 1) 2026-10-19 13:04:30 +02:00
jschoubben 7735cfd107 Try before the review, fail first on behaviour, survey every reader of a changed value (playbooks 07 and 08) 2026-10-19 13:03:28 +02:00
mesh-tools 7e1c0f4c15 Merge pull request 'Design 47: R3b's issues, and the two facts its prepare step found for R3c's insight on ADR 0437' (#432) from docs/design-47-r3b-issues into main 2026-10-19 09:53:21 +00:00
jschoubben 19a8e92801 Design 47: R3b's issues, and the two facts its prepare step found for R3c's insight on ADR 0437 (issues 1210-1218)
mesh/merge-gate head 19a8e9280103 merged into trunk 59c6954b0cf8 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 19a8e9280103 merged into trunk 59c6954b0cf8 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 11:51:15 +02:00
mesh-tools 59c6954b0c Merge pull request 'Design 47: R3a's issues, the two controller fixes it needed first, and R3c's open question on Go-compiling images' (#431) from docs/design-47-r3a-issues into main 2026-10-19 09:13:16 +00:00
jschoubben 95bf5cbf20 Design 47: R3a's issues, the two controller fixes it needed first, and R3c's open question on Go-compiling images (issues 1200-1204, 1208, 1209)
mesh/merge-gate head 95bf5cbf2089 merged into trunk f0bcc75fcfce — pass: the change touches no module of the mesh's graph
mesh/repo-check head 95bf5cbf2089 merged into trunk f0bcc75fcfce — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 11:09:15 +02:00
mesh-tools f0bcc75fcf Merge pull request 'Say in the as-is of bus logins that a module's process may have a login of its own (ADR 0436, ADR 0438)' (#430) from docs/process-accounts-as-is into main 2026-10-19 09:06:33 +00:00
jochen 550487fc90 Say in the as-is of bus logins that a module's process may have a login of its own (ADR 0436, ADR 0438)
mesh/merge-gate head 550487fc9018 merged into trunk 022392a22bb1 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 550487fc9018 merged into trunk 022392a22bb1 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 11:02:57 +02:00
mesh-tools 022392a22b Merge pull request 'Design 47: release 3 is the milestones R3a to R3d by ADR 0437, and answering asks in the view moves to R5' (#429) from docs/design-47-r3-cut into main 2026-10-19 08:22:06 +00:00
jschoubben 643ec2b1bf Design 47: release 3 is the milestones R3a to R3d by ADR 0437, and answering asks in the view moves to R5
mesh/merge-gate head 643ec2b1bf3a merged into trunk ee862d8e2260 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 643ec2b1bf3a merged into trunk ee862d8e2260 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 10:19:55 +02:00
mesh-tools ee862d8e22 Merge pull request 'Design 47: R2c is split by its order, the controller's last heard from presence before the node-engine stops sending alive (issue 1183)' (#428) from docs/design-47-r2c-split into main 2026-10-19 00:23:27 +00:00
jschoubben 292fae8863 Design 47: R2c is split by its order, the controller's last heard from presence before the node-engine stops sending alive (issue 1183)
mesh/delivery delivered
mesh/merge-gate out of date: judged against trunk f9139feb30c2, the trunk is now ee862d8e2260; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk f9139feb30c2, the trunk is now ee862d8e2260; judged again on a recheck or when it is merged
2026-10-19 02:12:16 +02:00
mesh-tools f9139feb30 Merge pull request 'Design 47: release 1-2 is the six milestones R1a to R2c, each one release; playbook 04 splits a milestone that holds a release order' (#427) from docs/design-47-milestones-r1a-r2c into main 2026-10-18 22:17:17 +00:00
jschoubben d10965a341 Design 47: release 1-2 is the six milestones R1a to R2c, each one release; playbook 04 splits a milestone that holds a release order (the operator, 2026-10-19)
mesh/merge-gate head d10965a34148 merged into trunk f7915f7f2952 — pass: the change touches no module of the mesh's graph
mesh/repo-check head d10965a34148 merged into trunk f7915f7f2952 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-19 00:11:02 +02:00
mesh-tools f7915f7f29 Merge pull request 'Playbook 07: the prepare step looks for the orders a release hides, and fixtures come from the writer's shape' (#426) from docs/prepare-checks-release-orders into main 2026-10-18 19:33:32 +00:00
jschoubben 86e66dd51f Playbook 07: the prepare step looks for the orders a release hides, and fixtures come from the writer's shape (design 47 release 1-2, 2026-10-18)
mesh/merge-gate head 86e66dd51fcf merged into trunk a353ed21c0a1 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 86e66dd51fcf merged into trunk a353ed21c0a1 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 21:26:06 +02:00
mesh-tools a353ed21c0 Merge pull request 'Design 47: release 1-2 cut by the orders its prepare step found, with its new decisions and issues' (#425) from docs/design-47-release-1-2-cuts into main 2026-10-18 18:36:19 +00:00
jschoubben 4a090df25a Design 47: release 1-2 cut by the orders its prepare step found, with its new decisions and issues (issue 1130)
mesh/merge-gate head 4a090df25a6a merged into trunk ea7c831196b6 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 4a090df25a6a merged into trunk ea7c831196b6 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 20:31:36 +02:00
mesh-tools ea7c831196 Merge pull request 'Design 47: the operator confirmed ADR 0429's releases as milestones; release 1-2's issues and their order' (#424) from docs/design-47-delivery-releases into main 2026-10-18 15:57:42 +00:00
jschoubben d81c23b65a Design 47: the operator confirmed the releases as milestones (2026-10-18); release 1-2's issues and their order
mesh/merge-gate head d81c23b65a55 merged into trunk 0f8d778ec25b — pass: the change touches no module of the mesh's graph
mesh/repo-check head d81c23b65a55 merged into trunk 0f8d778ec25b — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 17:55:21 +02:00
mesh-tools 0f8d778ec2 Merge pull request 'Design 47: the six releases of ADR 0429 as milestones; glossary: the delivery domain, its parts and their words' (#423) from docs/design-47-delivery-releases into main 2026-10-18 15:50:42 +00:00
jschoubben cd9336d05a Design 47 and glossary: review round 1 (the sections stay the design until their release, rule 4's step above zero, the Approve on the phone until issue 953, release of a milestone under The record)
mesh/merge-gate head cd9336d05aca merged into trunk ba127e29b796 — pass: the change touches no module of the mesh's graph
mesh/repo-check head cd9336d05aca merged into trunk ba127e29b796 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 17:48:15 +02:00
jschoubben 5c81f1170c Design 47: the six releases of ADR 0429 as milestones, milestone 6 folded into release 4; glossary: the delivery domain, its parts and their words (ADR 0428, ADR 0429)
Research 063 graduated into ADR 0429 and this design (playbook 02 step 3).
The design names the parts and the releases; its other sections hold until
each release is delivered and are rewritten as each is prepared (issue 1130).
The words the plans use are marked as going at release 6, not struck: they
stay true until that release is delivered.
2026-10-18 17:45:59 +02:00
mesh-tools ba127e29b7 Merge pull request 'Playbook 02: after three review rounds the lead fixes further findings itself; only a finding that changes what was decided goes to the operator' (#422) from docs/review-rounds-lead-fixes into main 2026-10-18 15:09:15 +00:00
jschoubben d0a2950bb0 Playbook 02: wrap the check line and name the step it means (review round 1)
mesh/merge-gate head d0a2950bb0c0 merged into trunk 42ecaa7ac419 — pass: the change touches no module of the mesh's graph
mesh/repo-check head d0a2950bb0c0 merged into trunk 42ecaa7ac419 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 17:05:46 +02:00
jschoubben 8e76098412 Playbook 02: after three review rounds the lead fixes further findings itself and proposes again; only a finding that changes what was decided goes to the operator (the operator, 2026-10-18) 2026-10-18 17:04:38 +02:00
mesh-tools 42ecaa7ac4 Merge pull request 'Playbooks 07 and 08: integration tests run once through mesh-controller.try before the pull request, never on the machine' (#421) from docs/integration-tests-through-try into main 2026-10-18 13:05:03 +00:00
jschoubben cec72d1dc9 Playbooks 07 and 08: integration tests run once through mesh-controller.try on the pushed branch before the pull request, never on the machine (the operator, 2026-10-18)
mesh/delivery delivered
mesh/merge-gate head cec72d1dc962 merged into trunk b98b93efc8b0 — pass: the change touches no module of the mesh's graph
mesh/repo-check head cec72d1dc962 merged into trunk b98b93efc8b0 — pass: its merge-check.sh passed
2026-10-18 15:02:56 +02:00
mesh-tools b98b93efc8 Merge pull request 'Playbooks 07 and 08: tests are written from acceptance criteria on the issue (issue 1118); design 47 gets milestone 5b and cites ADR 0426 (issue 1117)' (#420) from docs/acceptance-criteria-milestone-5b into main 2026-10-18 10:52:52 +00:00
jschoubben ec373556d0 Playbooks 07 and 08: an acceptance criterion is numbered and cited by its test as <issue>/AC<n>, so its check is a grep (review round 1)
mesh/merge-gate head ec373556d0bb merged into trunk 672011ab92ad — pass: the change touches no module of the mesh's graph
mesh/repo-check head ec373556d0bb merged into trunk 672011ab92ad — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 12:50:30 +02:00
jschoubben f242b8763b Design 47 builds with ADR 0426 (no timer where an event can end the wait) 2026-10-18 12:49:18 +02:00
jschoubben bb6af05f5c Playbooks 07 and 08: tests are written from acceptance criteria the larger model writes onto the issue (issue 1118); design 47 gets milestone 5b, the planning out of the controller (issue 1117) 2026-10-18 12:42:06 +02:00
mesh-tools 672011ab92 Merge pull request 'Design 55: designs and the glossary become records, and the knowledge base owns a design's state (ADR 0423, ADR 0424)' (#419) from design/055-designs-and-glossary-as-records into main 2026-10-18 02:28:14 +00:00
jochen 05ff547358 Design 55: hq owns part of the third slice's code, its checks and links (review round 1)
mesh/delivery delivered
mesh/merge-gate head 05ff547358fe merged into trunk 76a671b8c5da — pass: the change touches no module of the mesh's graph
mesh/repo-check head 05ff547358fe merged into trunk 76a671b8c5da — pass: its merge-check.sh passed
2026-10-18 04:25:11 +02:00
jochen 1def984c07 Design 55: designs and the glossary become records, and the knowledge base owns a design's state (ADR 0423, ADR 0424) 2026-10-18 04:23:10 +02:00
mesh-tools 76a671b8c5 Merge pull request 'Retire the word epic, and define milestone as a group of a design's issues delivered together (issue 1105)' (#418) from fix/1105-retire-epic into main 2026-10-18 02:10:49 +00:00
jschoubben 19bd7fd960 Retire the word epic, and define milestone as a group of a design's issues delivered together (issue 1105)
mesh/merge-gate head 19bd7fd96066 merged into trunk f82a90dfa021 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 19bd7fd96066 merged into trunk f82a90dfa021 — pass: its merge-check.sh passed
mesh/delivery delivered
The operator decided on 2026-10-18 that a domain word is used only when clearly defined. epic had three uses and no code; design 47 now names two ways, hotfix and feature.
2026-10-18 04:08:15 +02:00
mesh-tools f82a90dfa0 Merge pull request 'Playbook 07: a new integration test enters through the path the live trigger takes (issue 1086)' (#417) from fix/1086-test-enters-live-path into main 2026-10-18 00:23:21 +00:00
jschoubben 17c27ddf83 Playbook 07: a new integration test enters through the path the live trigger takes (issue 1086)
mesh/merge-gate head 17c27ddf834f merged into trunk 5c6b645c6ba9 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 17c27ddf834f merged into trunk 5c6b645c6ba9 — pass: its merge-check.sh passed
mesh/delivery delivered
Milestone 4's test saved its plan by hand, skipped the cut from a batch, and passed while the backup step never ran.
2026-10-18 02:19:20 +02:00
mesh-tools 5c6b645c6b Merge pull request 'Design 47: milestone 3 fix names issue 1079 (1084 was its duplicate)' (#416) from fix/design-47-milestone-row into main 2026-10-17 23:51:00 +00:00
jschoubben b6cf49d8ce Design 47: milestone 3 fix names issue 1079, of which 1084 was closed as a duplicate, and the forge module it touched
mesh/merge-gate head b6cf49d8ce12 merged into trunk 1484ff76826c — pass: the change touches no module of the mesh's graph
mesh/repo-check head b6cf49d8ce12 merged into trunk 1484ff76826c — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 01:49:37 +02:00
mesh-tools 1484ff7682 Merge pull request 'Design 47: milestone 4 delivered; a fix of milestone 3 comes next. Playbook 07: new integration tests are read against the code before the first push (issues 980, 1082)' (#415) from feat/milestone-4-delivered into main 2026-10-17 22:32:35 +00:00
jschoubben c10efa4d8c Playbook 07: the new test failed on its first two runs, not three
mesh/merge-gate head c10efa4d8c8f merged into trunk be5284f7b86c — pass: the change touches no module of the mesh's graph
mesh/repo-check head c10efa4d8c8f merged into trunk be5284f7b86c — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-18 00:30:41 +02:00
jschoubben 24fd567c31 Design 47: milestone 4 delivered and split; a fix of milestone 3 comes next. Playbook 07: new integration tests are read against the code before the first push (issues 980, 1082) 2026-10-18 00:29:35 +02:00
mesh-tools be5284f7b8 Merge pull request 'Say where nine half-built designs stand (issue 1072)' (#414) from fix/1072-design-states into main 2026-10-17 21:19:27 +00:00
jochen 21c4d212ec Correct three facts in the designs' states (issue 1072, review round 1)
mesh/merge-gate head 21c4d212ec8d merged into trunk 9d59c2ce1453 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 21c4d212ec8d merged into trunk 9d59c2ce1453 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 22:55:02 +02:00
jochen 1bd6f4611e Say where nine half-built designs stand (issue 1072)
The records cleanup found them saying proposed or designed although their
code is on main; each now says in-progress and what is built and left.
2026-10-17 22:53:10 +02:00
mesh-tools 9d59c2ce14 Merge pull request 'Playbooks 07 and 08 without the ready mark; a fixing agent runs no integration test; large issues split; the merge script ships first (issues 1022, 895)' (#413) from feat/one-group-one-plan into main 2026-10-17 20:10:56 +00:00
jschoubben 64e357da7d Playbook 07: a change the new merge script must judge ships the script first (issue 895)
mesh/merge-gate head 64e357da7d7e merged into trunk a6db6a70cffa beside mesh-catalog#352@07717974 +1 more — pass: the change touches no module of the mesh'…
mesh/repo-check head 64e357da7d7e merged into trunk a6db6a70cffa beside mesh-catalog#352@07717974 +1 more — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/one-group-one-plan delivered: every member is delivered
2026-10-17 21:32:35 +02:00
jschoubben 9a9a78f882 Playbook 07: report every ten minutes (the operator, 2026-10-17) 2026-10-17 21:28:46 +02:00
jschoubben e816b61cb7 Playbook 07: the prepare step splits an issue sized over an hour (the operator, 2026-10-17) 2026-10-17 21:25:39 +02:00
jschoubben 610be93803 Playbook 07: a fixing agent never runs an integration test locally (the operator, 2026-10-17) 2026-10-17 21:23:05 +02:00
jschoubben d5ddd1e715 Playbooks 07 and 08: no ready mark; the review record rides in the pull request and the Approve is asked once the checks pass (issue 1022, ADR 0421) 2026-10-17 20:55:47 +02:00
mesh-tools a6db6a70cf Merge pull request 'Playbook 07: a milestone has steps, runs the whole unit suite, writes its findings into its issues, watches on the smaller model; design 47 names milestone 2b and takes 895 (issue 1053)' (#412) from fix/1053-steps-not-phases into main 2026-10-17 18:29:24 +00:00
mesh-tools e44ec3f612 Merge pull request 'Playbook 07: a new node-engine key needs the controller first, on a branch of its own (issue 1051)' (#411) from fix/1051-pb07-controller-first into main 2026-10-17 18:28:25 +00:00
mesh-tools dbc7406490 Playbook 07: a new node-engine key needs the controller's pin moved first, and a release that must merge first has a branch name of its own (issue 1051)
mesh/merge-gate head dbc740649039 merged into trunk 90c67930969b — pass: the change touches no module of the mesh's graph
mesh/repo-check head dbc740649039 merged into trunk 90c67930969b — pass: its merge-check.sh passed
mesh/delivery delivered
Feature owned-paths (issue 1024) planned no controller release for a new key on directory resources, though the controller judges every machine with the node-engine's vendored validator; and the controller's release, on the feature's branch name, joined the catalogue's delivery group and could not merge before it.
2026-10-17 18:19:35 +00:00
jschoubben 2001d6e063 Playbook 07: a milestone has steps, runs the whole unit suite, writes its findings into its issues and watches on the smaller model; design 47 names milestone 2b and takes 895 into milestone 3 (issue 1053)
mesh/merge-gate head 2001d6e06334 merged into trunk e44ec3f61235 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 2001d6e06334 merged into trunk e44ec3f61235 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 20:19:16 +02:00
mesh-tools 90c6793096 Merge pull request 'Design 29: a folder a module owns says how it treats files it did not write (issue 1024)' (#409) from feat/owned-paths into main 2026-10-17 18:14:10 +00:00
mesh-tools 499407c669 Take in main up to 2cf1c64e24 (issue 1024)
mesh/merge-gate head 499407c66936 merged into trunk 2cf1c64e24f5 beside mesh-catalog#349@7104df6f — pass: the change touches no module of the mesh's graph
mesh/repo-check head 499407c66936 merged into trunk 2cf1c64e24f5 beside mesh-catalog#349@7104df6f — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/owned-paths delivered: every member is delivered
hq main moved after this change's check; it touches neither design 29 nor anything this change edits. The branch's tree is the reviewed design 29 change merged with main.
2026-10-17 18:09:09 +00:00
mesh-tools 2cf1c64e24 Merge pull request 'Playbook 07: the reviewer is kept for the whole release, and a watcher waits for its checks' (#410) from fix/pb07-reviewer-kept into main 2026-10-17 16:34:55 +00:00
jschoubben e1987b3654 Playbook 07: the reviewer is kept for the whole release, and a watcher waits for its checks (issue 1042)
mesh/merge-gate head e1987b365427 merged into trunk c87ab9af0f9a — pass: the change touches no module of the mesh's graph
mesh/repo-check head e1987b365427 merged into trunk c87ab9af0f9a — pass: its merge-check.sh passed
mesh/delivery delivered
The operator asked why review takes so long: a fresh reviewer per round or per mark reads
everything again, and a reviewer waiting on the forge's checks spends its run waiting.
2026-10-17 18:26:06 +02:00
mesh-tools c87ab9af0f Merge pull request 'Playbook 07: a design names its milestones' issues, never their delivery state' (#408) from fix/pb07-no-state-in-designs into main 2026-10-17 16:23:52 +00:00
mesh-tools 4e0dd1b0d3 Say in design 29 how a folder a module owns treats files it did not write (issue 1024, ADR 0422)
mesh/merge-gate out of date: judged against trunk 7a1d3d89f0b9, the trunk is now c87ab9af0f9a; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 7a1d3d89f0b9, the trunk is now c87ab9af0f9a; judged again on a recheck or when it is merged
mesh/delivery-group group feat/owned-paths checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
Design 29 is ADR 0422's home: it now says what `unknown` and `knows` mean on a directory under a home, that the found class no longer holds in ~/.ssh (ADR 0415, 0417), and lists ADR 0412, 0415, 0416, 0417 and 0422 among its decisions.
2026-10-17 16:23:05 +00:00
jschoubben b5d3f23947 Design 57: its milestones name their issues, not their delivery state (issue 1039, folded into 1038)
mesh/merge-gate head b5d3f2394798 merged into trunk 7a1d3d89f0b9 — pass: the change touches no module of the mesh's graph
mesh/repo-check head b5d3f2394798 merged into trunk 7a1d3d89f0b9 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 18:20:58 +02:00
jschoubben c753ff068a Playbook 07: a design names its milestones' issues, never their delivery state (issue 1038)
mesh/merge-gate head c753ff068a55 merged into trunk 7a1d3d89f0b9 — pass: the change touches no module of the mesh's graph
mesh/repo-check head c753ff068a55 merged into trunk 7a1d3d89f0b9 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
The tracker and the delivery records hold whether a milestone is delivered, when and by what;
a copy in the design's table is a central status kept by hand (AGENTS.md). Design 47 loses its
copied cells.
2026-10-17 18:18:45 +02:00
mesh-tools 7a1d3d89f0 Merge pull request 'Design 47: phase II release 1 delivered (issue 1025)' (#407) from fix/design-47-phase-2-release-1-delivered into main 2026-10-17 16:11:22 +00:00
mesh-tools cb79a28a80 Merge pull request 'Playbook 07: every wait on the wait list has a watcher agent on the bus' (#406) from fix/pb07-read-the-whole-log into main 2026-10-17 16:09:28 +00:00
jschoubben 0b9fda46cc Design 47: phase II release 1 delivered, 2026-10-17 (playbook 07, write-back)
mesh/merge-gate head 0b9fda46cc72 merged into trunk cb79a28a8062 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 0b9fda46cc72 merged into trunk cb79a28a8062 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 18:04:17 +02:00
jschoubben 6b58a6af82 Playbook 07: the glossary's word, the builder (review of #405)
mesh/merge-gate head 6b58a6af8246 merged into trunk c0b1c47be6b4 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 6b58a6af8246 merged into trunk c0b1c47be6b4 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 18:03:52 +02:00
jschoubben 9a39785116 Playbook 07: every wait on the wait list has a watcher agent on the bus (the operator, 2026-10-17)
mesh/merge-gate waiting in the build queue since 18:01
2026-10-17 18:00:35 +02:00
mesh-tools c0b1c47be6 Merge pull request 'Playbook 07: read a failed check's whole log; a change to merge-check.sh is judged by main's version' (#405) from fix/pb07-read-the-whole-log into main 2026-10-17 15:58:25 +00:00
jschoubben 06440c1522 Playbook 07: the build seat's holder, not a retired word (review of #405's check)
mesh/merge-gate head 06440c152298 merged into trunk cf73f9679771 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 06440c152298 merged into trunk cf73f9679771 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 17:56:21 +02:00
jschoubben 0e7eb9c2da Playbook 07: read a failed check's whole log; a change to merge-check.sh is judged by main's version
mesh/merge-gate head 0e7eb9c2dac8 merged into trunk cf73f9679771 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 0e7eb9c2dac8 merged into trunk cf73f9679771 — fail: its merge-check.sh failed: words: 1 problem(s)
mesh/delivery superseded: a newer head of the same pull request
2026-10-17 17:29:43 +02:00
mesh-tools cf73f96797 Merge pull request 'Design 47: one plan per group, no order lines, the Approve as the only gate (ADR 0418, 0419, 0421)' (#404) from fix/design-47-records-0418-0421 into main 2026-10-17 15:25:14 +00:00
jschoubben b0cfd08f7c Design 47: ADR 0420 accepted, ADR 0249 superseded, milestone 3's issues named (review of #404)
mesh/merge-gate head b0cfd08f7c97 merged into trunk aaa0647ec31c — pass: the change touches no module of the mesh's graph
mesh/repo-check head b0cfd08f7c97 merged into trunk aaa0647ec31c — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 17:20:16 +02:00
jschoubben 0a6613f0d4 Design 47: one plan per group, no order lines, the Approve as the only gate (ADR 0418, 0419, 0421)
mesh/merge-gate head 0a6613f0d4b8 merged into trunk aaa0647ec31c — pass: the change touches no module of the mesh's graph
mesh/repo-check head 0a6613f0d4b8 merged into trunk aaa0647ec31c — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-17 17:18:03 +02:00
mesh-tools aaa0647ec3 Merge pull request 'Design 47: milestones 1 and 2 delivered' (#403) from fix/design-47-milestone-2-delivered into main 2026-10-17 13:10:39 +00:00
mesh-tools de32fb82ea Merge pull request 'Playbook 07: the full report shows the whole design's milestones' (#402) from fix/pb07-report-shows-the-design into main 2026-10-17 13:08:59 +00:00
jschoubben 85a729c607 Design 47: milestones 1 and 2 delivered, 2026-10-17 (playbook 07, write-back)
mesh/merge-gate head 85a729c607a1 merged into trunk de32fb82ea73 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 85a729c607a1 merged into trunk de32fb82ea73 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 15:06:38 +02:00
jschoubben fb284fa6ee Playbook 07: the full report shows the whole design's milestones, the running one's issues under it (the operator, 2026-10-17)
mesh/merge-gate head fb284fa6ee2a merged into trunk 2b04a5f0a1aa — pass: the change touches no module of the mesh's graph
mesh/repo-check head fb284fa6ee2a merged into trunk 2b04a5f0a1aa — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 14:13:28 +02:00
mesh-tools 2b04a5f0a1 Merge pull request 'Playbooks 07 and 04: a milestone is one release, never cut for size or time' (#401) from fix/pb07-a-milestone-is-one-release into main 2026-10-17 11:50:34 +00:00
jschoubben addbd74c39 Playbook 04 and design 47: the milestone is one release, not a feature step (review of #401)
mesh/merge-gate head addbd74c3924 merged into trunk 0f63a73def93 — pass: the change touches no module of the mesh's graph
mesh/repo-check head addbd74c3924 merged into trunk 0f63a73def93 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 13:47:37 +02:00
jschoubben 6bf18062c4 Playbooks 07 and 04: a milestone is one release, never cut for size or time; milestones are cut by release order (the operator, 2026-10-17) 2026-10-17 13:47:21 +02:00
mesh-tools 0f63a73def Merge pull request 'Describe the mesh MCP server as it runs and mark its design implemented (issue 1008)' (#400) from fix/1008-mcp-server-shipped into main 2026-10-17 11:43:03 +00:00
mesh-tools d0ee14712e Merge pull request 'Playbooks 07 and 08: a repository's own clone stays on main (issue 1010)' (#399) from fix/1010-clone-stays-on-main into main 2026-10-17 11:40:24 +00:00
jochen f6199fafce As-is 13: fix five review findings on the mesh MCP server (issue 1008, review round 1)
mesh/merge-gate head f6199fafcee4 merged into trunk d0ee14712e18 — pass: the change touches no module of the mesh's graph
mesh/repo-check head f6199fafcee4 merged into trunk d0ee14712e18 — pass: its merge-check.sh passed
mesh/delivery delivered
node-tools still has a manifest (invokes *, waits, the mcp-endpoint provision) —
describe it in prose instead of saying it has none. The TypeScript sources
remain on main; only the built artifact is Go. The mesh-controller seat
answers 63 verbs as its own runtime, not 60-93 per node (that is node-tools'
own node-scoped seat verbs). Prefer reading mesh_overview live over a count
that drifts. An as-is document amends nothing — reworded accordingly.
2026-10-17 13:39:14 +02:00
mesh-tools d901156614 Merge pull request 'Playbook 07: the lead writes a design's state back (issue 1009)' (#398) from fix/design-status-written-back into main 2026-10-17 11:38:12 +00:00
jochen e934a0286f Playbook 08: check that the clone stays on main (issue 1010, review round 1)
mesh/merge-gate head e934a0286f7f merged into trunk d9011566146e — pass: the change touches no module of the mesh's graph
mesh/repo-check head e934a0286f7f merged into trunk d9011566146e — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 13:37:09 +02:00
jochen 8dbfd8d8ff Playbooks 07 and 08: a repository's own clone stays on main (issue 1010)
A clone left on an old work branch hands the next session stale playbooks
and designs as if current.
2026-10-17 13:36:37 +02:00
jochen 2cea6c9b0c Date design 57's delivered milestones (issue 1009, review round 2)
mesh/merge-gate head 2cea6c9b0cf4 merged into trunk 84b464d109c0 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 2cea6c9b0cf4 merged into trunk 84b464d109c0 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 13:35:56 +02:00
jochen 79dcc0a6ec Give design 57 its milestones and check every write-back moment (issue 1009, review round 1) 2026-10-17 13:35:20 +02:00
jochen 65f69d96a9 Describe the mesh MCP server as it runs and mark its design implemented (issue 1008)
mesh-console is gone: the tool runner (node-tools) is a core component on
every node, host-side and never a container (ADR 0175), announcing seven
tools by address (ADR 0195, 0367), not the five-tool container the old
as-is doc described. The to-be document's unsettled items get owners:
the mesh's own verbs already shipped (ADR 0154); identity and
per-assignment grants are design 58 milestone 6 (issue 883).
2026-10-17 13:35:12 +02:00
jochen b83a1e748e Playbook 07: the lead writes a design's state back (issue 1009)
Design 57 was merged as a release with three tasks left and still said
designed, so another session took it for unbuilt. Say it in-progress, with
what is left and when it is built.
2026-10-17 13:33:37 +02:00
mesh-tools 84b464d109 Merge pull request 'Playbook 07: one design is built at a time (issue 1007)' (#397) from fix/pb07-one-design-at-a-time into main 2026-10-17 11:26:12 +00:00
jochen 88d7db36c8 Playbook 07: the check counts milestone issues only (issue 1007, review round 1)
mesh/merge-gate head 88d7db36c866 merged into trunk c61f01d63eb8 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 88d7db36c866 merged into trunk c61f01d63eb8 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 13:23:21 +02:00
jochen 974b3e7d42 Playbook 07: one design is built at a time (issue 1007)
The operator decided on 2026-10-17 that a second design's milestones wait
until the design being built is delivered; nothing in the playbooks said so.
2026-10-17 13:22:41 +02:00
mesh-tools c61f01d63e Merge pull request 'Playbook 07: one milestone is one branch per repository' (#395) from fix/pb07-one-branch-per-milestone into main 2026-10-17 10:20:34 +00:00
mesh-tools 4325467b4d Merge pull request 'Design 58: milestones 11 to 14 from research 057, as-is 17, and role names in place of machine and login names' (#396) from design/058-milestones-11-to-14 into main 2026-10-17 10:19:33 +00:00
mesh-tools 0a010b8b79 Design 58: milestone 13 does not wait for milestone 7; until then nox's dangerous acts are refused (review round 1 of #396)
mesh/merge-gate head 0a010b8b79a1 merged into trunk 85f0aecf5ad6 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 0a010b8b79a1 merged into trunk 85f0aecf5ad6 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 10:17:00 +00:00
mesh-tools cc0503ef66 Design 58 and 36: role names for the machines and no operator logins, as a public repository needs (fixes what #394 wrote)
mesh/merge-gate head cc0503ef663a merged into trunk 85f0aecf5ad6 — pass: the change touches no module of the mesh's graph
mesh/repo-check head cc0503ef663a merged into trunk 85f0aecf5ad6 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-17 10:13:02 +00:00
mesh-tools 56d8d546c7 Design 58: milestones 11 to 14 from research 057 with their issues, and as-is 17 on today's accounts (playbook 04) 2026-10-17 10:10:42 +00:00
jschoubben edff1f25a1 Playbook 07: one milestone is one branch per repository, its steps reuse the name (the operator, 2026-10-17)
mesh/merge-gate head edff1f25a10b merged into trunk 4325467b4d59 — pass: the change touches no module of the mesh's graph
mesh/repo-check head edff1f25a10b merged into trunk 4325467b4d59 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 12:04:37 +02:00
mesh-tools 85f0aecf5a Merge pull request 'Design 58 and 36: identities carry logins, run for an identity, a bus password per identity (research 057)' (#394) from design/057-identities into main 2026-10-17 09:54:42 +00:00
mesh-tools 0dc8cab178 Design 58 and 36: cite ADR 0266's ways to root, and make acceptance tests 7 and 9 and the nox glossary entry agree with ADR 0411, 0412 and 0414 (review round 1)
mesh/merge-gate head 0dc8cab1781b merged into trunk 375e77690b8a — pass: the change touches no module of the mesh's graph
mesh/repo-check head 0dc8cab1781b merged into trunk 375e77690b8a — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 09:40:48 +00:00
mesh-tools 98b731d5d1 Design 58 and 36: identities carry logins, run for an identity, a bus password per identity (ADR 0411 to 0414, research 057)
mesh/merge-gate head 98b731d5d1ff merged into trunk 375e77690b8a — pass: the change touches no module of the mesh's graph
mesh/repo-check head 98b731d5d1ff merged into trunk 375e77690b8a — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-17 09:35:37 +00:00
mesh-tools 375e77690b Merge pull request 'Feature agents-write-git, step 2: design 29 cites ADR 0399 and ADR 0400 (issue 955)' (#390) from feat/agents-write-git-ssh into main 2026-10-16 22:31:07 +00:00
jschoubben 24701df23e Merge branch 'main' into feat/agents-write-git-ssh
mesh/merge-gate head 24701df23e8a merged into trunk dd5a06529556 beside mesh-catalog#340@ae9cda85 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 24701df23e8a merged into trunk dd5a06529556 beside mesh-catalog#340@ae9cda85 — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/agents-write-git-ssh delivered: every member is delivered
2026-10-16 22:27:02 +00:00
mesh-tools dd5a065295 Merge pull request 'Design 47's milestones and issues, and the as-is delivery as it runs today' (#393) from handoff/47-milestones into main 2026-10-16 22:09:55 +00:00
jschoubben 748641dd22 Review of #393: rollouts run one at a time today (issue 525); Approve on Telegram (issue 953 for the terminal); 976's place in the order; milestone 1 takes the rest of 525 and acceptance test 9
mesh/merge-gate head 748641dd22ae merged into trunk 73b9cc3257a1 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 748641dd22ae merged into trunk 73b9cc3257a1 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-17 00:06:59 +02:00
jschoubben 75d3ac0f82 Playbooks 04 and 07 after research 055: a milestone's shape from what-if; the Approve on every merge into main; when the two-step exception ends (issue 922)
mesh/merge-gate head 75d3ac0f8258 merged into trunk 73b9cc3257a1 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 75d3ac0f8258 merged into trunk 73b9cc3257a1 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-17 00:04:48 +02:00
jschoubben b6a191d0a6 Design 47's milestones and issues (playbook 04), and the as-is delivery rewritten from research 055's findings
mesh/merge-gate head b6a191d0a6dc merged into trunk 73b9cc3257a1 — pass: the change touches no module of the mesh's graph
mesh/repo-check head b6a191d0a6dc merged into trunk 73b9cc3257a1 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
The operator confirmed six milestones on 2026-10-17; issues 973-981 and 971
carry their repository parts, linked depends-on in order. The as-is described
the predecessor's pipeline; it now says what runs today, which design 47
replaces.
2026-10-17 00:04:00 +02:00
mesh-tools 73b9cc3257 Merge pull request 'Playbook 08: a fix that moves a file to a new filler compares the values first (issue 957)' (#391) from fix/957-playbook-08-first-fill into main 2026-10-16 21:56:23 +00:00
mesh-tools f795356ae1 Merge pull request 'Design 47 rewritten from research 055's decisions: hotfix, feature and epic from the trunk, delivery on the bus' (#392) from graduate/055-nats-native-delivery into main 2026-10-16 21:50:05 +00:00
jschoubben aa4bfe289e Design 47 and the glossary: the reviewer's findings (three work queues, a feature in one repository, what-if, ADR 0406 clause numbers, backups by protection, no suite-time claim, the Go module's judge account)
mesh/merge-gate head aa4bfe289ee4 merged into trunk f9c240a957b8 — pass: the change touches no module of the mesh's graph
mesh/repo-check head aa4bfe289ee4 merged into trunk f9c240a957b8 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-16 23:44:59 +02:00
jschoubben 6f0db74ee4 Design 47: the SDK commits the catalogue accepts (ADR 0402 §4)
mesh/merge-gate head 6f0db74ee47f merged into trunk f9c240a957b8 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 6f0db74ee47f merged into trunk f9c240a957b8 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-16 23:42:56 +02:00
jschoubben e7854a31a6 Design 47: a member naming the SDK member's head is proven by its own merge check (ADR 0402's review)
mesh/merge-gate head e7854a31a6a9 merged into trunk f9c240a957b8 — pass: the change touches no module of the mesh's graph
mesh/repo-check head e7854a31a6a9 merged into trunk f9c240a957b8 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-16 23:41:37 +02:00
jschoubben 8bfb11da2c Design 47 and the glossary: the batch's record in the chain is the rollout (ADR 0408's review) 2026-10-16 23:40:11 +02:00
jschoubben 96f2ae72c1 Rewrite design 47 from research 055's decisions: hotfix, feature and epic from the trunk, delivery on the bus
ADR 0401-0408 graduate research 055: no release branch, two more ordering
rules, the composed check as each member's gate, a backup before the send,
installed toolchains, events and work queues on the bus, the merge window
as keys, and one chain kept on the bus alone. Design 47 is rewritten from
them (one design per thing); the glossary takes hotfix, feature, toolchain,
milestone and epic and retires release branch; design 18 loses the
toolchain image; playbooks 07 and 08 drop the release-branch wait.
2026-10-16 23:39:12 +02:00
nox 78353ad272 Playbook 08: a fix that moves a file to a new filler compares the values first (issue 957)
mesh/merge-gate head 78353ad272a3 merged into trunk f795356ae1d6 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 78353ad272a3 merged into trunk f795356ae1d6 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-16 22:37:59 +02:00
nox fa5fd9fc69 Cite ADR 0399 and 0400 in design 29: the account's forge key lives in ~/.ssh (issue 955)
mesh/merge-gate out of date: judged against trunk f9c240a957b8, the trunk is now f795356ae1d6; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk f9c240a957b8, the trunk is now f795356ae1d6; judged again on a recheck or when it is merged
mesh/delivery-group group feat/agents-write-git-ssh checking: 0 of 2 member(s) ready
mesh/delivery superseded: a newer head of the same pull request
2026-10-16 22:37:15 +02:00
mesh-tools f9c240a957 Merge pull request 'Playbook 07: report the whole issue tree every five minutes, every two while a delivery runs' (#389) from fix/pb07-overview-every-five into main 2026-10-16 19:34:36 +00:00
mesh-tools fff89b7c62 Playbook 07: report the whole issue tree every five minutes, every two while a delivery runs
mesh/merge-gate head fff89b7c6218 merged into trunk 80e36c75957f — pass: the change touches no module of the mesh's graph
mesh/repo-check head fff89b7c6218 merged into trunk 80e36c75957f — pass: its merge-check.sh passed
mesh/delivery delivered
The operator asked for a status update more often, with an overview of the issues worked on.
2026-10-16 19:18:42 +00:00
mesh-tools 80e36c7595 Merge pull request 'Playbook 09 and the hq-cleanup skill: a cleanup that deletes only what is proven done' (#388) from process/09-cleanup into main 2026-10-16 19:08:33 +00:00
jschoubben 3c49b389d1 Playbook 09: a deletion is approved as an ask on the operator's warrant once design 58 milestone 7 is built; until then the operator runs the approved list
mesh/merge-gate head 3c49b389d1e7 merged into trunk b29008ad36f4 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 3c49b389d1e7 merged into trunk b29008ad36f4 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-16 20:51:00 +02:00
jschoubben 222fbe95f4 Playbook 09 and the hq-cleanup skill: a cleanup reads first, deletes only what the operator approves, and reports what it kept (the operator, 2026-10-16)
mesh/merge-gate head 222fbe95f4bb merged into trunk b29008ad36f4 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 222fbe95f4bb merged into trunk b29008ad36f4 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-16 20:01:13 +02:00
mesh-tools b29008ad36 Merge pull request 'Playbook 02: one design per thing' (#387) from process/02-one-design-per-thing into main 2026-10-16 17:41:22 +00:00
jschoubben 101d7f30f7 Playbook 02: one design per thing; a graduation rewrites the design that covers it instead of writing a second (the operator, 2026-10-16)
mesh/merge-gate head 101d7f30f7e2 merged into trunk 225eee14d0bf — pass: the change touches no module of the mesh's graph
mesh/repo-check head 101d7f30f7e2 merged into trunk 225eee14d0bf — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-16 19:40:48 +02:00
mesh-tools 225eee14d0 Merge pull request 'Design 47: a move of the trunk asks every active check again (ADR 0395)' (#381) from design/47-recheck-on-trunk-move into main 2026-10-16 17:35:52 +00:00
jschoubben 12e75cdfa2 Design 47: the transition paragraph, the recheck verb and the section's bullets say ADR 0395 whole (review)
mesh/merge-gate head 12e75cdfa215 merged into trunk f1081c4f9768 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 12e75cdfa215 merged into trunk f1081c4f9768 — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-16 19:27:52 +02:00
jschoubben 377fa3f88a Design 47: say ADR 0395 as accepted — a conflict is asked again at every move, an ended check's builds are not reused, a group's composed check reruns through its members, and the cost bound 2026-10-16 19:26:47 +02:00
jschoubben 11e32ce5be Merge remote-tracking branch 'origin/main' into design/47-recheck-on-trunk-move 2026-10-16 19:26:35 +02:00
mesh-tools f1081c4f97 Merge pull request 'Playbook 08: a second fault the delivery shows joins the issue only in the same files' (#386) from fix/pb08-second-fault into main 2026-10-16 17:09:53 +00:00
mesh-tools 152cc0c0e7 Playbook 08: a second fault the delivery shows joins the issue only in the same files
mesh/merge-gate head 152cc0c0e77c merged into trunk a8b951f3b488 — pass: the change touches no module of the mesh's graph
mesh/repo-check head 152cc0c0e77c merged into trunk a8b951f3b488 — pass: its merge-check.sh passed
mesh/delivery delivered
Taken from issue 937, where mesh-catalog #335 was taken into the issue
and issue 943 was opened for a fault in other code.
2026-10-16 17:06:25 +00:00
mesh-tools a8b951f3b4 Merge pull request 'Playbook 08: needing a decision keeps a fix small' (#385) from process/08-a-decision-keeps-a-fix-small into main 2026-10-16 15:58:01 +00:00
jschoubben 0eadc27fc7 Playbook 08: needing a decision keeps a fix small; a feature is several modules with research or a design before it (the operator, 2026-10-16)
mesh/merge-gate head 0eadc27fc722 merged into trunk 4e437eb1488f — pass: the change touches no module of the mesh's graph
mesh/repo-check head 0eadc27fc722 merged into trunk 4e437eb1488f — pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-16 17:46:23 +02:00
jschoubben 91f82cd38e Design 47: a group's own merge in order asks nothing again (ADR 0395 §1, review)
mesh/merge-gate out of date: judged against trunk b9af4e373dc0, the trunk is now 6dcfa2226e63; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk b9af4e373dc0, the trunk is now 6dcfa2226e63; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
2026-10-16 13:32:39 +02:00
jschoubben a65e1076a2 Design 47: a pull request that no longer merges cleanly is told, not checked (ADR 0395 §2)
mesh/merge-gate head a65e1076a2e5 merged into trunk b9af4e373dc0 — pass: the change touches no module of the mesh's graph
mesh/repo-check head a65e1076a2e5 merged into trunk b9af4e373dc0 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
2026-10-16 11:42:03 +02:00
jschoubben 09ec150bd6 Design 47: a move of the trunk asks every active check again, one at a time per pull request (ADR 0395, issue 888)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate error: the check could not run: killed by hand
mesh/repo-check error: the check could not run: killed by hand
2026-10-16 11:40:14 +02:00
41 changed files with 3499 additions and 774 deletions
+44
View File
@@ -0,0 +1,44 @@
---
name: hq-cleanup
description: Use when the operator asks to clean up the forge and the working copies — stale or merged branches, old pull requests, finished worktrees, leftover folders, stashes. Triggers on "cleanup", "clean up our repos", "stale branches", "old worktrees", "tidy the forge". Reads first, deletes only what the operator approves, and reports what it cleaned, kept and left.
---
# hq-cleanup
Runs playbook [09 — Cleanup](../../../00-META/process/09-cleanup.md); the playbook is authoritative. This skill
adds the mechanics.
## 1. Take stock (one read-only agent, smaller model)
Brief it to change nothing and to write one report to the session's scratchpad:
- **Forge.** `gitea.gitea_list_repos`; per repository its default branch and **every branch from the git refs**
(`gitea.gitea_api` GET `/repos/{owner}/{repo}/git/refs/heads`; the list tool misses branches, issue 949);
`gitea.gitea_branch_protection_get`; open pull requests (`gitea.gitea_open_pulls`, titles and update times
from `gitea.gitea_api` `/repos/{owner}/{repo}/pulls?state=open`). Per branch: contained in the default branch
(`gitea.gitea_contains`, with the default branch's sha), patch-equal (`git cherry` in a local clone), tip date.
- **Local.** Under the projects folder: each clone's `git worktree list --porcelain`, `git branch -vv`,
`git branch --merged origin/<default>`, `git stash list`, `git status --porcelain` per worktree; folders and
loose files beside the clones with `du -sh` and newest file. A worktree under another session's scratchpad is
Kept.
- A **Missing tools** section: each read it did without a mesh tool.
## 2. Classify and ask
Classes as playbook 09 step 2: Safe, Likely safe, Kept, Unknown. Tell the operator the counts and the report's
place, and ask one question: delete the Safe list? Likely-safe and Unknown items are asked separately.
## 3. Delete the approved list only
- Forge branch: `gitea.gitea_delete_branch`.
- Worktree: `git worktree remove <path>` (never `--force`), then `git worktree prune`.
- Local branch: `git branch -d <name>` (never `-D`).
Once design 58 milestone 7 is built (issue 884), each deletion is a call held as an ask: the operator approves
the exact list on Telegram and the mesh carries it out on the warrant. Until then a session's permission check
refuses these; hand the operator the approved list as a file, with the commands, and do not work around the
refusal.
## 4. Report
Four lists: **cleaned** (with its evidence), **kept** (why), **unknown** (what it looks like), **the operator
kept** (their word). Open each missing tool with `issue-tracker.open`, or note it on an existing issue.
+1 -1
View File
@@ -15,7 +15,7 @@ works — plus the engineering practice that holds across everything Novox build
## Rules ## Rules
- Markdown only. - Markdown only, but for the checks and one drawn overview of the playbooks (`process/playbooks.html`).
- **Stable by nature.** Changes here reflect a genuine shift in intent, not iteration. The one - **Stable by nature.** Changes here reflect a genuine shift in intent, not iteration. The one
exception is `how-we-build.md`, which changes whenever a rule is earned — and only through exception is `how-we-build.md`, which changes whenever a rule is earned — and only through
its amendment process. its amendment process.
+193 -59
View File
@@ -98,6 +98,9 @@ and repair).
*Not:* ~~tools-sdk~~ *Not:* ~~tools-sdk~~
- **invokes** — the manifest word for the tools a module calls, `<module>.<tool>` each or `*` for every - **invokes** — the manifest word for the tools a module calls, `<module>.<tool>` each or `*` for every
one. A grant on the publish side and nothing else; a module that declares none calls nothing. one. A grant on the publish side and nothing else; a module that declares none calls nothing.
- **run for** — an assignment, or a start of a module, that serves one agent identity with that identity's machine
account, home and bus password. Only a module whose manifest declares it may be run for an identity; running for
the operator is the default run, as the operator account, with no setting (ADR 0412).
- **upgrade policy** — how a module's new builds reach its nodes: `roll` (one node first, judged, then - **upgrade policy** — how a module's new builds reach its nodes: `roll` (one node first, judged, then
the rest), `together`, or `record` (register the build, send it nowhere) the rest), `together`, or `record` (register the build, send it nowhere)
(ADR 0236). (ADR 0236).
@@ -219,18 +222,19 @@ every other domain has something to run on. **Recorded by** the controller and t
(ADR 0229). (ADR 0229).
- **record mirror** — a key-value bucket on the bus that the controller writes from one kind of its - **record mirror** — a key-value bucket on the bus that the controller writes from one kind of its
records (nodes, assignments, rollouts, builds, settings), after each change commits in its store and only records (nodes, settings, declared config, predefined keys), after each change commits in its store and only
when the value differs, with one event per write. The store stays the record; the self-check compares when the value differs, with one event per write. The store stays the record; the self-check compares
the two and raises `mirror-drifted` on any difference the two and raises `mirror-drifted` on any difference
(ADR 0289). Shortened to *mirror* where the (ADR 0289, ADR 0356). Builds, rollouts and assignments are no mirrors: they are
text is about the controller's records; the builder's *mirrored* `upstream` image (Change and delivery) is the chain, kept on the bus as their only source (ADR 0408). Shortened to *mirror* where the
text is about the controller's records; the builder's *mirrored* `upstream` image (Delivery) is
a different thing, a copy of an outside image in the artifact store. a different thing, a copy of an outside image in the artifact store.
## Placement — what runs on which node ## Placement — what runs on which node
**Purpose.** To decide, send and apply what each node runs, and to say why. **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 **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. and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261, 0411.
**Uses:** node, machine, module, seat, manifest (Module), config value (Config). **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, - **assignment** — a module put on a node. Its config values on that node (module-assignment config,
@@ -263,15 +267,15 @@ and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 02
other things (see [Homonyms](#homonyms)). other things (see [Homonyms](#homonyms)).
- **apply** — the node-engine making its node match its declaration; **reconcile** is doing so again - **apply** — the node-engine making its node match its declaration; **reconcile** is doing so again
until nothing differs. until nothing differs.
- **operator account** — the login name of the person who works on a node, stated on the node record; - **operator account** — the machine account of the operator's person identity on a node, read from the
empty for a node nobody logs into. Everything the mesh places under a person's home is resolved identity and no longer stated on the node record; empty for a node nobody logs into. Everything the mesh
against this account's home and owned by it places under a person's home is resolved against this account's home and owned by it
(ADR 0181). (ADR 0181, ADR 0411).
Not a name a manifest carries. Not a name a manifest carries.
- **agent account** — the login every coding agent on a node runs as, when the node names one of its own; - **agent account** — the machine account of the agent identity the identity seat marks as a node's agent account,
stated on the node record at the controller's terminal, never by a verb or a config value. Empty means agents on the operator's Approve, never by an assignment or a config value. Where none is marked, agents run as the
run as the operator account. It cannot become root without a person, and the node-engine judges that on operator account. It cannot
every look (ADR 0266). become root without a person, and the node-engine judges that on every look (ADR 0266, ADR 0411).
- **account group** — a group of the machine's group database that a module puts an account in, by - **account group** — a group of the machine's group database that a module puts an account in, by
declaring the account with that group and nothing else of it. Several modules may each add one to the declaring the account with that group and nothing else of it. Several modules may each add one to the
same account; the mesh takes back only an account group it added same account; the mesh takes back only an account group it added
@@ -339,8 +343,8 @@ and vault (Identity and access).
**Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents **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. and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault.
**Upstream of** Provisioning, Change and delivery, and Operator and conversation. **Upstream of** Provisioning, Delivery, and Operator and conversation.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388. **Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414.
**Uses:** module, node, seat, tool (Module), operator, consumer and provision (Provisioning), agent and ask (Operator and conversation). **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** - **credential** — anything that proves an identity: a password, a key, a token. A **pair credential**
@@ -354,8 +358,13 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
- **vault** — the module that owns every secret (`mesh-vault`, - **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)).
- **identity** — a person, an agent or a module as the mesh knows it: a name, a kind, a role, and whether it - **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. Every identity and role is held by the identity seat, in the bus's key-value store, and is switched on; a person or an agent also has a login name, the machines it is present on and, for a person,
changes only through its verbs (ADR 0386). the login kept on a machine where it differs. Every identity and role is held by the identity seat, in the
bus's key-value store, and changes only through its verbs (ADR 0386, ADR 0411). Each person and agent
identity has a bus account of its own, carrying its role (ADR 0413).
- **machine account** — an identity's login on one machine, made by the mesh from the identity: the
controller composes it and the node-engine applies it, adopting a login that exists and never renaming or
deleting one (ADR 0411).
- **role** — what an identity may call, as a set of grants (ADR 0386). Not a seat: a bare *role* is a - **role** — what an identity may call, as a set of grants (ADR 0386). Not a seat: a bare *role* is a
homonym (see [Homonyms](#homonyms)). homonym (see [Homonyms](#homonyms)).
- **grant** — one line of a role: an effect everywhere, an effect on one seat, or one named tool, and may be - **grant** — one line of a role: an effect everywhere, an effect on one seat, or one named tool, and may be
@@ -388,19 +397,29 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
agent can run as that account: the control-node, and a node whose agents run under an account of their own agent can run as that account: the control-node, and a node whose agents run under an account of their own
(ADR 0272). (ADR 0272).
## Change and delivery — a commit on its way to the nodes ## Delivery — a commit on its way to the nodes
**Purpose.** To take one commit from its pull request to every node that should run it, and to put it **Purpose.** To take one commit from its pull request to every node that should run it, and to put it
back when it fails. **Recorded by** the `mesh-delivery` module (deliveries) and the controller (the back when it fails. The domain is continuous integration and delivery, named **delivery**, and its parts live in
planner, the rollout). **Downstream of** Module, Core, Health and repair and Data. the repository `mesh-delivery` (ADR 0429 §2). **Recorded by** the `mesh-delivery` module (deliveries) and the
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323. 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.
**Uses:** module, node, declaration and send (Placement), health (Health and repair), person. **Uses:** module, node, declaration and send (Placement), health (Health and repair), person.
- **merge check** — the two statuses a pull request carries before it may merge: the **merge gate** - **merge check** — what a pull request carries before it may merge, judging the merge of its head into a
(`mesh/merge-gate`, the build seat's composed check of the modules the change touches) and the named **trunk commit**, made in the builder's own clone and never pushed (ADR 0323). Until release 4 of
**repository check** (`mesh/repo-check`, the repository's own `merge-check.sh`) ADR 0429 is delivered, two statuses: the **merge gate** (`mesh/merge-gate`, the build seat's composed check of
(ADR 0238). Both judge the merge of the pull request's head into a named **trunk commit**, made in the the modules the change touches) and the **repository check** (`mesh/repo-check`, the repository's own
build seat's own clone and never pushed (ADR 0323). `merge-check.sh`) (ADR 0238). From that release, one status, `mesh/check`, written by the checker: the
change's tests, run by the language's build module, and the **composition check**; for a delivery group, one
verdict on every member's head (ADR 0445, ADR 0447).
- **composition check** — whether the machines a change touches still compose with it, run by the assigner
from the modules the indexer and the builder hold, with the composition the controller sends with
(ADR 0446). It replaces the merge gate from release 4 of ADR 0429; the old word is struck once that release
is delivered and every design that still says it is reworded (issue 1303).
- **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).
- **trunk commit** — the commit of the trunk a merge check's verdict was judged against: the trunk's head when - **trunk commit** — the commit of the trunk a merge check's verdict was judged against: the trunk's head when
the build seat cloned it. The verdict holds for that commit only, and goes out of date when the trunk moves the build seat cloned it. The verdict holds for that commit only, and goes out of date when the trunk moves
(ADR 0323). (ADR 0323).
@@ -408,7 +427,10 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
**builder**. Any node may hold the seat, so there is no build node by nature **builder**. Any node may hold the seat, so there is no build node by nature
(ADR 0237). (ADR 0237).
*Not:* ~~build machine~~ *Not:* ~~build machine~~
- **build queue** — the controller's queue of builds to run; one entry in it is a **build request**. - **build queue** — the builds waiting to run, kept by the bus as three work queues a builder takes in order:
delivery builds, then checks of pull requests whose Approve is asked or given (ADR 0421 §10), then every other check; each check queue keeps one
request per pull request, the newest (ADR 0406); one entry in it is a
**build request**.
The queue's verbs still say *ask* for an entry; the conversation's ask is another thing The queue's verbs still say *ask* for an entry; the conversation's ask is another thing
(see [Homonyms](#homonyms)). (see [Homonyms](#homonyms)).
- **package** — what code resolves when it is **compiled**: an npm/cargo/pypi dependency, by **version**. - **package** — what code resolves when it is **compiled**: an npm/cargo/pypi dependency, by **version**.
@@ -450,28 +472,69 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
- **delivery group** — two or more deliveries sharing a pull request head branch name across the mesh's - **delivery group** — two or more deliveries sharing a pull request head branch name across the mesh's
repositories, delivered as one unit in an order declared (`after:`) or inferred by the planner. One repositories, delivered as one unit in an order declared (`after:`) or inferred by the planner. One
level: a group holds deliveries, never groups. Its state is derived from its members, never set. level: a group holds deliveries, never groups. Its state is derived from its members, never set.
- **release branch** — a branch other than `main`, shared by name across repositories, that collects a feature's - **hotfix** — one pull request in one repository, delivered at its merge (ADR 0401).
merged work and is brought into `main` as one delivery (design 57's `feat/57-config-and-provisioning`). While it - **feature** — one branch name across its repositories: in two or more, its pull requests are one delivery group,
is in flight, a change that touches its files waits for it (playbook 07, *Is the coast clear?*). merged together and delivered member by member in the group's order; in one, a single pull request delivered as a
lone delivery (ADR 0401). Work is never held back on a branch other than a trunk: the trunk is what runs.
*Not:* ~~release branch~~ (hq)
- **toolchain** — a language's compiler and tools, installed by that language's build module on every node holding
the build seat, pinned to one version that is the same on every builder; its version is part of a build's reuse
key (ADR 0405). No longer an image.
- **delivery plan** — what a delivery does to the mesh: its **build plan** (the modules it moves and - **delivery plan** — what a delivery does to the mesh: its **build plan** (the modules it moves and
their dependents, in tiers), its **deploy plan** (per node, what it receives and what waits for a their dependents, in tiers), its **deploy plan** (per node, what it receives and what waits for a
person) and its verdict (the composed nodes, the replays). Computed by the controller's planner from a person) and its verdict (the composed nodes, the replays). Computed by the controller's planner from a
diffset; the controller's verb `delivery-plan` shows it. *Deploy* is used in this one place. diffset; the controller's verb `delivery-plan` shows it. *Deploy* is used in this one place. Deleted at
release 6 of ADR 0429 (§8): the word stays in use until that release is delivered, and then retires.
*Not:* ~~change plan~~, ~~release plan~~ *Not:* ~~change plan~~, ~~release plan~~
- **merge window** — the rolling wait in which the controller collects merges before it plans them: each - **merge window** — the rolling wait in which the controller collects merges before it plans them: each
merge restarts it, and it closes when none arrived for its length (90 seconds by default) or at the latest merge restarts it, and it closes when none arrived for its length (90 seconds by default) or at the latest
10 minutes after its first merge (ADR 0276). 10 minutes after its first merge (ADR 0276). Kept as two keys with a time to live on the bus; the first to
expire closes the batch (ADR 0407). Goes at release 6 of ADR 0429, when nothing collects merges (§12).
- **batch** — the merges one merge window collected, planned once as one rollout. For a repository merged more - **batch** — the merges one merge window collected, planned once as one rollout. For a repository merged more
than once in it, the rollout takes the latest commit, and the earlier merges are answered by it. Not *group*, than once in it, the rollout takes the latest commit, and the earlier merges are answered by it. Not *group*,
which is a delivery group. which is a delivery group. Goes at release 6 of ADR 0429 (§11, §12).
- **mesh-delivery** — the module that owns deliveries and delivery groups, holding the mesh-scoped seat - **mesh-delivery** — the repository of the delivery domain, holding its parts as modules prefixed
of the same name. It records and decides; the controller sends, judges and rolls back when it is asked. `mesh-delivery-` (ADR 0429 §2), made by the forge module from its config (ADR 0428). Until release 3 of
ADR 0429 also the module in the catalogue that owns deliveries and delivery groups, holding the mesh-scoped
seat of the same name: it records and decides, and the controller sends, judges and rolls back when it is
asked. Release 3 splits that module into the parts, and then the name means the repository alone.
- **part** — one independent process of the delivery domain with its own bus credential, which watches keys
and writes keys and never calls another part (ADR 0429 §1). Each key has one writer; the operator's
credential may also write a want, the one manual escape. The parts: the **forge bridge** (forge events in,
the only caller of the forge), the **indexer** (fingerprints, the candidates of a group, the modules of the
trunk), the **builder** (claims and builds one fingerprint once), the **checker** (the merge check on built
artifacts), the **gate** (merges a group and moves the source revision; *gate part* where it could be read
as another gate, see [Homonyms](#homonyms)), the **teller** (the operator's phone, and the only writer of an
ask's answer), the **assigner** (writes each node's wants by its six rules), the **node agent** (the
node-engine's own process, applying its wants), the **watchman** (presence and pulses) and the **status
view** (`mesh-delivery-cli` and a web view, writing only its answers to asks). The reviewer is the review
agent's session, not a part. Built over releases 1 to 6 of ADR 0429.
- **fingerprint** — what identifies a module's build by its content: a hash over its source files, its
manifest and the fingerprints of its build-time inputs, so equal content gives an equal fingerprint whatever
the commit (ADR 0429 §5). It is today's source fingerprint with a base entering by its module's
fingerprint, the builder by its own module's fingerprint, and a build resolving packages by its lock. From
release 4 a build's key, so one source is never built twice.
- **source revision** — one revision of the key `source.main`, written only by the gate: every repository's
merged head and the group that moved it. A group reaches the mesh as one source revision (ADR 0429 §11); a
source revision is **delivered** when every want equals its module's fingerprint and every have equals its
want and is healthy, derived from the keys and recorded by no part (§1).
- **canary** — the first node of a module's nodes, ordered by ring then name: a new fingerprint reaches it
first and the module's other nodes only once it runs healthy there (ADR 0429 §6). Replaces sending to every
machine at once from release 6 (§15).
- **want** and **have** — a want (`want.<node>.<module>`) is the fingerprint a node should run, written by the
assigner; a have (`have.<node>.<module>`) is what the node runs, its phase and health, written by that node's
agent (ADR 0429 §4, §6). A node's facts that no module owns are its node key. They replace assignments and
rollouts at release 6 (§17); haves are written from release 1.
- **pulse** — a key the watchman writes for each key whose next move a machine owes, with a time to live; its
expiry is a stall, raised as a condition, so no part needs a timer (ADR 0429 §1, research 063). A person's
wait carries none.
- **rollout** — the controller's sending of one batch's builds across nodes, at most one trunk commit per - **rollout** — the controller's sending of one batch's builds across nodes, at most one trunk commit per
repository, tier by tier, one node first and judged at the first-node gate, then the rest (ADR 0236, repository, tier by tier, to every node that runs each module at once (ADR 0276, ADR 0360). Rollouts that share a
ADR 0276). One rollout is open at a time; it names every merge it answers. A primitive the delivering stage repository, a module or a graph edge run one after another; others run side by side (ADR 0407). It names every
merge it answers, and its record is one key of the bus's rollouts bucket, a link of the chain (ADR 0408). A primitive the delivering stage
asks for, not an object anyone manages. A **tier** is one step of a rollout: a module, then the modules asks for, not an object anyone manages. A **tier** is one step of a rollout: a module, then the modules
built against it. The controller's verb `plans` lists the rollouts. A module is rolled out by its `roll` built against it. The controller's verb `plans` lists the rollouts. A module is rolled out by its `roll`
policy. policy. Rollouts and tiers go at release 6 of ADR 0429 (§8, §15), replaced by wants and haves (§17).
*Not:* ~~walk~~ (hq) *Not:* ~~walk~~ (hq)
- **first-node gate** — the judgement on a delivery's first node: healthy three times, no witness put it - **first-node gate** — the judgement on a delivery's first node: healthy three times, no witness put it
back, no condition raised since the send (ADR 0236 §2). Not *the gate* bare (see [Homonyms](#homonyms)). back, no condition raised since the send (ADR 0236 §2). Not *the gate* bare (see [Homonyms](#homonyms)).
@@ -501,7 +564,7 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
**Purpose.** To notice what is wrong with the mesh, say it once, repair what may be repaired unattended, **Purpose.** To notice what is wrong with the mesh, say it once, repair what may be repaired unattended,
and record what was done by hand. **Recorded by** the controller's condition store. and record what was done by hand. **Recorded by** the controller's condition store.
**Upstream of** Change and delivery (the first-node gate) and Operator and conversation (a condition **Upstream of** Delivery (the first-node gate) and Operator and conversation (a condition
becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283. becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
**Uses:** node, module, node-engine (Core). **Uses:** node, module, node-engine (Core).
@@ -543,7 +606,7 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
**Purpose.** To know every item of data a module holds, how precious it is, and to keep it recoverable. **Purpose.** To know every item of data a module holds, how precious it is, and to keep it recoverable.
**Recorded by** the manifests' `data` declarations and every node's `node-backup` seat. **Recorded by** the manifests' `data` declarations and every node's `node-backup` seat.
**Upstream of** Change and delivery and Health and repair. **Decided by** ADR 0232, 0233, 0235. **Upstream of** Delivery and Health and repair. **Decided by** ADR 0232, 0233, 0235.
**Uses:** module, node, provider and consumer (Provisioning), person. **Uses:** module, node, provider and consumer (Provisioning), person.
- **data class** — how precious an item of data is, ranked by the operator: `irreplaceable`, - **data class** — how precious an item of data is, ranked by the operator: `irreplaceable`,
@@ -590,10 +653,10 @@ it should not. **Recorded by** the controller. **Upstream of** Provisioning and
**Purpose.** To let the mesh and its operator talk: tell, ask, answer, and act only on an answer it can **Purpose.** To let the mesh and its operator talk: tell, ask, answer, and act only on an answer it can
trust. **Recorded by** the router (asks, warrants and the operator's identities). trust. **Recorded by** the router (asks, warrants and the operator's identities).
**Downstream of** Health and repair, Change and delivery, and Identity and access. An authorising **Downstream of** Health and repair, Delivery, and Identity and access. An authorising
answer acts in whichever domain asked, through that domain's own verb; the conversation owns the answer acts in whichever domain asked, through that domain's own verb; the conversation owns the
asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 0384, 0385, 0390. asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 0384, 0385, 0390.
**Uses:** operator, person, node, module, seat, tool (Module), proof and identity (Identity and access), release (Change and delivery). **Uses:** operator, person, node, module, seat, tool (Module), proof and identity (Identity and access), release (Delivery).
- **mesh MCP server** — the endpoint on a node's loopback through which every agent and person on that - **mesh MCP server** — the endpoint on a node's loopback through which every agent and person on that
node reaches the mesh: the MCP server named `mesh`, with its five tools `mesh_overview`, node reaches the mesh: the MCP server named `mesh`, with its five tools `mesh_overview`,
@@ -607,8 +670,8 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03
line as the **controller's terminal** or as an ordinary call line as the **controller's terminal** or as an ordinary call
(ADR 0272). (ADR 0272).
- **nox** — `mesh-cli`'s alias, in interactive zsh only. A script, `sudo` and a document meant to work everywhere - **nox** — `mesh-cli`'s alias, in interactive zsh only. A script, `sudo` and a document meant to work everywhere
say `mesh-cli`. The operator also calls the mesh itself *nox mesh*; on this page and in the records *nox* is the say `mesh-cli`. The operator also calls the mesh itself *nox mesh*. *nox* is also the agent (ADR 0414 §7): see
alias alone. It is the one command the operator types: every line the mesh addresses to the operator — a [Homonyms](#homonyms), and say which is meant. It is the one command the operator types: every line the mesh addresses to the operator — a
condition's words, a verb's answer, a README, a design — names a `nox` line, never a daemon's (the controller's condition's words, a verb's answer, a README, a design — names a `nox` line, never a daemon's (the controller's
or the node-engine's binary followed by a command), which `words.py` and the catalogue's check refuse or the node-engine's binary followed by a command), which `words.py` and the catalogue's check refuse
(ADR 0272, (ADR 0272,
@@ -709,11 +772,59 @@ it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Dec
- **grab** — an engine handing one media release to a download client. It names the work it is for, so - **grab** — an engine handing one media release to a download client. It names the work it is for, so
the media-importer knows where the copy belongs. the media-importer knows where the copy belongs.
## Mail — the operator's own correspondence, which the mesh holds and reads for them
**Purpose.** To hold the operator's mail accounts, say each mail on the bus, find and read mail for whoever the
operator granted, note each mail for the operator and tell them of the mail that matters, and send a mail only on
the operator's Approve. **Recorded by** `mesh-mail` (its index database and attachment bucket, ADR 0359 §3; its
account buckets, ADR 0439) and `mesh-mail-reader` (its database of mail entries, mail groups and reading lessons,
ADR 0459). **Downstream of** Identity and access and Operator and conversation; **upstream of** nothing.
**Decided by** ADR 0359, 0361, 0378, 0439, 0440, 0458, 0459, 0461, 0462, 0472.
**Uses:** operator, person, module, seat; grant, identity (Identity and access); ask, warrant, operator channel,
content rule, untrusted input (Operator and conversation). A *judgement* is one call of the model with no tools
and no session (ADR 0278), in its plain sense.
- **mail account** — one of the operator's mailboxes that `mesh-mail` holds, of a kind of adapter (`imap`,
`imap-oauth`, `graph`), named by its address (ADR 0359 §1).
- **`mail-store`** — the seat holding the mail accounts: `accounts`, `search`, `read`, `send`, `send_status` (ADR
0378 §2, ADR 0440 §7). Always the seat's name: the bare *store* is Core's.
- **mail event** — what `mesh-mail` publishes on its stream `MAIL` for a new, changed or removed mail: headers,
flags and attachment references, never a body (ADR 0359 §2).
- **mail send** — a mail the mesh sends, which goes out only on the operator's warrant for that one mail (ADR 0361
§2), with the states `mail_send_status` answers (ADR 0440 §7). Any caller `mesh-mail`'s `grants` names for that
account may ask for one (ADR 0472).
- **mail web client** — the module `mesh-mail-web`, holder of `mail-webclient`: the operator's mail in a browser on
the private network (ADR 0440 §1). The page it serves is the mail page.
- **mail notice** — `mesh-mail-notifier`'s desktop pop-up for a new inbox mail (ADR 0378 §7, §9). It retires when
`mesh-mail-notifier` is unassigned everywhere (ADR 0461 §7) and then moves to *mail signal*'s *Not:* line
(issue 1418).
- **mail reading** — the model's reading of each new mail as it arrives, in batches, into its mail group and mail
entry; the seat `mail-reading`, held by `mesh-mail-reader` (ADR 0459). Not *triage* (the issue tracker's
judgement), and not `mail_read`, `mail-store`'s tool that fetches one mail's text.
- **mail entry** — what mail reading keeps about one mail: its mail group, a one-line summary, its mark and why, a
due date; never the body (ADR 0459 §4). Not *catalogue entry*: the catalogue is Delivery's.
- **mark** — what a mail entry says the mail is to the operator: *matters*, *scam warning* or neither, never two
(ADR 0459 §7). Not *kind* (Media's).
- **mail group** — a set of mails the model put together because they belong to one subject (ADR 0459 §4). Not *delivery
group* or *account group*.
- **reading lesson** — one correction the operator made on the mail page (a mark or a mail group), kept so later
mail reading follows it (ADR 0459 §8 to §10). Always qualified: the bare *lesson* is Operator and conversation's.
- **mail signal** — the notice on the operator's channel, one `tell` per batch in words only, and the place at the
top of the mail page, that tells the operator mail matters (ADR 0461). Always qualified: the bare *signal* is
Health and repair's.
- **scam warning** — the mark of a mail the model judges an attempt to deceive the operator, with why; shown in
its own place on the mail page and never a mail signal (ADR 0459 §7, ADR 0461 §6).
- **mail conversation** — a thread (ADR 0383) with a mail context, followed on the mail page: the mail questions
and their answers, each answer with the mails it found. The mail context is the framing that a mail is data, the
tools `mail_search` and `mail_read` only, the operator's senders and folders, and the reading lessons (ADR 0462).
- **mail question** — what the operator writes in the mail conversation to find or understand mail. Not *ask*
(Operator and conversation's request for the operator's input).
## The record — how this repository works ## The record — how this repository works
Not a domain of the mesh but of the way it is built, listed because its words meet the mesh's. Not a domain of the mesh but of the way it is built, listed because its words meet the mesh's.
**Recorded in** this repository, the issue tracker and the knowledge base. **Decided by** ADR 0019, 0080, 0244, 0275, **Recorded in** this repository, the issue tracker and the knowledge base. **Decided by** ADR 0019, 0080, 0244, 0275,
0284, 0290, 0291. 0284, 0290, 0291, 0423, 0424.
- **research effort**, **graduation**, **hand-off**, **playbook** — an investigation, held whole in the - **research effort**, **graduation**, **hand-off**, **playbook** — an investigation, held whole in the
knowledge base and named "research NNN"; knowledge base and named "research NNN";
@@ -727,11 +838,23 @@ Not a domain of the mesh but of the way it is built, listed because its words me
(ADR 0290). Not a *rule*, which is a line of (ADR 0290). Not a *rule*, which is a line of
`00-META/how-we-build.md`. `00-META/how-we-build.md`.
- **knowledge base** — what holds the decision records and research efforts whole (number, status, topic, - **knowledge base** — what holds the decision records and research efforts whole (number, status, topic,
typed links, clauses and reasoning, with every version), takes them as proposals, has them reviewed and accepted, typed links, clauses and reasoning, with every version), and, once they are moved, the designs and the glossary
entries too (ADR 0423); takes them as proposals, has them reviewed and accepted,
and answers what holds: the seat `knowledge-base`, held by the module `mesh-knowledge` and answers what holds: the seat `knowledge-base`, held by the module `mesh-knowledge`
([55 — The knowledge base](../03-DESIGN/01-to-be/55-the-knowledge-base.md)). Not **the record**, the literal reader ([55 — The knowledge base](../03-DESIGN/01-to-be/55-the-knowledge-base.md)). Not **the record**, the literal reader
of this repository. of this repository.
- **design** — a document in `03-DESIGN/`: **as-is** (what runs) or **to-be** (what is being built). - **design** — a record of the knowledge base, named "design NN", with a **to-be** section (what is being built)
and an **as-is** section (what runs), its owning repositories and its links to its decisions and other designs
(ADR 0423). Until the move, a document in `03-DESIGN/`, in `01-to-be/` or `00-as-is/`.
- **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).
- **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
it touches one repository; named by what the operator or the mesh can do once it is delivered (playbook 04,
ADR 0401).
*Not:* ~~epic~~ (hq)
- **issue** — a question about the mesh that someone must answer, numbered and tracked by the issue tracker, which - **issue** — a question about the mesh that someone must answer, numbered and tracked by the issue tracker, which
holds it whole: its report, diagnosis and resolution included (ADR 0291); holds it whole: its report, diagnosis and resolution included (ADR 0291);
the folders in `04-ISSUES/` are imported there and removed. the folders in `04-ISSUES/` are imported there and removed.
@@ -773,23 +896,24 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| Word | Sense | Say | Domain | | Word | Sense | Say | Domain |
|---|---|---|---| |---|---|---|---|
| plan | what the controller would send one node | that node's **declaration** (verb `plan`) | Placement | | plan | what the controller would send one node | that node's **declaration** (verb `plan`) | Placement |
| | what a delivery would do to the mesh | **delivery plan** | Change and delivery | | | what a delivery would do to the mesh | **delivery plan** | Delivery |
| | the sending of one batch of commits across nodes | **rollout** (verb `plans`) | Change and delivery | | | the sending of one batch of commits across nodes | **rollout** (verb `plans`) | Delivery |
| | a step a person starts, like the bus's | **planned step** | Core | | | a step a person starts, like the bus's | **planned step** | Core |
| membership | the subjects a bus account is issued | **membership** | Provisioning | | membership | the subjects a bus account is issued | **membership** | Provisioning |
| | an account in a group of the machine | **account group** | Placement | | | an account in a group of the machine | **account group** | Placement |
| push | the controller giving nodes their declarations | **send** (verb `push`) | Placement | | push | the controller giving nodes their declarations | **send** (verb `push`) | Placement |
| | a git push | **git push** | The record | | | a git push | **git push** | The record |
| | a phone notification | **push notification** | Operator and conversation | | | a phone notification | **push notification** | Operator and conversation |
| release | a person letting a held delivery go on | **release** | Change and delivery | | release | a person letting a held delivery go on | **release** | Delivery |
| | one milestone built, checked and delivered as one (playbook 07; releases 1 to 6 of ADR 0429) | **release** of a milestone | The record |
| | taking a consumer off a licence | **unbind** (verb `release`, to rename) | Identity and access | | | taking a consumer off a licence | **unbind** (verb `release`, to rename) | Identity and access |
| | a copy of a work an engine can fetch | **media release** | Media | | | a copy of a work an engine can fetch | **media release** | Media |
| | a branch brought into `main` as one delivery | **release branch** | Change and delivery | | gate | the judgement on a delivery's first node | **first-node gate** | Delivery |
| gate | the judgement on a delivery's first node | **first-node gate** | Change and delivery | | | the pull request status, until release 4 of ADR 0429 | **merge gate**; from release 4, **composition check** | Delivery |
| | the pull request status | **merge gate** | Change and delivery | | | the part that merges a group and moves the source revision | **gate part** (`mesh-delivery-gate`) | Delivery |
| | a failed step stopping its module's later steps (ADR 0136) | *a failed step holds its module* | Placement | | | a failed step stopping its module's later steps (ADR 0136) | *a failed step holds its module* | Placement |
| tier | a level of the mesh | **layer** | Core | | tier | a level of the mesh | **layer** | Core |
| | a step of a rollout | **tier** | Change and delivery | | | a step of a rollout | **tier** | Delivery |
| | how much proof an ask needs | **assurance level** | Identity and access | | | how much proof an ask needs | **assurance level** | Identity and access |
| harness | the part of `mesh-sdk` that serves a tools bundle | **SDK harness** | Module | | harness | the part of `mesh-sdk` that serves a tools bundle | **SDK harness** | Module |
| | the program that runs an agent's sessions | **agent harness** | Operator and conversation | | | the program that runs an agent's sessions | **agent harness** | Operator and conversation |
@@ -797,33 +921,43 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| role | what a seat names, at a scope | **seat**; the bare word in older designs is tracked by issue 872 | Module | | role | what a seat names, at a scope | **seat**; the bare word in older designs is tracked by issue 872 | Module |
| | what an identity may call | **role** | Identity and access | | | what an identity may call | **role** | Identity and access |
| ask | a request for the operator's input | **ask** | Operator and conversation | | ask | a request for the operator's input | **ask** | Operator and conversation |
| | an entry in the build queue | **build request** | Change and delivery | | | an entry in the build queue | **build request** | Delivery |
| store | the one database server | **store** | Core | | store | the one database server | **store** | Core |
| | the OCI registry | **artifact store** | Change and delivery | | | the OCI registry | **artifact store** | Delivery |
| | a node's own copy of its last declaration | **last declaration** | Placement | | | a node's own copy of its last declaration | **last declaration** | Placement |
| | the seat holding the mail accounts | **`mail-store`** | Mail |
| record | a numbered decision | **decision record** | The record | | record | a numbered decision | **decision record** | The record |
| | the literal reader of this repository, searched first for a symptom | **the record** (the `records` module) | Operator and conversation | | | the literal reader of this repository, searched first for a symptom | **the record** (the `records` module) | Operator and conversation |
| | the upgrade policy that sends nowhere | `record` | Module | | | the upgrade policy that sends nowhere | `record` | Module |
| nox | the mesh's one agent identity, its logins and accounts (ADR 0414) | **`nox`, the agent** | Identity and access |
| | the operator's terminal command, `mesh-cli` | **the `nox` command** | Operator and conversation |
| knowledge base | the module holding the decision records and research efforts whole | **knowledge base** | The record | | knowledge base | the module holding the decision records and research efforts whole | **knowledge base** | The record |
| | the predecessor's operational memory, in older playbooks (issue 430) | none: it no longer exists | — | | | the predecessor's operational memory, in older playbooks (issue 430) | none: it no longer exists | — |
| check | a pull request's two statuses | **merge check**, **merge gate**, **repository check** | Change and delivery | | check | a pull request's two statuses, one from release 4 of ADR 0429 | **merge check**, **merge gate**, **repository check**; from release 4, `mesh/check` | Delivery |
| | a delivery group's verdict | **composed check** | Change and delivery | | | whether the machines a change touches still compose, the assigner's | **composition check** | Delivery |
| | a delivery group's verdict | **composed check** | Delivery |
| | the controller's own examination | **self-check** | Health and repair | | | the controller's own examination | **self-check** | Health and repair |
| | a file in `00-META/checks/` | **hq check** | The record | | | a file in `00-META/checks/` | **hq check** | The record |
| agent | a coding agent | **agent** | Operator and conversation | | agent | a coding agent | **agent** | Operator and conversation |
| | the build seat's holder | **builder** | Change and delivery | | | the build seat's holder; from release 4 of ADR 0429 the part `mesh-delivery-builder` | **builder** | Delivery |
| the user | an account of a wrapped program, or of the bus server | **its account**, **bus account** | Identity and access | | the user | an account of a wrapped program, or of the bus server | **its account**, **bus account** | Identity and access |
| | a human | **operator** or **person** | words every domain uses | | | a human | **operator** or **person** | words every domain uses |
| firewall | what the mesh enforces | **packet filter** | Connectivity | | firewall | what the mesh enforces | **packet filter** | Connectivity |
| | what was found on a machine | **found firewall** | Connectivity | | | what was found on a machine | **found firewall** | Connectivity |
| ticket | an answer's one-time token (ADR 0258) | **answer ticket** | Operator and conversation | | ticket | an answer's one-time token (ADR 0258) | **answer ticket** | Operator and conversation |
| | a question someone must answer | **issue** | The record | | | a question someone must answer | **issue** | The record |
| deploy | the per-node part of a delivery plan | **deploy plan** | Change and delivery | | deploy | the per-node part of a delivery plan | **deploy plan** | Delivery |
| | a wrapped program's own deploy | its own word | — | | | a wrapped program's own deploy | its own word | — |
| managed | a placed file of which only named keys or a marked block are the mesh's | **managed** (`owner-at-runtime`) | Provisioning | | managed | a placed file of which only named keys or a marked block are the mesh's | **managed** (`owner-at-runtime`) | Provisioning |
| | a file the mesh writes, such as the agent's instruction file or `managed_settings` | the file's own name, or *a file the mesh places* | — | | | a file the mesh writes, such as the agent's instruction file or `managed_settings` | the file's own name, or *a file the mesh places* | — |
| trusted | a config value that decides what runs or who can reach what | **trusted** config value | Config | | trusted | a config value that decides what runs or who can reach what | **trusted** config value | Config |
| | a sender on the operator's own list | **trusted** sender, its message an **operator message** | Operator and conversation | | | a sender on the operator's own list | **trusted** sender, its message an **operator message** | Operator and conversation |
| lesson | an agent's proposal after the operator corrects its way of working | **lesson** | Operator and conversation |
| | the operator's correction of mail reading | **reading lesson** | Mail |
| signal | something that must keep happening | **signal** | Health and repair |
| | the notice that mail matters | **mail signal** | Mail |
| conversation | the domain *Operator and conversation* | the domain's name | Operator and conversation |
| | the mail questions and answers on the mail page | **mail conversation** | Mail |
## How this page is kept ## How this page is kept
+5 -1
View File
@@ -48,6 +48,9 @@ the expensive half.
## The playbooks ## The playbooks
A drawn overview of them, the cycle, a feature's steps, the review loop and an issue's life, is
[`playbooks.html`](playbooks.html): a picture of these playbooks, never their authority.
| # | Playbook | Trigger | | # | Playbook | Trigger |
|---|---|---| |---|---|---|
| [01](01-research.md) | Research | An idea from the operator, or one worth investigating before committing to design: setup questions, findings, a direction the operator confirms | | [01](01-research.md) | Research | An idea from the operator, or one worth investigating before committing to design: setup questions, findings, a direction the operator confirms |
@@ -57,7 +60,8 @@ the expensive half.
| [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces | | [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 | | [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 | | [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 review, one push, 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 |
| [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 |
## The cycle is checked ## The cycle is checked
+10 -2
View File
@@ -33,7 +33,10 @@ about an hour, and said to the operator if it runs past.
- **Pre-check each record before proposing it**, as for research (playbook [01](01-research.md), step - **Pre-check each record before proposing it**, as for research (playbook [01](01-research.md), step
5): every record, issue or design it names is in `links`; each deferral names its issue; each figure 5): every record, issue or design it names is in `links`; each deferral names its issue; each figure
is given as measured; no word on a glossary *Not:* line. is given as measured; no word on a glossary *Not:* line.
3. **Write the design.** Create the document under `03-DESIGN/01-to-be/` with frontmatter: 3. **Write the design.** **One design per thing** (the operator, 2026-10-16): when a to-be design already
covers what the decisions change, they rewrite that design, as *Amending an existing design* below says,
and no second design is written beside it that refers to it. Only for something no design covers, create
the document under `03-DESIGN/01-to-be/` with frontmatter:
```yaml ```yaml
--- ---
@@ -91,7 +94,10 @@ about an hour, and said to the operator if it runs past.
deferral no issue tracks. The knowledge base then reviews the proposal itself, through a judgement that deferral no issue tracks. The knowledge base then reviews the proposal itself, through a judgement that
sees only the record, the records it links and the rules, and answers `approve` or `changes`. On sees only the record, the records it links and the rules, and answers `approve` or `changes`. On
`changes` the record stays proposed with the findings: revise it by proposing it again, whole, with `why`; `changes` the record stays proposed with the findings: revise it by proposing it again, whole, with `why`;
at most three rounds, then the findings go to the operator with a recommendation. after three rounds the lead does not stop to ask, but fixes each further finding itself and proposes again
(the operator, 2026-10-18: ADR 0429 went past three rounds, each finding narrower than the last, and asking
first only added a wait). Only a finding that would change what was decided, rather than how the record
says it, goes to the operator, with a recommendation.
On `approve` it is accepted, or, when it says `needs-operator`, it waits for the operator's Approve on a On `approve` it is accepted, or, when it says `needs-operator`, it waits for the operator's Approve on a
channel that proves who answered. Nothing proposed is in force. `knowledge-base.get` shows a record's channel that proves who answered. Nothing proposed is in force. `knowledge-base.get` shows a record's
reviews and the operator's ask when it waits for one. reviews and the operator's ask when it waits for one.
@@ -161,3 +167,5 @@ when one fails. In this repository, `records.py` checks that a design's `decisio
order's six topics, which the knowledge base reads. The three habits of graduating step 2 (side by side, clauses order's six topics, which the knowledge base reads. The three habits of graduating step 2 (side by side, clauses
from the direction, the pre-check) are the lead's, read by the reviewer; `propose` refuses a link to nothing, from the direction, the pre-check) are the lead's, read by the reviewer; `propose` refuses a link to nothing,
a deferral no issue tracks and a retired word, and the review returns a record named but not linked. a deferral no issue tracks and a retired word, and the review returns a record named but not linked.
The rounds are read in `knowledge-base.get` (its reviews, one per version): a question to the operator about a
record's wording after its third round is the failure of step 4 of *Writing a decision record*.
+17 -6
View File
@@ -12,8 +12,16 @@ A design becomes **milestones**, and each milestone becomes **issues**:
- **A milestone is usable on its own.** Once its delivery plan is done, the operator or the mesh can do - **A milestone is usable on its own.** Once its delivery plan is done, the operator or the mesh can do
something new, said in one sentence ("a Claude Code session is mirrored into a thread on the bus"). something new, said in one sentence ("a Claude Code session is mirrored into a thread on the bus").
A milestone that changes nothing anyone can use is not a milestone; fold it into the next. A milestone that changes nothing anyone can use is not a milestone; fold it into the next.
- **A milestone is one feature step** of playbook [07](07-feature-branches.md): prepared, developed in one to - **A milestone is one release** of playbook [07](07-feature-branches.md): prepared, developed whole, reviewed,
two hours, reviewed, checked and delivered as one. A milestone larger than that is split. checked and delivered as one, never cut for its size or for time (the operator, 2026-10-17).
- **Milestones are cut by release order.** A milestone ends where one repository's change must be released before
another can use it (a new manifest word before the catalogue reads it, an SDK change before its users, until
design 47's milestones 3 and 6 lift those orders); the lead checks this when cutting, so no milestone needs a
second release. The orders to look for are those playbook [07](07-feature-branches.md) step 0 item 6 names (an SDK
change before its users, a new wire key before the node that reads it, the first module of a repository the catalogue
never built, a part that needs a decision first); a milestone cut by such an order may be usable only by the next one,
and says so in its outcome. A milestone the prepare step finds holding such an order is split into milestones in the
design, each one release, never carried as parts of one (2026-10-18: design 47's release 1-2 became six).
- **An issue is one repository's part of a milestone,** sized so a fixing agent finishes it inside the - **An issue is one repository's part of a milestone,** sized so a fixing agent finishes it inside the
milestone's development time. Issues that must land in order are linked `depends-on`. milestone's development time. Issues that must land in order are linked `depends-on`.
@@ -30,7 +38,8 @@ A design becomes **milestones**, and each milestone becomes **issues**:
4. **Break it into milestones.** Write them into the design, in a section *Milestones*: for each, its number, 4. **Break it into milestones.** Write them into the design, in a section *Milestones*: for each, its number,
its one-sentence outcome, the acceptance tests of the design it proves, the repositories it touches, and its one-sentence outcome, the acceptance tests of the design it proves, the repositories it touches, and
what it depends on. Order them so each is usable as soon as it is delivered, the smallest useful one what it depends on. Order them so each is usable as soon as it is delivered, the smallest useful one
first. first. A milestone is named by what it lets someone do; how it ships is shaped by the plan its changes would
have, asked through `mesh-delivery.what-if`: the modules it moves and their fan-out (ADR 0401 §6).
5. **Ask the operator to confirm the milestones**, in a short table: number, outcome, the acceptance tests it 5. **Ask the operator to confirm the milestones**, in a short table: number, outcome, the acceptance tests it
proves, repositories, the parts each repository builds. Change what they change. proves, repositories, the parts each repository builds. Change what they change.
6. **Open the issues.** For each confirmed milestone, `issue-tracker.open` one issue per repository part, its 6. **Open the issues.** For each confirmed milestone, `issue-tracker.open` one issue per repository part, its
@@ -39,8 +48,10 @@ A design becomes **milestones**, and each milestone becomes **issues**:
entry in the design. entry in the design.
7. **Flip the status.** `status: in-progress`, `updated:` today; the design change goes through playbook 7. **Flip the status.** `status: in-progress`, `updated:` today; the design change goes through playbook
[08](08-small-fixes.md) (one hq pull request). [08](08-small-fixes.md) (one hq pull request).
8. **Hand each milestone to playbook 07, one at a time.** The milestone is the feature step; its issues are the 8. **Hand each milestone to playbook 07, one at a time.** The milestone is one release of playbook 07; its issues
step's issues; the next milestone starts when the previous one's delivery plan is done. are the release's issues; the next milestone starts when the previous one's delivery plan is done. A design waits
while another design's milestones are being built: one design is built at a time (playbook
[07](07-feature-branches.md)), so this step starts only when the design before it is delivered.
9. **On completion of the last milestone**, run the "when something ships" section of playbook 9. **On completion of the last milestone**, run the "when something ships" section of playbook
[02](02-graduation.md). [02](02-graduation.md).
@@ -51,7 +62,7 @@ A design becomes **milestones**, and each milestone becomes **issues**:
owns what is not. The knowledge base's rule `deferral` checks the decision record when it is proposed owns what is not. The knowledge base's rule `deferral` checks the decision record when it is proposed
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md)). ([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md)).
- HQ is not a code repository and never carries implementation. - HQ is not a code repository and never carries implementation.
- How the building is done — worktrees, agents, review, checks, the ready mark and the delivery — is playbook - How the building is done — worktrees, agents, review, checks, the Approve and the delivery — is playbook
07's, not this one's. 07's, not this one's.
## How it is checked ## How it is checked
+341 -108
View File
@@ -28,72 +28,158 @@ A small fix (one repository, one issue, about an hour of work) follows
hours of local work, met its first check on 2026-10-16 and failed on 29 problems, TypeScript errors among hours of local work, met its first check on 2026-10-16 and failed on 29 problems, TypeScript errors among
them, that a unit suite run continuously while working finds at once. them, that a unit suite run continuously while working finds at once.
- **A green pull request nobody marks.** An approved, green pull request that was not marked ready waited - **A green pull request nobody marks.** An approved, green pull request that was not marked ready waited
12 h 17 min for someone to merge it (mesh-catalog #300, 2026-10-14). 12 h 17 min for someone to merge it (mesh-catalog #300, 2026-10-14). There is no ready mark since ADR 0421:
the operator's Approve is asked as soon as the checks pass.
- **Review rounds spent on what a run or a search would have shown.** On 2026-10-19 milestone R3b of design 47
took two review rounds and one more cycle after a failed try. A test that "failed first" only by not compiling
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 module proven only after it reached the machines.** The mail modules were delivered with unit tests - **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. only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.
## The shape ## The shape
One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**, One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**,
opened **once**, after the review. It runs in five phases, and its time is bounded: opened **once**, after the review. 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:
| Phase | What happens | Bound | | Step | What happens | Bound |
|---|---|---| |---|---|---|
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes | | 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, unit tests only | 1 to 2 hours | | 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; reports go both ways; at most 3 rounds | 15 minutes | | 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 forge's merge checks run the integration tests; no further review | one check run | | 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 |
| 4. Deliver | merge; the delivery plan builds, publishes and sends the whole feature | its delivery plan | | 4. Deliver | merge; the delivery plan builds, publishes and sends the whole feature | its delivery plan |
A feature that cannot be developed in about two hours is split into **steps**. Each step runs all five **One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, tried before its review, reviewed once,
phases on its own; the next step starts only when the previous step's delivery plan is done. A step is the opened once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
unit that ships, so nothing finished waits for work that is not. merge check, an Approve and a delivery plan of waiting, so the fewest cuts is the fastest. Milestones are cut by
release order when the design is broken up (playbook [04](04-build-handoff.md)); a release-order dependency found
inside a milestone is a fault of that cut, said to the operator, never a silent extra step.
**Scope is fixed at phase 0.** An issue found during a feature goes to the next step or the next feature, **One design is built at a time** (the operator, 2026-10-17). While the milestones of one design are being
never into the running one, unless it blocks the running one. built, no milestone of another design starts: its issues stay `open`, and nobody moves them to `diagnosing` or
`fixing`. The next design starts when the last milestone of the one being built has its delivery plan done. A
lead who finds another design's milestone already in `fixing` asks its owner before starting its own. A small
fix ([playbook 08](08-small-fixes.md)) and an issue that is in no design's milestones are not held by this rule.
## Phase 0 — Prepare (the lead, larger model) **Scope is fixed at the prepare step.** An issue found during a feature never goes into the running milestone, unless it
blocks the running one: it is fixed in between or right after the delivery, as the next paragraph says, or goes to the
next milestone or the next feature.
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches. The **An issue opened along the way is fixed by the session that opened it** (the operator, 2026-10-21): it is never only
branch is never protected. listed under "Opened along the way" and left. A small one (one repository, about an hour) is fixed in between, as a
small fix ([playbook 08](08-small-fixes.md)) while the milestone waits on a check, an Approve or a delivery plan; the
rest are fixed right after the milestone's delivery, before the next milestone starts, together on one branch where
they share a repository. Either way it stays out of the running milestone's branch and pull request, unless it blocks
the running milestone, so the scope above holds. Only an issue in another design's milestones (one design is built at
a time), or one that needs a decision or the operator first, waits, and the report says which and why. Each report's line "Opened along the way" says for every
issue whether it is being fixed in between, after the delivery, or waits, and on what.
## Step 0 — Prepare (the lead, larger model)
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches (but see the
exception for a new manifest word in phase 3: a release that must merge first has a name of its own). The
branch is never protected. **One milestone is one branch per repository** (the operator, 2026-10-17), with one
folder `.work/<slug>/`; never a second name for part of it (`-2`, `-step2`).
2. **Make the worktrees:** one per touched repository under `.work/<slug>/<repo>`, branched off `main` 2. **Make the worktrees:** one per touched repository under `.work/<slug>/<repo>`, branched off `main`
(`git worktree add .work/<slug>/<repo> -b feat/<slug> origin/main`). The lab beds find the other (`git worktree add .work/<slug>/<repo> -b feat/<slug> origin/main`). The lab beds find the other
repositories by sibling path, so add the untouched siblings the lab reads as detached worktrees on repositories by sibling path, so add the untouched siblings the lab reads as detached worktrees on
`main` (`git worktree add --detach .work/<slug>/mesh-tools main`), never symlinks. `main` (`git worktree add --detach .work/<slug>/mesh-tools main`), never symlinks. **A repository's own
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 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. A fixing agent that meets an undecided design question
stops, and its run is spent again. 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 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: reads instead of rediscovering the code, and it is reused by every agent and every round:
- the issues it builds, in order, each with: the files and functions it changes, the decisions it builds - the issues it builds, in order, each with: the files and functions it changes, the decisions it builds
with (quoted, with where each was decided), the test to write first, done-when in one sentence; with (quoted, with where each was decided), and its acceptance criteria (item 5), each with the test that
proves it;
- the repository's map for this feature: the packages and files involved, with one line each on what they - the repository's map for this feature: the packages and files involved, with one line each on what they
hold, and the patterns to copy (file:line); hold, and the patterns to copy (file:line);
- what not to touch, and what lands beside it in other repositories; - 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 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 the home (`TMPDIR`), and the unit tests to run (never a database server: tests that need one are
integration tests, which the forge's checks run). integration tests, which `mesh-controller.try` and the forge's checks run).
When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with 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. file:line, and the lead writes the file from its answers.
5. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository. 5. **Write the findings into the issues** (the operator, 2026-10-17: the better the content of the issues, the
better work the agents do, and the less review is needed). Before any agent starts, each issue carries, with
`issue-tracker.note`: every file and test that touches the change, found by search and not from memory
(file:line); **every other reader and writer of each value the change adds or changes** (a field, a key, a kind
of record), each with what it does with the new value, and a criterion for each that could treat it wrongly
(2026-10-19: the router's sync with the controller ended every condition the controller did not list, and the
survey, which followed only the new condition's own path, never named it); how the change is checked and delivered end to end (which test proves its done-when, which
modules its delivery plan moves, to which machines); and for a group, its merge order. The context file
points to these notes; an issue reread by another session or a later agent then holds what was found.
When the reading is large, it starts with a read-only survey agent on the smaller model.
**Each issue carries its acceptance criteria, written by the lead** (the operator, 2026-10-18: the larger
model writes them, and they guide every test). Each criterion is one sentence, given, when, then, about what
the module or the operator observes, never about how the code does it, and names the level of the test that
proves it: unit, integration (a bus, a store or a process started) or end to end (none exist yet, so such a
criterion is proven live after the delivery and said so). Together they cover the issue's done-when and the
cases its life passes through: on 2026-10-18 mesh-delivery's group life (a group of three, a member's own
check, a close in a group, a second pull request at the same commit, a stop and a resend) had no criterion,
and each case was found by a failure on the live mesh. Each criterion is numbered in its note and cited as
`<issue>/AC<n>` (`1118/AC2`). They are an `issue-tracker.note` headed `Acceptance criteria`, a revision a new note that replaces the older one, until the tracker holds them as a
field (issue 1118). A criterion the reviewer adds in step 2 is written onto the issue the same way.
**The survey sizes each issue, and an issue sized over an hour is split before any agent starts** (the operator,
2026-10-17), into issues of about 30 minutes each, one change each, in the order they land: the bound of a
fixing agent's issue is then a real bound. On 2026-10-17 one issue held four changes sized at four to six
hours; it ran past its 30-minute bound for over an hour, and the reports could only say "still on part D".
6. **Look for the orders a release hides, before any agent starts** (2026-10-18: design 47's release 1-2 was cut
as one milestone and its prepare step found three orders inside it, each a cut of its own). Read, for each
repository of the release, what it takes from the others and how: an SDK change still ships before its users
(they vendor a pinned SDK, and a pin to a commit no branch holds is a download that fails later, issue 1148);
a new key on the wire between the controller and the node-engine is sent only to a node that says it reads it,
since the node-engine's decoder refuses an unknown key; the first module of a repository the catalogue has never
built registers only by the route the controller allows for it; and a part that needs something the bus or a
manifest cannot yet give (an event only a system account hears, a bucket whose keys expire) needs its decision
first. **A new manifest word is read by more than the controller**: every program that parses a manifest strictly
(with the SDK's `ParseManifest`) refuses a key its pinned SDK does not know, so the prepare step lists them by search
(`ParseManifest` across every repository, with the SDK commit each pins) and each one older than the word ships
first (2026-10-21: the milestones of design 60 counted only the controller, and the prepare step of the catalogue's
milestone found the build module mesh-build-go and the delivery's indexer reading manifests strictly with an SDK
from before the word, which became a release of its own). Each order found is said to the operator with the cut
it asks for, never built around.
7. **Write fixtures from the shape the other side writes, never from memory** (2026-10-18: a watchman's tests
mirrored a build key the controller never writes, and every test passed): copy a value from the writer's code or
from the live bus, and name where it came from.
8. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
## Phase 1 — Develop (one fixing agent per repository, smaller model) ## Step 1 — Develop (one fixing agent per repository, smaller model)
- **One agent per repository, kept for the whole feature.** It reads its context file once and builds the - **One agent per repository, kept for the whole feature.** It reads its context file once and builds the
issues one after another, committing each locally with a message naming the issue ("Keep the assignments issues one after another, committing each locally with a message naming the issue ("Keep the assignments
on the bus (issue 652)"). It is not replaced per issue: the lead continues the same agent (by its id) with on the bus (issue 652)"). It is not replaced per issue: the lead continues the same agent (by its id) with
the next instruction, so its context is paid for once. the next instruction, so its context is paid for once.
- **Unit tests only, of what changed, each well under a second** (how-we-build §5, ADR 0391). Each issue's - **Unit tests only, each well under a second** (how-we-build §5, ADR 0391). **The whole unit suite runs before
first test fails before its change and passes after; `gofmt`, `go vet` every commit** (the operator, 2026-10-17), not only the tests of what changed: it takes under a second, and a
and the repository's unit tests stay green. The integration tests (`merge-check.sh` with the store and the change breaks tests in packages it never touched. Each issue's first test fails before its change, on an assertion about what the
bus) wait for phase 3. code does, and passes after. **A test that fails only because it does not compile has not failed first**: where
- **Nothing is pushed** and no pull request is opened in this phase. Never land on a red check: run the the test calls what the change adds, the agent takes out the change's effect (the line that does it, not the new
function), sees the test fail, and puts it back (2026-10-19: a check request's new field was proven by a test of
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 forge'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. checks, 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 - **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; 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 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 is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
move another issue. Report back in under 150 words: commits, tests added and that they failed first, the move another issue; **never run an integration test on the machine** (no `go test -tags integration`, no `merge-check.sh`,
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). Report back in under 150 words: commits, tests added and that they failed first, the
check results, anything you stopped on. check results, anything you stopped on.
- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead - **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. settles it, updates the context file, and continues the agent.
@@ -105,71 +191,118 @@ never into the running one, unless it blocks the running one.
tests pass; if another landed first, it merges again and repeats. In a shared clone, fetch the feature tests pass; if another landed first, it merges again and repeats. In a shared clone, fetch the feature
branch by name before branching from it. Never resolve a conflict with a blind `git merge-file --union`. branch by name before branching from it. Never resolve a conflict with a blind `git merge-file --union`.
## Phase 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes) ## Step 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)
1. **Bring every feature branch up to the current trunk** (fetch by name, then merge), so the reviewed tree is 1. **The reviewed tree is the tree that was tried**: every feature branch was brought up to the current trunk
the tree that is pushed. 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
trunk that moves later is met by the forge's merge check, not by another merge here.
2. **One reviewer reads every repository's local commits** together, against the context files and the 2. **One reviewer reads every repository's local commits** together, against the context files and the
decisions. Its report is per repository: each finding as file:line, the failure it causes, and the fix. decisions. **It is one agent, kept for the whole release** like the fixing agents: the lead continues it by
its id for every later round, for the extra cycle after a failed check (step 3) and for its review record, so it
reads the decisions, the issues, the context files and the commits once (issue 1042). A fresh reviewer per
round or per mark pays for all of that again. Its report is per repository: each finding as file:line, the failure it causes, and the fix.
It says which round it is in ("round 1 of at most 3"). 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 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. with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
4. **The reviewer reads the answers and the new commits**, and approves or reports again (step 5 says what a round 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).
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. may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
5. **A finding outside the issues' done-when is never a change asked, in any round.** More hardening, a 6. **The reviewer reads every acceptance criterion against its test**: each criterion has a test at its level
that would fail without the change, and no test proves less than its criterion says. A criterion missing
for a case the issue's done-when needs is written onto the issue (step 0, item 5) and is a finding; a test
missing for a criterion is a finding.
**A rule keyed on a property is checked against everything else on the mesh that has it.** When a change
decides by a property the bus or the store gives (a stream's retention, a folder's name, an account's kind),
the reviewer lists what else already has that property, live, and asks what the rule does to it. On
2026-10-19 release 1 of feature mail-sender made a work-queue stream's dead letters drop-only by the bus's
work-queue retention; three review rounds passed it, and only its try showed that every seat's work stream has
that retention too, so dead asks across the mesh would no longer have been delivered again (issue 1178, fixed
before its pull request by a mark only a declared module work queue carries).
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 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 report, and the round does not wait for it. What a round asks is only what makes the change wrong against
its issues and its decisions. its issues, their acceptance criteria and its decisions.
## Is the coast clear? ## Step 3 — Check (no review)
Before the push of phase 3 (and of a small fix, playbook [08](08-small-fixes.md) step 5), the lead checks, for every 1. **Push the reviewed heads, try again a head the review changed, then open one pull request per repository,
repository the change touches, whether a **release branch** is in flight there: a branch other than `main` that together.** **The integration tests run before the pull requests are opened**, first before the review (step 1)
collects a feature's merged work to bring it into `main` as one delivery. and again for a head the review changed since its try: `mesh-controller.try` with the
repository and the pushed branch runs the repository's `merge-check.sh` (main's version) on the merge, in the
1. **Look for one.** A pull request into `main` whose head is a release branch (`gitea.gitea_open_pulls`), a toolchain containers and against the throwaway store and bus a merge check gets, and answers with an id to
release branch holding merged work that `main` does not have yet (`git log origin/main..origin/<branch>`), or read back. A try that fails goes to step 3 item 3 as a failed check does; the pull requests are opened only
a delivery plan of that release branch still running (`mesh-controller.plans`). when every head's last try passed (the operator, 2026-10-18: three pushes in a row failed their first merge check on
2. **Compare the files.** `git diff --name-only origin/main...origin/<release branch>` against the files the faults a try finds in minutes). Never `merge-check.sh`, `go test -tags integration` or a store or bus started
change touches. by hand on the machine (the operator, 2026-10-18). Pull requests in two or
3. **Overlap: hold.** If the release branch touches a file the change touches, nothing is pushed into that repository
until the release branch's delivery plan is done. Then the feature branch is brought up to the new `main`, the unit
tests run again, and the push follows. The hold is on the wait list (its bound: the release branch's delivery plan),
and is said to the operator with what the change waits for.
4. **No overlap: go on.** A release branch in another repository, or one that shares no file with the change, does not
hold it; its merge joins the merge window as any other (ADR 0276).
Merging beside a release branch still in flight would make its lead resolve someone else's conflicts in the
middle of it, and could send part of the change with its delivery (2026-10-16: the file module's
controller part touched four files feature 57's release branch also changed).
## Phase 3 — Check (no review)
1. **Push every feature branch and open one pull request per repository, together, once the coast is clear**
(above). The integration tests
run only in the merge check (the operator, 2026-10-16), never before. Pull requests in two or
more repositories that share the branch name are one delivery group (ADR 0239 §2). more repositories that share the branch name are one delivery group (ADR 0239 §2).
Where a session pushes through the forge tool `gitea_commit` (one commit on the forge's current base), the 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. reviewer first confirms that commit's tree equals the reviewed one. **A large change is pushed by a pusher agent** on the smaller
2. **The forge's merge checks run the integration tests once** (`mesh/merge-gate`, `mesh/repo-check`). No model: `gitea_commit` patches of about 30 KB each, a file copied from another path sent as a copy (`copy_from`)
second review: phase 2 reviewed this tree. and not as its bytes, and the forge's tree compared with the local one after the last part (2026-10-20: R4c-2's
3. **A check that fails** goes back to the reviewer with the failure, for **one more cycle** of phase 2 with two repositories went up in parts this way, and the lead's own context stayed free for the review). **Each pull request's description carries
the same fixing agents; then push once again. the review record** (ADR 0421 §5), one line the lead writes from the reviewer's approval:
4. **The step's acceptance is proven before it is marked ready.** A step that adds or changes a module `Reviewed-by: <who@node> at <full sha>` (the issue 1022 note gives its form). mesh-delivery reads it at every new
head and puts it in the Approve's ask; a missing line, or one naming another commit, is said first in the ask.
It is never a gate.
**The description names its issues as `issue <n>`, and only its own** (2026-10-21: okioki #9 listed its four
issues bare, `Issues: 1608, 1646, …`, and named a milestone-2 issue as `issue 1654` and a follow-up as
`issues 1665, 1666, 1667`, so mesh-delivery carried 1654 and 1665 as the pull request's issues and the Approve's
ask named them): mesh-delivery takes a pull request's issues from the words `issue <n>` and `issues <n>` in its
description (the plural yields its first number only), so write `issue 1608, issue 1646`, and name any other
issue in words, never by `issue <n>` or `issues <n>`.
**A catalogue pull request that removes a module, or a subject a module names, is also judged by the
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
(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 forge's merge checks run the integration tests again, as the gate** (`mesh/merge-gate`, `mesh/repo-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
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 change to `merge-check.sh` is judged by main's version of it**, so a test the change adds must pass under
either version: one that runs the new script's behaviour runs a file of its own, never `merge-check.sh` itself.
**So a change that needs the new script to judge it ships the script 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 integration tests behind a build
tag in the same pull request as the script that runs that tag, and main's script would have skipped every
tagged test, its acceptance tests among them. The prepare step looks for this: a change to `merge-check.sh` or to
what it selects (build tags, the store-tests list) is cut ahead of the milestone.
4. **The milestone's acceptance is proven by its check.** A milestone that adds or changes a module
carries an integration test that starts the module whole, its process against the real bus and store, and carries an integration test that starts the module whole, its process against the real bus and store, and
shows the step's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not shows the milestone's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
that proof; the mesh has no end-to-end tests yet, and the lab is not used (the operator, 2026-10-16). that proof; the mesh has no end-to-end tests yet, and the lab is not used (the operator, 2026-10-16).
5. **The reviewer marks each pull request ready as soon as the acceptance is shown** with **A new manifest word's test goes through the push that meets it first, not only the verb that fills it.**
`mesh-delivery.mark-ready` (the delivery of the head it approved, who reviewed it and why), which writes the On 2026-10-19 release 1 of feature mail-sender tested a process's own bus account through `module issue`
label `mesh/ready` and then `ready at <full sha>`, the order the forge counts (issue 913); mesh-delivery merges it when its checks pass alone; the first push after mesh-mail declared the process composed a random 40-character stand-in for the
(ADR 0364). The operator's word is waited for only when the step changes what the operator sees, removes account file before anyone issued it, and the process crash-looped until the account was issued and pushed by
data, or needs a step only the operator's terminal can take; the lead says which before the step starts. 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
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,
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.
**One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads **One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads
manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first; manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first;
then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Plan such a feature as two steps. then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way.
**A new key the node-engine checks is such a word too** (issue 1051): the controller judges every machine with the
node-engine's own validator, vendored at the commit `go.mod`'s `replace` pins, so a key added to the node-engine is
refused by the gate until the controller's pin moves to the delivered node-engine (`go mod vendor`); the feature is
then three releases, the node-engine's, the controller's, the catalogue's. **Every release that must merge before
the catalogue's is on a branch name of its own** (`feat/<slug>-node-engine`, `feat/<slug>-controller`): on the
feature's name it joins the catalogue's delivery group, and a group merges only whole, so neither merges.
The exception ends when milestone 3 of design 47 is delivered (issue 977, ADR 0402, ADR 0403): the group's composed
check, judged by the group's controller, is then each member's gate, and the feature is one group.
## Phase 4 — Deliver ## Step 4 — Deliver
The merge starts the delivery plan: build, publish, send. A merge into a branch other than `main` is not a The merge starts the delivery plan: build, publish, send. A merge into a branch other than `main` is not a
delivery: no machine receives it (issue 889 while it still starts one). The lead follows it to done delivery: no machine receives it (issue 889 while it still starts one). The lead follows it to done
@@ -177,14 +310,39 @@ delivery: no machine receives it (issue 889 while it still starts one). The lead
naming its pull requests, and removes what is left: no `feat/<slug>` branch and no `.work/<slug>` worktree naming its pull requests, and removes what is left: no `feat/<slug>` branch and no `.work/<slug>` worktree
survive. survive.
**What a merge moves is read from its plan before the operator is told.** A change to mesh-sdk rebuilds every
module that takes the SDK, with their backups and their asks for a person (issue 721): on 2026-10-20 the lead
said an SDK merge moved nothing, and its delivery plan moved mesh-sdk with 41 dependents to all four machines.
The lead reads the merge gate's plan in `mesh-delivery.show` and names what it moves before asking an Approve.
**A failed plan's Retry is asked of the operator alone, never beside an Approve.** An Approve's merge starts a
plan, and the controller refuses a Retry while another plan runs; on 2026-10-20 the operator chose both at once,
the merge's plan started first, the chosen Retry was refused and nobody asked it again (issue 1408). So the lead
never tells the operator to answer a Retry while an Approve is open, and reads the failed plan again after the
answer: a Retry refused is asked again once the other plan ends.
**A push by hand is read from its plan first.** `mesh-controller.push` sends a machine everything it is owed, not
only what the lead changed: on 2026-10-21 a push of the control node to tell the bus of two new accounts also moved
mail's module to a build its upgrade policy holds for a person's word, and recreated one of its containers. Before
every push by hand, the lead reads `mesh-controller.plan` with `diff` for that machine, and for the machine holding
the bus when a push issues accounts, and pushes only when the plan names what the lead means to change.
**A new module's first assignment is its first real composition.** The merge gate composes every machine as it is
assigned, so a module that no machine runs yet is composed by no check: on 2026-10-21 the build modules' first
assignment found two faults no merge check could see (issue 1537, a template the controller filled for a value the
forge serves only later; issue 1542, the node-engine refusing the module's own `.ssh`). So a new module is assigned
to one machine first, its plan read before the push and its apply read after it (`mesh-controller.node`: the
answer, and `status` for a failed resource), and taken back at once when that machine cannot compose or apply, so
the machine keeps applying everything else; the other machines follow once it runs there.
## Models ## Models
| Who | Model | Why | | Who | Model | Why |
|---|---|---| |---|---|---|
| The lead (phase 0 decisions, the context files, following and reporting) | larger | judgement | | The lead (step 0 decisions, the context files, following and reporting) | larger | judgement |
| Read-only questions about the code for the context files | smaller | reading, answered with file:line | | Read-only questions about the code for the context files | smaller | reading, answered with file:line |
| Fixing agents (phase 1, and the fixing in phases 2 and 3) | smaller | building to a written context | | Fixing agents (step 1, and the fixing in steps 2 and 3) | smaller | building to a written context |
| The reviewer (phase 2, and the extra cycle in phase 3) | larger | judgement, once per round | | The reviewer (step 2, the extra cycle in step 3 and the review record; one agent, continued by its id) | larger | judgement, once per round |
| Decision records | larger | judgement | | Decision records | larger | judgement |
Every agent the mesh runs draws on one weekly allowance shared by every session, and the larger model Every agent the mesh runs draws on one weekly allowance shared by every session, and the larger model
@@ -200,56 +358,104 @@ on, with when it started and its bound.
|---|---|---| |---|---|---|
| a fixing agent's issue | 30 minutes | ask it where it stands; split or take over | | 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 | | the review | 15 minutes a round | ask the reviewer; stop at round 3 |
| a merge check | 15 minutes | read the build queue (`mesh-controller.queue`) and the check's log | | a merge check | 15 minutes | read `mesh-delivery.show` (its state and verdict), then the check's log; the build queue (`mesh-controller.queue`) never lists a running check |
| the ready mark | 15 minutes after the acceptance is shown | the reviewer marks it, or the lead does | | 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 |
| a release branch in flight that shares files | its delivery plan done | report it every ten minutes; bring the branch up to `main` after | | a merge after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
| a merge after ready | 5 minutes after the checks pass | read `mesh-delivery.show` for the delivery |
| a delivery plan | its class's budget (`mesh-delivery.times`), at most 30 minutes | read `mesh-controller.plans`, then `mesh-delivery.stalled` | | a delivery plan | its class's budget (`mesh-delivery.times`), at most 30 minutes | read `mesh-controller.plans`, then `mesh-delivery.stalled` |
**Every ten minutes, and at once when something lands, stops or needs the operator,** the lead reports, in **Every wait on the list has a watcher** (the operator, 2026-10-17). A session runs only when a message reaches
one of the two layouts below. Times are local and absolute; every number has its unit and its bound or it, so a check, a merge, a delivery plan, a record's review or the operator's Approve on a channel that lands
baseline; an issue is named by its number and a few words, never by its number alone. while the lead's turn has ended wakes nobody: on 2026-10-17 an approved group sat half merged for 14 minutes
unseen (issue 1032). For each wait the lead starts a watcher, an agent on the smaller model that changes nothing,
never the smallest (the operator, 2026-10-17).
It loops `mesh_wait` on the event families of what it watches (`mesh-delivery-gate.transition` and
`mesh-delivery-gate.group` for a delivery, for example; the families a watcher may wait on are those the mesh granted
the tool runner, and a refusal names them) with a timeout under 60 seconds, since the call itself ends near
a minute. After every answer or timeout it reads the state from the verb that owns it (`mesh-delivery.show`,
`mesh-controller.plans`, `knowledge-base.get`), because an event only wakes it (ADR 0367). It finishes, and so wakes
the lead, when the state changes or the wait passes its bound. The lead never sleeps or polls by hand to wait.
**A watcher watches one wait and ends with it.** It is never re-pointed at the next pull request or plan: on
2026-10-19 one watcher sent on to three pull requests in turn missed that the third's checks had passed at 04:58,
and the lead told the operator for 15 minutes that they were still running. The next wait gets a fresh watcher,
and before a report the watcher reads the owning verb once more and says only what it says now.
**An ask is said to exist only after it is read on the router.** Before the lead tells the operator that an
Approve, a Retry or a Release waits for them, it reads `operator-channel.asks` and finds that ask open by its key;
on 2026-10-20 an ask was named to the operator that the router did not hold. A merge the gate passed but whose
ask is not on the router is the fault of the Approve's wait in the table above.
**The full layout** (in the session), the issues as a tree: grouped by repository, each issue under the one it **Every ten minutes (the operator, 2026-10-17), every two minutes while a delivery plan runs, and at once when something lands, stops or
waits for, marked ✓ landed, ▶ fixing, ⏸ waiting, ✗ stopped: needs the operator,** the lead reports, in one of the two layouts below (the operator, 2026-10-16: an overview
of the issues, often). Times are local and absolute, from the clock (`date`), never guessed; every number has its
unit and its bound or baseline; an issue is named by its number and a few words, never by its number alone.
**The full layout** (in the session) is the whole design as a tree (the operator, 2026-10-17: always the full
overview): every milestone of the design with its state, and under the running milestone its repositories and
their issues, each issue under the one it waits for, marked ✓ landed or delivered, ▶ fixing, ⏸ waiting, ✗ stopped:
``` ```
Feature <slug> · step <n> of <m> · phase <name>, <minutes used> of <bound> min Feature <slug> · milestone <n> of <m> · step <name>, <minutes used> of <bound> min
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting
mesh-controller (agent: fixing, 2 of 3 landed) Design <nn> · <its title>
├── ✓ 652 assignments on the bus landed 14:02 ├── ✓ milestone 1 <its outcome, a few words> delivered 12:58
├── ▶ 736 placement words leave the layers fixing since 13:51, 23 of 30 min ├── ▶ milestone 2 <its outcome, a few words>
│ └── ⏸ 751 config keys named (mesh-catalog) waits for 736 │ ├── mesh-controller (agent: fixing, 2 of 3 landed)
└── ✓ 654 a file names its template landed 13:20 │ │ ├── ✓ 652 assignments on the bus landed 14:02
mesh-catalog (agent: waiting) │ │ ├── ▶ 736 placement words leave the layers fixing since 13:51, 23 of 30 min
└── ⏸ 751 (above, under 736) │ │ │ └── ⏸ 751 config keys named (mesh-catalog) waits for 736
│ │ └── ✓ 654 a file names its template landed 13:20
│ └── mesh-catalog (agent: waiting)
│ └── ⏸ 751 (above, under 736)
└── ⏸ milestone 3 <its outcome, a few words> <its issues>
Opened along the way: <each issue opened during the feature, its number and a few words, and when it is fixed: in between, after the delivery, or waits on what> — or "none"
Past its bound: <the wait, how long, what is being done> — or "none" Past its bound: <the wait, how long, what is being done> — or "none"
Needs you: <one line per question, with my recommendation> — or "nothing" Needs you: <one line per question, with my recommendation> — or "nothing"
Allowance: week <n> % (<+d> since the last report), session <n> % Allowance: week <n> % (<+d> since the last report), session <n> %
Next: <the next action, one line> Next: <the next actions up to the delivery, in order>
``` ```
**The short layout** (on a channel such as Telegram, or when nothing changed but time): **The short layout** (on a channel such as Telegram, or when nothing changed but time):
``` ```
<slug> step <n>/<m> · <phase> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> % <slug> milestone <n>/<m> · <step> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> %
``` ```
A report says what changed since the last one first; an unchanged table is not repeated, the short layout A report says what changed since the last one first. In the session every report is the full layout, the whole
says it instead. tree of the milestone's issues included, also when only time passed: the operator reads the state from it without
asking. The short layout is for a channel.
## The context of the lead's own session ## The context of the lead's own session
At the end of each phase, and whenever the session's context grows large or spread over unrelated work, the At the end of each step, and whenever the session's context grows large or spread over unrelated work, the
lead prepares a `/clear` as the mesh's instruction "prepare before clear" says: a save point with the lead prepares a `/clear` as the mesh's instruction "prepare before clear" says: a save point with the
feature's phase, its branches and heads, the context files' paths, the agents' ids, the wait list and the feature's step, its branches and heads, the context files' paths, the agents' ids, the wait list and the
next action; then the prompt the operator pastes. The context files make the feature resumable after a clear. next action; then the prompt the operator pastes. The context files make the feature resumable after a clear.
## The design's state is written back
Other sessions know what is built only from the design's frontmatter and the issue tracker; what the lead
knows and does not write back, nobody else knows. A design merged in one delivery with three of its tasks
left, its frontmatter still saying `designed`, read to another session as not built, and a second design
was started beside it (issue 1009, 2026-10-17). So the lead writes the design's state back at each of these
moments, the same hour, an hq change going through [playbook 08](08-small-fixes.md):
- **The first milestone starts:** the design's `status: in-progress` ([playbook 04](04-build-handoff.md) step 7),
if it is not already.
- **A milestone's delivery plan is done:** its issues are resolved (step 4), with `fixed-by` naming its pull
requests. The design's milestone entry is not changed: it names the milestone's issues, and whether they are
delivered, when and by what is read from the issue tracker when it is asked, never copied into the design
(issue 1038; AGENTS.md: cross-cutting views are generated when they are read).
- **The building pauses with work left** (another design takes its turn, a hand-over, the session's end): the
design stays `in-progress`, and each issue left gets a note with `issue-tracker.note` saying what it waits
for and what comes after it ("built after design N is delivered, before design M"). The first issue of
a design that waits its turn gets the same note.
- **The last milestone is delivered:** [playbook 02](02-graduation.md), "When something ships": the as-is is
written and the design says `implemented`.
## Issues that stop ## Issues that stop
An issue stays in `fixing` only while an agent works on it. A step that stops, for a wait past its bound, a An issue stays in `fixing` only while an agent works on it. A milestone that stops, for a wait past its bound, a
hand-over, or the session's end, moves each of its issues not delivered back to `open` with hand-over, or the session's end, moves each of its issues not delivered back to `open` with
`issue-tracker.move` and a note saying how far it got (its branch, its last commit, what is left). A list of `issue-tracker.move` and a note saying how far it got (its branch, its last commit, what is left). A list of
issues in `fixing` that nobody works on reads to the operator as stuck work. issues in `fixing` that nobody works on reads to the operator as stuck work.
@@ -258,19 +464,46 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
- A feature's issues show their commits on the feature branch (`git log --grep 'issue <n>'`), and no pull - A feature's issues show their commits on the feature branch (`git log --grep 'issue <n>'`), and no pull
request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in
phase 1, is the failure this playbook prevents. step 1, is the failure this playbook prevents.
- A context file exists per touched repository under `.work/<slug>/` while the feature runs. - A context file exists per touched repository under `.work/<slug>/` while the feature runs.
- No issue of a running milestone is sized over an hour in its prepare note; an issue whose fixing agent passes
twice its bound was not split.
- Every issue of a running milestone has a note from the prepare step naming its files and tests (file:line), how
it is checked and delivered, and for a group its merge order (`issue-tracker.get`); an issue moved to `fixing`
without one is a prepare step skipped.
- Every issue of a running milestone has a note headed `Acceptance criteria` before it moves to `fixing`
(`issue-tracker.get`), each criterion naming its test level, and every criterion's label is cited by a test in the
pushed tree (`git grep -n '<issue>/AC<n>'`); an issue without them is a prepare step skipped, and a
criterion no test names is a review that missed it (until issue 1118 makes the tracker refuse it).
- Every fixing agent's report gives the whole unit suite's result and time; a report naming only some packages'
tests is the whole-suite rule not kept.
- The lead's save point names each watcher with its model; a watcher on the smallest model is the failure of
"Following and reporting".
- A report or a save point that names a "phase" of a milestone is the old wording: a milestone has steps.
- Every repository's own clone says `main` (`git -C <clone> branch --show-current`); another branch there is
work done outside a worktree (issue 1010).
- After a feature's delivery, `git branch -r | grep feat/<slug>` and `git worktree list` return nothing for it. - After a feature's delivery, `git branch -r | grep feat/<slug>` and `git worktree list` return nothing for it.
- More than one open pull request in a repository that share no feature name, or a feature step whose issues - A design whose milestone issues are partly resolved says `in-progress`, and one whose issues are all
resolved and delivered says `implemented`: read its Milestones section against `issue-tracker.get` for each
issue. A design that says `designed` with a resolved milestone issue is a state not written back.
- A design's Milestones section names each milestone's issues and nothing of their state: a "delivered", a date
or a pull request in an entry is a copy of the tracker's state (issue 1038), read with
`grep -n 'delivered' <design>` over its Milestones section.
- Every open issue of a design that says `in-progress` and has no agent on it shows, in `issue-tracker.get`, a
note saying what it waits for; one without such a note is a pause not written back.
- The milestone issues in `fixing` (`issue-tracker.list`) belong to one design at most: milestone issues
of two designs in `fixing` at once is the failure of "one design is built at a time".
- More than one open pull request in a repository that share no feature name, or a milestone whose issues
stay in `fixing` for more than a working day, is the smell: stop and bring it to the operator. stay in `fixing` for more than a working day, is the smell: stop and bring it to the operator.
- A review report that asks a change outside the issues' done-when, or a fourth round, is the failure of the - A review report that asks a change outside the issues' done-when and acceptance criteria, or a fourth round, is the failure of the
review rules; `issue-tracker.list` with `flag: abandoned` lists issues left in `fixing` with nobody on them. review rules; `issue-tracker.list` with `flag: abandoned` lists issues left in `fixing` with nobody on them.
- A pull request reviewed and green but not marked ready for more than 15 minutes is a lead that did not - A pull request green for more than 5 minutes with no Approve asked is a fault of mesh-delivery
mark it. (`mesh-delivery.show`), said to the operator.
- A pull request opened into a repository whose release branch was in flight and shared a file with it is the failure of - Every review record of a release names one reviewer (`mesh-delivery.show`, the review record), and the lead's
*Is the coast clear?*; the lead's report names the check it made before the push. save point names one reviewer id per release; a second reviewer within one release is a reviewer not kept
- A step that adds or changes a module marked ready with no integration test that starts the module whole is (issue 1042). A pull request whose ask says it has no review record at its head is a record not written.
the failure of phase 3's fourth step. - 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 - A bed run from `.work/<slug>/mesh-lab` that fails naming a `.work/<slug>/<repo>/…` path it cannot find is a
missing sibling worktree, not a mesh fault. missing sibling worktree, not a mesh fault.
+67 -29
View File
@@ -2,7 +2,10 @@
**Trigger.** One issue in one repository, small enough to build in about an hour: a bug with a known cause, **Trigger.** One issue in one repository, small enough to build in about an hour: a bug with a known cause,
a missing check, a wording, a playbook or design edit. Anything larger, or anything that needs a change in a a missing check, a wording, a playbook or design edit. Anything larger, or anything that needs a change in a
second repository first, is a feature and follows [playbook 07](07-feature-branches.md). second repository first, is a feature and follows [playbook 07](07-feature-branches.md). A feature is the
larger kind: it touches several modules and has a research effort or a design before it. **Needing a decision
does not make a fix a feature**: the decision is settled first (step 1) and the fix stays small (the operator,
2026-10-16).
**Related issues in one fix.** A small fix may cover two or three related issues when they are in the same **Related issues in one fix.** A small fix may cover two or three related issues when they are in the same
repository, mostly in the same module, and together still fit in about an hour: they touch the same files, or repository, mostly in the same module, and together still fit in about an hour: they touch the same files, or
@@ -15,36 +18,47 @@ hour is a feature.
## The aim ## The aim
A small fix spends its time on the fix, and runs the long part once: **one review, one push, one check run, 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 push is local and fast. one delivery**. Everything before the try is local and fast.
## Steps ## Steps
1. **Read the issue and the code it names** (the session's own model, or one read-only agent on the smaller 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 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, or by the operator. A fix that turns out to
need a decision or a second repository stops here and becomes a feature. 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,
naming the level of the test that proves it, as an `issue-tracker.note` headed `Acceptance criteria`. A
playbook or design edit has none: its check is `sh merge-check.sh`.
2. **Move the issue to `fixing`** with `by` as who@node. 2. **Move the issue to `fixing`** with `by` as who@node.
3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`. Write the 3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`, never in
test that fails today first; make the change; keep `gofmt`, `go vet` and the unit tests of what changed green the repository's own clone, which stays on `main` (playbook [07](07-feature-branches.md), step 0, item 2). Write the
(each well under a second; never a merge check or a database locally: how-we-build §5, ADR 0391). test that fails today first, on an assertion about what the code does: a test that fails only because it
Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq, does not compile has not failed first (playbook [07](07-feature-branches.md) step 1); make the change; keep `gofmt`, `go vet` and the repository's whole unit suite green
`sh merge-check.sh`). before every commit (each test well under a second; never a merge check or a database locally: how-we-build §5, ADR 0391).
4. **One review of the local commit, before anything is pushed** (a reviewer that did not write it, larger Check that it compiles with `go vet`, never `go build` in a module's folder: with one main package it writes the
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; a finding outside the issue's program beside `go.mod`, and `git add -A` takes it (issue 1577: two 5-10 MB programs committed on 2026-10-21,
done-when 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 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,
`sh merge-check.sh`). 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
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
its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the
operator's word on the pull request is its review, and the lead marks it ready on that word. operator's Approve of its merge is its review, and the review record says so.
5. **Push once and open the pull request, once the coast is clear** (playbook [07](07-feature-branches.md), *Is 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
the coast clear?*: no release branch in flight in the repository that shares a file with the fix). The forge's merge (ADR 0401), so a fix waits for no other change's delivery. `mesh-controller.try` on the pushed branch runs
check runs the integration tests, the only the integration tests, and the pull request is opened only when the head's last try passed; the forge's merge check runs
place they run (the operator, 2026-10-16). A failed check them again as the gate (playbook [07](07-feature-branches.md) step 3). They are never run on the machine. A
goes back to the same agent with the failure, and the reviewer reads only the change it makes. failed try or check goes back to the same agent with the failure, and the reviewer reads only the change it makes.
6. **The reviewer marks it ready at once** (for an edit that skipped the reviewer, the lead, on the 6. **The pull request's description carries the review record** (`Reviewed-by: <who@node> at <full sha>`,
operator's word) with `mesh-delivery.mark-ready` (the delivery of the head it approved, who reviewed it and playbook [07](07-feature-branches.md) step 3), and mesh-delivery asks the operator's Approve as soon as its
why), which writes the label `mesh/ready` and then `ready at <full sha>`, the order the forge counts (issue 913); 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
mesh-delivery merges it when its checks pass. 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 forge's checks
integration test starting the module whole against the real bus and store, run by the forge's checks
(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, (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. or needs the operator's terminal.
7. **Follow its delivery plan to done** (`mesh-controller.plans` with `wait` and `until: done`), prove the fix on 7. **Follow its delivery plan to done** (`mesh-controller.plans` with `wait` and `until: done`), prove the fix on
@@ -53,15 +67,34 @@ one delivery**. Everything before the push 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 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. (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
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.
**A second fault found by the delivery.** When a fix's delivery shows a second fault that keeps the issue's
done-when from holding, and that fault is in the same files, it is taken into the same issue as a second pull
request with a review round of its own. A fault in other code is opened as an issue of its own, and the first
issue waits for it (issue 937: mesh-catalog #335 was taken in; issue 943 was opened).
**A fix that moves a file to a new filler compares the values first.** When a fix makes a module's file filled a
new way for the first time (the provisioner client in place of the controller, a provider's entry in place of a
composed value), the fix lists every value the old way put into that file on each machine it reaches and shows,
in its review, that the new way gives the same values, or says which differ and why. A difference nobody looked
at is an outage: on 2026-10-16 the forge and the identity provider on the control node were down for 17 minutes
because postgres's consumers were served the port inside its container instead of the port it is published on
(issue 957). The comparison is read from the machines (the file as it is today) before the merge, not from the
code.
## Bounds ## Bounds
| Waiting on | Bound | Past the bound | | Waiting on | Bound | Past the bound |
|---|---|---| |---|---|---|
| building the fix | 1 hour | it is a feature: stop and plan it under playbook 07 | | building the fix | 1 hour | it is a feature: stop and plan it under playbook 07 |
| the review | 15 minutes | ask the reviewer where it stands | | the review | 15 minutes | ask the reviewer where it stands |
| the ready mark | 15 minutes after the review approved | the reviewer marks it, or the lead does |
| the merge check | 15 minutes | read `mesh-controller.queue` and the check's log | | the merge check | 15 minutes | read `mesh-controller.queue` and the check's log |
| the merge after ready | 5 minutes after the checks pass | read `mesh-delivery.show` for the delivery | | 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 after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
| the delivery plan | its class's budget (`mesh-delivery.times`), at most 30 minutes | read `mesh-controller.plans`, then `mesh-delivery.stalled` | | the delivery plan | its class's budget (`mesh-delivery.times`), at most 30 minutes | read `mesh-controller.plans`, then `mesh-delivery.stalled` |
A wait past its bound is said to the operator at once, with what is being done about it. A wait past its bound is said to the operator at once, with what is being done about it.
@@ -74,10 +107,15 @@ reading itself and uses one subagent only for the review.
## How it is checked ## How it is checked
- A small fix has one pull request with one or few commits naming its issue, opened after its review: a pull - 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 push. request with many heads is a fix reviewed after its pull request was opened.
- Its issue moves to `resolved` within the hour its delivery plan ends. - Its issue moves to `resolved` within the hour its delivery plan ends.
- A pull request reviewed and green but unmarked - A pull request with no review record at its head (the Approve's ask says so first), a fix to a module's
for more than 15 minutes, a fix to a module's running marked ready with no integration test of the module whole, or a review report running pushed with no integration test of the module whole, or a review report
asking a change outside the issue's done-when, is the failure of steps 4 to 6. asking a change outside the issue's done-when and acceptance criteria, is the failure of steps 4 to 6.
- A fixed code issue shows a note headed `Acceptance criteria` (`issue-tracker.get`) older than its first
commit, and each criterion's label is cited by a test in the pushed tree
(`git grep -n '<issue>/AC<n>'`); one without is step 1 skipped.
- An issue in `fixing` with nobody on it shows in `issue-tracker.list` with `flag: abandoned`; a stopped fix - An issue in `fixing` with nobody on it shows in `issue-tracker.list` with `flag: abandoned`; a stopped fix
moves it back to `open` with its note. moves it back to `open` with its note.
- The repository's own clone says `main` (`git -C <clone> branch --show-current`); another branch there is a
fix built outside a worktree (issue 1010).
+49
View File
@@ -0,0 +1,49 @@
# Playbook 09 — Cleanup
**Trigger.** The operator asks for a cleanup, or the forge and the working copies have gathered what nobody
uses: merged branches, stale pull requests, worktrees and folders of finished work.
**Who runs it.** A session on the operator's word, with the operator deciding every deletion. Agents read and
propose; a deletion is the operator's act or is done on the operator's word for the list it was shown.
## The aim
Remove what is provably done, and nothing else. A cleanup that deletes one piece of unfinished work costs more
than every stale branch it removed. So every item is shown with its evidence, and anything not provably safe is
reported and left.
## Steps
1. **Take stock, reading only.** One agent on the smaller model reads, changing nothing:
- the forge: every repository, every branch **as git refs** (the branch list tool misses some, issue 949),
every open pull request with its head, title and last update;
- each working copy on the machine: its worktrees, local branches, stashes, and the folders beside the
clones, with size and newest file.
It writes one report with each item, its class and its evidence, and a list of the tools it lacked.
2. **Classify each item**, by evidence, never by name or age alone:
- **Safe** — a forge branch whose tip is contained in its repository's default branch, with no open pull
request, not protected and not a long-lived branch the mesh treats as a trunk; a worktree that is clean,
whose branch is contained in the remote default branch, untouched for two hours and not in another
session's working folder; a local branch contained in the remote default branch.
- **Likely safe, not proven** — a branch whose every commit is patch-equal to one on the default branch
(the forge lands some commits under new hashes): listed, deleted only on the operator's word for it.
- **Kept** — unmerged commits, a dirty worktree, a stash, an open pull request, a protected or trunk-like
branch, a repository of another owner, another session's worktree.
- **Unknown** — a folder or file outside git: described, never opened where a secret may be, left.
Stale open pull requests (no update for three days) are reported with their author's issue, never closed.
3. **Ask the operator** with the counts per class and the report's place. One question: delete the safe
items as listed? The likely-safe and unknown items are separate questions, item by item when few.
4. **Delete only the approved list**, each with the command that refuses an unsafe case (a worktree removal
that refuses a dirty one, a branch deletion that refuses an unmerged one). A refusal is reported, never
forced. The operator approves a deletion on a channel that proves who answered, as an ask bound to that
exact list and carried out on the warrant (ADR 0387, design 58 milestone 7, issue 884). Until that is built,
an agent session's permission check refuses these deletions, and the approved list is handed to the
operator to run.
5. **Report**: what was cleaned, what was kept and why, what was unknown, what the operator kept, and the
missing tools, each opened as an issue (`issue-tracker.open`) or added to an existing one.
## How it is checked
- The report names, for every deleted item, the evidence it was safe (contained, clean, no pull request).
- Nothing classed Kept or Unknown is deleted without the operator's word for that item.
- A cleanup's missing tools are issues, not notes in a session.
+517
View File
@@ -0,0 +1,517 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Novox Playbooks</title>
<style>
:root{
--bg:#f7f6f2; --card:#ffffff; --ink:#1d1f23; --mute:#5d6470; --line:#d9d6cc; --soft:#efede6;
--idea:#2f5fa7; --sym:#b4462b; --dec:#7a4bb0; --build:#3c8a5a; --mesh:#0f6f7a; --op:#c2410c; --side:#8a6d1f;
--ok:#15803d; --okbg:#e6f4ea; --warn:#a16207; --warnbg:#fdf3d7; --bad:#b91c1c; --badbg:#fde8e8;
}
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
--bg:#15171b; --card:#1e2127; --ink:#e8e6e1; --mute:#9aa1ad; --line:#353a43; --soft:#262a31;
--idea:#7aa7ef; --sym:#f08a6c; --dec:#b993ef; --build:#6fcf97; --mesh:#4fc3cf; --op:#fb923c; --side:#d9b44a;
--ok:#4ade80; --okbg:#173222; --warn:#facc15; --warnbg:#3a3016; --bad:#f87171; --badbg:#3a1d1d;
}
}
:root[data-theme="dark"]{
--bg:#15171b; --card:#1e2127; --ink:#e8e6e1; --mute:#9aa1ad; --line:#353a43; --soft:#262a31;
--idea:#7aa7ef; --sym:#f08a6c; --dec:#b993ef; --build:#6fcf97; --mesh:#4fc3cf; --op:#fb923c; --side:#d9b44a;
--ok:#4ade80; --okbg:#173222; --warn:#facc15; --warnbg:#3a3016; --bad:#f87171; --badbg:#3a1d1d;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif}
main{max-width:1120px;margin:0 auto;padding:28px 16px 72px}
h1{font-size:28px;margin:0 0 4px}
h2{font-size:20px;margin:44px 0 6px}
h3{font-size:16px;margin:0 0 6px}
p{margin:6px 0 10px}
.sub{color:var(--mute);margin:0 0 18px}
.lead{max-width:760px}
nav{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 6px}
nav a,.chip{display:inline-block;padding:4px 11px;border:1px solid var(--line);border-radius:999px;color:var(--ink);text-decoration:none;font-size:13px;background:var(--card);cursor:pointer}
nav a:hover,.chip:hover{border-color:var(--mute)}
.chip.on{background:var(--ink);color:var(--bg);border-color:var(--ink)}
.box{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px;margin:12px 0}
.scroll{overflow-x:auto}
svg{display:block;max-width:100%;height:auto;font-family:inherit}
svg text{fill:var(--ink);font-size:13px}
svg .mute{fill:var(--mute);font-size:11.5px}
svg .t{font-weight:600}
svg .edge{stroke:var(--mute);stroke-width:1.6;fill:none}
svg .dash{stroke-dasharray:5 4}
.legend{display:flex;flex-wrap:wrap;gap:14px;font-size:13px;color:var(--mute);margin-top:8px}
.legend i{display:inline-block;width:12px;height:12px;border-radius:3px;margin-right:6px;vertical-align:-1px}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
.pb{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px 16px;border-top:4px solid var(--c,var(--line))}
.pb .num{font:600 12px/1 ui-monospace,monospace;color:var(--c);letter-spacing:.04em}
.pb h3{margin:4px 0 4px;font-size:17px}
.pb .trig{color:var(--mute);font-size:13.5px;margin:0 0 8px}
.pb details{border-top:1px solid var(--line);padding-top:6px;margin-top:6px}
.pb summary{cursor:pointer;font-size:13.5px;font-weight:600;color:var(--ink);list-style:none}
.pb summary::before{content:"▸ ";color:var(--mute)}
.pb details[open] summary::before{content:"▾ "}
.pb ol,.pb ul{margin:6px 0 4px;padding-left:20px;font-size:13.5px}
.pb li{margin:2px 0}
.kv{display:grid;grid-template-columns:96px 1fr;gap:4px 10px;font-size:13px;margin-top:6px}
.kv b{color:var(--mute);font-weight:500}
.tag{display:inline-block;font-size:11.5px;padding:1px 7px;border-radius:999px;background:var(--soft);color:var(--mute);margin:0 4px 4px 0}
.new{background:var(--okbg);color:var(--ok);font-weight:600}
table{border-collapse:collapse;width:100%;font-size:14px}
th,td{text-align:left;padding:7px 9px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--mute);font-weight:600;font-size:12.5px;text-transform:uppercase;letter-spacing:.03em}
.note{border-left:3px solid var(--ok);background:var(--okbg);padding:9px 12px;border-radius:6px;font-size:14px}
.two{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media (max-width:760px){.two{grid-template-columns:1fr}}
footer{color:var(--mute);font-size:13px;margin-top:48px}
code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;border-radius:4px}
.theme{float:right}
</style>
</head>
<body>
<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>
<nav>
<a href="#cycle">The cycle</a>
<a href="#feature">A feature, step by step</a>
<a href="#tests">When tests run</a>
<a href="#review">The review loop</a>
<a href="#issues">An issue's life</a>
<a href="#where">Where things live</a>
<a href="#all">All playbooks</a>
</nav>
<!-- ============ THE CYCLE ============ -->
<h2 id="cycle">The development cycle</h2>
<p class="lead">Work enters as an <b>idea</b> or a <b>symptom</b>. An idea becomes a decision, a design and then milestones; a symptom becomes an issue. Both end as code delivered to the machines, and the as-is design is updated. <b>No design without a decision; no development without a design or an issue that sends it.</b></p>
<div class="box scroll">
<svg viewBox="0 0 1080 470" role="img" aria-label="The development cycle across playbooks 01 to 09">
<defs>
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker>
</defs>
<!-- entry points -->
<g>
<rect x="16" y="40" width="120" height="50" rx="25" fill="none" stroke="var(--idea)" stroke-width="2"/>
<text x="76" y="70" text-anchor="middle" class="t">Idea</text>
<rect x="16" y="300" width="120" height="50" rx="25" fill="none" stroke="var(--sym)" stroke-width="2"/>
<text x="76" y="330" text-anchor="middle" class="t">Symptom</text>
</g>
<!-- idea path -->
<g>
<rect x="176" y="30" width="150" height="70" rx="10" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
<text x="251" y="58" text-anchor="middle" class="t">01 Research</text>
<text x="251" y="78" text-anchor="middle" class="mute">questions, findings,</text>
<text x="251" y="92" text-anchor="middle" class="mute">direction confirmed</text>
<rect x="366" y="30" width="160" height="70" rx="10" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<text x="446" y="58" text-anchor="middle" class="t">02 Graduation</text>
<text x="446" y="78" text-anchor="middle" class="mute">decisions in the</text>
<text x="446" y="92" text-anchor="middle" class="mute">knowledge base · to-be</text>
<rect x="566" y="30" width="160" height="70" rx="10" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<text x="646" y="58" text-anchor="middle" class="t">04 Breakdown</text>
<text x="646" y="78" text-anchor="middle" class="mute">milestones by release,</text>
<text x="646" y="92" text-anchor="middle" class="mute">issues per repository</text>
<rect x="766" y="30" width="150" height="70" rx="10" fill="var(--card)" stroke="var(--build)" stroke-width="2.5"/>
<text x="841" y="58" text-anchor="middle" class="t">07 Feature</text>
<text x="841" y="78" text-anchor="middle" class="mute">one milestone =</text>
<text x="841" y="92" text-anchor="middle" class="mute">one release</text>
</g>
<!-- symptom path -->
<g>
<rect x="176" y="290" width="150" height="70" rx="10" fill="var(--card)" stroke="var(--sym)" stroke-width="2"/>
<text x="251" y="318" text-anchor="middle" class="t">03 Issues</text>
<text x="251" y="338" text-anchor="middle" class="mute">opened in the tracker,</text>
<text x="251" y="352" text-anchor="middle" class="mute">diagnosed, located</text>
<rect x="566" y="290" width="160" height="70" rx="10" fill="var(--card)" stroke="var(--build)" stroke-width="2.5"/>
<text x="646" y="318" text-anchor="middle" class="t">08 Small fix</text>
<text x="646" y="338" text-anchor="middle" class="mute">one issue, one repository,</text>
<text x="646" y="352" text-anchor="middle" class="mute">about an hour</text>
</g>
<!-- delivered + as-is -->
<g>
<rect x="950" y="160" width="116" height="74" rx="10" fill="var(--card)" stroke="var(--mesh)" stroke-width="2.5"/>
<text x="1008" y="188" text-anchor="middle" class="t">Delivered</text>
<text x="1008" y="206" text-anchor="middle" class="mute">Approve, merge,</text>
<text x="1008" y="220" text-anchor="middle" class="mute">delivery plan</text>
<rect x="766" y="160" width="150" height="74" rx="10" fill="var(--card)" stroke="var(--dec)" stroke-width="2" stroke-dasharray="5 4"/>
<text x="841" y="188" text-anchor="middle" class="t">As-is updated</text>
<text x="841" y="206" text-anchor="middle" class="mute">02 "when it ships":</text>
<text x="841" y="220" text-anchor="middle" class="mute">to-be → implemented</text>
</g>
<!-- side playbooks -->
<g>
<rect x="176" y="400" width="190" height="56" rx="10" fill="var(--soft)" stroke="var(--side)"/>
<text x="271" y="424" text-anchor="middle" class="t">05 Constitution sync</text>
<text x="271" y="442" text-anchor="middle" class="mute">how-we-build → derived page</text>
<rect x="396" y="400" width="190" height="56" rx="10" fill="var(--soft)" stroke="var(--side)"/>
<text x="491" y="424" text-anchor="middle" class="t">06 Writing a module</text>
<text x="491" y="442" text-anchor="middle" class="mute">what runs today → a module</text>
<rect x="876" y="400" width="190" height="56" rx="10" fill="var(--soft)" stroke="var(--side)"/>
<text x="971" y="424" text-anchor="middle" class="t">09 Cleanup</text>
<text x="971" y="442" text-anchor="middle" class="mute">branches, worktrees, on the word</text>
</g>
<!-- edges -->
<path class="edge" d="M136,65 H174" marker-end="url(#ar)"/>
<path class="edge" d="M326,65 H364" marker-end="url(#ar)"/>
<path class="edge" d="M526,65 H564" marker-end="url(#ar)"/>
<path class="edge" d="M726,65 H764" marker-end="url(#ar)"/>
<path class="edge" d="M916,65 C990,65 1008,100 1008,158" marker-end="url(#ar)"/>
<path class="edge" d="M136,325 H174" marker-end="url(#ar)"/>
<path class="edge" d="M326,325 H564" marker-end="url(#ar)"/>
<text x="445" y="317" text-anchor="middle" class="mute">a fix of about an hour</text>
<path class="edge" d="M726,325 C990,325 1008,290 1008,236" marker-end="url(#ar)"/>
<path class="edge" d="M948,197 H918" marker-end="url(#ar)"/>
<path class="edge dash" d="M251,290 C251,200 420,170 440,102" marker-end="url(#ar)"/>
<text x="300" y="196" class="mute">a design gap: decision first</text>
<path class="edge dash" d="M326,300 C560,250 720,170 800,102" marker-end="url(#ar)"/>
<text x="612" y="236" class="mute">larger than an hour: a feature</text>
<path class="edge dash" d="M1008,236 C1008,330 990,360 971,398" marker-end="url(#ar)"/>
</svg>
<div class="legend">
<span><i style="background:var(--idea)"></i>from an idea</span>
<span><i style="background:var(--sym)"></i>from a symptom</span>
<span><i style="background:var(--dec)"></i>decisions and designs</span>
<span><i style="background:var(--build)"></i>code changes</span>
<span><i style="background:var(--mesh)"></i>the mesh delivers</span>
<span><i style="background:var(--side)"></i>alongside</span>
</div>
</div>
<!-- ============ 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>
<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>
<marker id="ar2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker>
</defs>
<!-- step columns -->
<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="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="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">its delivery plan</text>
</g>
<!-- lanes -->
<g>
<rect x="0" y="46" width="1080" height="72" fill="var(--soft)" opacity=".55"/>
<rect x="0" y="190" width="1080" height="72" fill="var(--soft)" opacity=".55"/>
<rect x="0" y="334" width="1080" height="72" fill="var(--soft)" opacity=".55"/>
<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, plan</text>
<text x="10" y="374" class="t">Operator</text><text x="10" y="390" class="mute">Telegram</text>
</g>
<!-- lead -->
<rect x="110" y="56" width="160" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<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>
<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>
<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>
<!-- fixing -->
<rect x="300" y="128" width="220" height="52" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
<text x="410" y="150" text-anchor="middle" class="t">Build each issue, commit</text>
<text x="410" y="168" text-anchor="middle" class="mute">unit suite before every commit</text>
<rect x="560" y="128" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
<text x="630" y="150" text-anchor="middle" class="t">Fix findings</text>
<text x="630" y="168" text-anchor="middle" class="mute">the same agents</text>
<!-- 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>
<!-- 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>
<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>
<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 · delivery plan</text>
<text x="1000" y="312" text-anchor="middle" class="mute">backups first, tier by tier</text>
<!-- operator -->
<rect x="860" y="344" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--op)" stroke-width="2.5"/>
<text x="930" y="366" text-anchor="middle" class="t">Approve</text>
<text x="930" y="384" text-anchor="middle" class="mute">once, for the group</text>
<!-- 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 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)"/>
<text x="712" y="140" class="mute">approved</text>
<path class="edge" d="M830,108 V270" marker-end="url(#ar2)"/>
<path class="edge" d="M900,298 C915,298 920,330 925,342" marker-end="url(#ar2)"/>
<path class="edge" d="M1000,344 V326" marker-end="url(#ar2)"/>
<path class="edge" d="M1000,272 V110" marker-end="url(#ar2)"/>
</svg>
</div>
<div class="two">
<div class="box">
<h3>Step 0 · what the lead writes before any agent starts</h3>
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
<li>Every open question settled: on the issue, in the knowledge base, or by the operator.</li>
<li>Into each issue: every file and test it touches (file:line), how it is checked and delivered, a group's merge order.</li>
<li><span class="tag new">new</span>Every other reader and writer of each value the change touches, each with a criterion.</li>
<li>Acceptance criteria: given / when / then, each naming its test level.</li>
<li>Release orders hidden in the milestone, said to the operator, never built around.</li>
<li>Fixtures copied from what the other side really writes, never from memory.</li>
<li>An issue sized over an hour is split before work starts.</li>
</ul>
</div>
<div class="box">
<h3>Step 1 · the fixing agent's brief</h3>
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
<li>One commit per issue, naming it; tests cite their criterion (<code>// 1217/AC3</code>).</li>
<li><span class="tag new">new</span>A test fails first on what the code does. A compile error is no failure: take out the change's effect and see an assertion fail.</li>
<li>The whole unit suite before every commit; each test well under a second.</li>
<li>Integration tests are compiled only: never run on the machine.</li>
<li>Never push, open or merge; never move an issue; never ssh.</li>
<li>Code that differs from the context file: stop and report, file:line.</li>
</ul>
</div>
</div>
<!-- ============ 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, and the forge's merge check runs them again as the gate.</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>
<path class="edge" d="M120,140 H1060" marker-end="url(#ar3)"/>
<text x="120" y="162" class="mute">develop</text><text x="470" y="162" class="mute">end of step 1</text><text x="640" y="162" class="mute">review</text><text x="800" y="162" class="mute">step 3</text><text x="960" y="162" class="mute">merge</text>
<text x="10" y="44" class="t">Unit</text><text x="10" y="60" class="mute">on the machine</text>
<g fill="var(--build)">
<rect x="120" y="36" width="700" height="16" rx="8" opacity=".25"/>
<circle cx="160" cy="44" r="6"/><circle cx="220" cy="44" r="6"/><circle cx="290" cy="44" r="6"/><circle cx="350" cy="44" r="6"/><circle cx="420" cy="44" r="6"/><circle cx="600" cy="44" r="6"/><circle cx="680" cy="44" r="6"/>
</g>
<text x="830" y="42" class="mute">whole suite before every commit</text><text x="830" y="56" class="mute">each test under a second</text>
<text x="10" y="104" class="t">Integration</text><text x="10" y="120" class="mute">never on the machine</text>
<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="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>
</div>
<!-- ============ REVIEW LOOP ============ -->
<h2 id="review">The review loop</h2>
<div class="two">
<div class="box scroll">
<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>
<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"/>
<text x="455" y="44" text-anchor="middle" class="t">Approve</text><text x="455" y="62" text-anchor="middle" class="mute">Reviewed-by line</text>
<rect x="210" y="130" width="180" height="54" rx="8" fill="var(--card)" stroke="var(--build)" stroke-width="2"/>
<text x="300" y="154" text-anchor="middle" class="t">Same fixing agents fix</text><text x="300" y="172" text-anchor="middle" class="mute">answer per finding</text>
<rect x="20" y="130" width="160" height="54" rx="8" fill="var(--soft)" stroke="var(--side)"/>
<text x="100" y="154" text-anchor="middle" class="t">issue-tracker.open</text><text x="100" y="172" text-anchor="middle" class="mute">anything beyond the issue</text>
<rect x="300" y="236" width="210" height="54" rx="8" fill="var(--warnbg)" stroke="var(--warn)" stroke-width="2"/>
<text x="405" y="260" text-anchor="middle" class="t">After round 3: the operator</text><text x="405" y="278" text-anchor="middle" class="mute">what is left, with a recommendation</text>
<path class="edge" d="M170,47 H218" marker-end="url(#ar4)"/>
<path class="edge" d="M380,47 H398" marker-end="url(#ar4)"/><text x="384" y="38" class="mute">no</text>
<path class="edge" d="M300,74 V128" marker-end="url(#ar4)"/><text x="306" y="104" class="mute">yes</text>
<path class="edge" d="M210,157 C140,157 95,120 95,76" marker-end="url(#ar4)"/><text x="120" y="112" class="mute">n+1</text>
<path class="edge dash" d="M250,66 C200,100 170,120 150,128" marker-end="url(#ar4)"/><text x="178" y="96" class="mute">hardening,</text><text x="178" y="108" class="mute">a nicer form</text>
<path class="edge dash" d="M360,184 C380,210 400,220 405,234" marker-end="url(#ar4)"/>
</svg>
</div>
<div class="box">
<h3>What a round may ask</h3>
<p style="font-size:14px">Only what makes the change <b>wrong</b> against its issues, their acceptance criteria and its decisions. Anything more (hardening, a further case, a nicer form) becomes a new issue, and the round does not wait for it.</p>
<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>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>
</div>
<!-- ============ ISSUE LIFE ============ -->
<h2 id="issues">An issue's life (playbook 03)</h2>
<p class="lead">The issue tracker (seat <code>issue-tracker</code>) holds every issue's number, status, owner, fix and links. Its <code>move</code> refuses a wrong move at the write.</p>
<div class="box scroll">
<svg viewBox="0 0 1080 230" role="img" aria-label="Issue statuses and the moves between them">
<defs><marker id="ar5" 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>
<g>
<rect x="20" y="70" width="120" height="50" rx="25" fill="var(--card)" stroke="var(--sym)" stroke-width="2"/><text x="80" y="100" text-anchor="middle" class="t">open</text>
<rect x="220" y="70" width="140" height="50" rx="25" fill="var(--card)" stroke="var(--warn)" stroke-width="2"/><text x="290" y="100" text-anchor="middle" class="t">diagnosing</text>
<rect x="440" y="70" width="120" height="50" rx="25" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/><text x="500" y="100" text-anchor="middle" class="t">located</text>
<rect x="640" y="70" width="120" height="50" rx="25" fill="var(--card)" stroke="var(--build)" stroke-width="2"/><text x="700" y="100" text-anchor="middle" class="t">fixing</text>
<rect x="860" y="70" width="140" height="50" rx="25" fill="var(--okbg)" stroke="var(--ok)" stroke-width="2.5"/><text x="930" y="100" text-anchor="middle" class="t">resolved</text>
<rect x="440" y="170" width="120" height="44" rx="22" fill="var(--soft)" stroke="var(--mute)"/><text x="500" y="197" text-anchor="middle" class="t">wontfix</text>
</g>
<path class="edge" d="M140,95 H218" marker-end="url(#ar5)"/><text x="146" y="86" class="mute">by</text>
<path class="edge" d="M360,95 H438" marker-end="url(#ar5)"/><text x="366" y="86" class="mute">located-in</text>
<path class="edge" d="M560,95 H638" marker-end="url(#ar5)"/><text x="590" y="86" class="mute">by</text>
<path class="edge" d="M760,95 H858" marker-end="url(#ar5)"/><text x="770" y="138" class="mute">fixed-by, delivered,</text><text x="770" y="152" class="mute">proven · core: replay</text>
<path class="edge dash" d="M140,105 C300,150 520,150 640,108" marker-end="url(#ar5)"/><text x="300" y="150" class="mute">straight to fixing, with by</text>
<path class="edge dash" d="M500,120 V168" marker-end="url(#ar5)"/><text x="508" y="150" class="mute">reason</text>
<path class="edge dash" d="M700,70 C700,30 600,30 590,30 C400,30 120,30 90,68" marker-end="url(#ar5)"/><text x="320" y="24" class="mute">stopping: back to open, with a note of branch, last commit and what is left</text>
</svg>
<div class="legend"><span>A worker idle for a day may be taken over without a reason; otherwise <code>take-over</code> with a reason. An issue about a condition is linked to it, so the alert names the issue.</span></div>
</div>
<!-- ============ WHERE THINGS LIVE ============ -->
<h2 id="where">Where things live</h2>
<div class="box scroll">
<table>
<tr><th>What</th><th>Lives in</th><th>Written with</th><th>Never</th></tr>
<tr><td>Decisions (ADRs) and research efforts</td><td>the knowledge base (seat <code>knowledge-base</code>)</td><td><code>reserve</code>, then <code>propose</code> the record whole</td><td>a file in hq</td></tr>
<tr><td>Issues: status, owner, fix, links</td><td>the issue tracker</td><td><code>open</code>, <code>move</code>, <code>note</code>, <code>link</code></td><td>a status key in a report</td></tr>
<tr><td>An issue's narrative (design or governance only)</td><td>hq <code>04-ISSUES/NNN-…/</code></td><td>a pull request</td><td>its status</td></tr>
<tr><td>Designs, as-is and to-be</td><td>hq <code>03-DESIGN/</code>, status in frontmatter</td><td>a pull request</td><td>the two layers mixed</td></tr>
<tr><td>Playbooks, glossary, how-we-build</td><td>hq <code>00-META/</code></td><td>a pull request</td><td>a copy in a session's memory</td></tr>
<tr><td>The rules every session keeps</td><td>instruction sections (module <code>claude-code</code>)</td><td><code>claude_code_instructions_propose</code>, the operator's Approve</td><td>a hand-edited file</td></tr>
</table>
</div>
<!-- ============ ALL PLAYBOOKS ============ -->
<h2 id="all">All playbooks</h2>
<p class="sub">Open a card for its steps, what it produces and how it is checked.</p>
<div id="filters" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px"></div>
<div class="grid" id="cards"></div>
<footer>Drawn from hq main f269e83 on 2026-10-19 and kept in hq beside the playbooks. The playbooks are the authority; this page is a picture of them, and a change to a playbook's steps updates it in the same pull request (not yet checked: issue 1222).</footer>
</main>
<script>
const P = [
{n:"01",name:"Research",kind:"idea",c:"--idea",
trig:"An idea worth investigating before it is committed to design. A symptom is not research.",
who:"The session that heard the idea leads.",
steps:["Read what is known: ask, get, the tracker's list.","Ask 3 to 5 setup questions, one at a time, each with a recommendation.","Check every option against the mission and how-we-build first.","Reserve a number (kind research), never pick one by hand.","Propose the effort: overview, questions, method, with links.","Research; findings in 04-findings, one version per settled set.","Try at the cheapest level that answers the question.","Write 03-domains: modules, seats, glossary words added or retired.","Write 05-direction: decisions, rejected options, acceptance tests.","Ask the operator to confirm the direction, one point at a time.","If the review asks changes: propose again, at most three times."],
prod:"A research effort in the knowledge base, status active, every version kept. Nothing in hq.",
next:"02",bounds:"Setup 30 min · proposing 15 min · each experiment 30 min · direction 30 min · report every 10 min",
prev:["Research that measured nothing","A recommendation the operator must argue back","Efforts copied into hq"],
check:"propose refuses rule breaks; the knowledge base reviews; records.py on every PR."},
{n:"02",name:"Graduation & design change",kind:"idea",c:"--dec",
trig:"A research effort concludes, or an existing design must change.",
who:"The lead, on the larger model; the decision is recorded before the design moves.",
steps:["Check the conclusion against mission, context and effect.","Reserve a number per decision.","Propose one record per choice that can be reversed on its own.","Pre-check, then propose siblings together.","On changes: revise whole; after three rounds fix findings yourself.","A finding that would change what was decided: ask the operator.","needs-operator: wait for the operator's Approve.","Amend the design, or create a to-be design with status designed.","Add the decisions, updated:, and the effort's glossary words.","Hand the design to the breakdown (04).","Close the effort: graduated with became links, or abandoned.","When it ships: write the as-is, set the to-be implemented."],
prod:"Decision records; a to-be design with frontmatter; glossary entries; later the as-is.",
next:"04",bounds:"One sitting of about an hour",
prev:["A decision rewritten silently","A deferral nothing tracks","A design amended without a decision"],
check:"propose refuses; records.py and index.py; set-wide rules raise conditions."},
{n:"03",name:"Issues",kind:"symptom",c:"--sym",
trig:"Something is wrong: an unenforced rule, a stated behaviour that does not happen, a silent failure.",
who:"Anyone opens; a worker claims it by moving it.",
steps:["Open it in the tracker: title, report; take the number it gives.","Tracker unreachable: take no number, say so.","A design or governance issue: a folder in hq with the report.","About a condition: link the condition's key.","Move to diagnosing with by, before investigating.","Note the trail; a folder issue also gets a dated diagnosis.","Move to located with located-in, a registered owner.","Move to fixing with by, before fixing.","Name fixed-by at the merge; stay fixing until proven live.","Move to resolved; a core issue gives replay or replay-none.","A design gap: run 02 and give amended-design.","Not going to be fixed: wontfix with a reason."],
prod:"A tracker record with notes and links; for a design issue, its narrative folder in hq.",
next:"02 · 07 · 08",bounds:"A worker idle for a day is stale",
prev:["Numbers that collide","Two agents starting the same work","Status held in two places"],
check:"The tracker's move refuses at the write; cycle.py checks the folders."},
{n:"04",name:"Work breakdown & handoff",kind:"idea",c:"--dec",
trig:"A to-be design is settled and its building is about to start.",
who:"The lead who will run the feature, on the larger model.",
steps:["Confirm status designed; each decision traces to a record.","An open question remains: back to research.","Set code: to the owning repositories.","Read the as-is; write it first if it does not say what is replaced.","Write the Milestones: outcome, acceptance tests, repositories.","Cut milestones by release order; fold any not usable alone.","Ask the operator to confirm the milestones.","Open one issue per repository part, linked depends-on.","Write the issue numbers into the design; flip to in-progress.","Hand each milestone to 07, one at a time, one design at a time."],
prod:"Milestones in the design; issues per repository part; the design in-progress.",
next:"07",bounds:"One sitting · one design built at a time · a milestone is one release",
prev:["Building without a breakdown","Building against an undocumented as-is","Deferrals with no owning issue"],
check:"Read by the lead and the reviewer; a check is tracked by issue 845."},
{n:"05",name:"Constitution sync",kind:"side",c:"--side",
trig:"how-we-build.md changed a rule the mesh enforces at runtime.",
who:"Whoever made the change.",
steps:["Edit how-we-build; the reasoning stays there.","Record the change as a decision (02).","Publish the derived page under its slug.","Keep the section numbering stable.","Read the page back; verify the change is there.","Note the sync in the record's consequences."],
prod:"A decision record; the derived constitution page injected into design sessions.",
next:"02",bounds:"no time bound",
prev:["Drift between the reasoned and the enforced copy","A publish that said success and did nothing","Edits to the derived page that vanish"],
check:"The read-back in step 5."},
{n:"06",name:"Writing a module",kind:"side",c:"--side",
trig:"Something that runs today must run on the mesh.",
who:"Whoever ports or writes it.",
steps:["Read what runs: compose file, environment, where data sits.","Name what it provides and requires.","Declare capabilities for machine facts.","Write resources in the order they must happen.","Every secret in a file, never in env.","Pin every image by digest.","New: generate the credential; adopted: accept it.","Prove it against the real software: auth, refusal, restart."],
prod:"A module declaration, optionally a provisioner, and its proof.",
next:"07",bounds:"no time bound",
prev:["A service running with no password","A new password locking out adopted data","A test that cannot fail"],
check:"Example tests refuse the env-file mistake; review."},
{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)","Find release orders; 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.","Open one pull request per repository, together, with Reviewed-by.","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery plan to done; resolve the issues; clean up."],
prod:"One pull request per repository, one delivery plan, 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"],
check:"The forge's merge-gate and repo-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 plan.","Prove the fix live and resolve the same hour."],
prod:"One pull request, one delivery, a resolved issue.",
next:"—",bounds:"About an hour · one try before the review, one review, one check",
prev:["A fix reviewed after its pull request opened","A long part run twice","A fix waiting on someone else's delivery"],
check:"The forge's checks; the review record; the tracker's move."},
{n:"09",name:"Cleanup",kind:"side",c:"--side",
trig:"The operator asks, or merged branches, stale pull requests and old worktrees pile up.",
who:"A session on the operator's word; the operator decides every deletion.",
steps:["Take stock, read-only.","Write one report: item, class, evidence.","Classify: safe, likely safe, kept, unknown.","Report stale pull requests; never close them.","Ask: delete the safe items as listed?","Delete only the approved list; never force.","Report what was cleaned, kept and unknown."],
prod:"A stock report, the approved deletions, issues for missing tools.",
next:"03",bounds:"A worktree is safe only if untouched for 2 h · a PR is stale after 3 days",
prev:["Deleting unfinished work","Deletion by name or age alone","Missing tools left as session notes"],
check:"The report names the evidence for each deletion."}
];
const kinds={all:"All",idea:"From an idea",symptom:"From a symptom",build:"Code changes",side:"Alongside"};
let current="all";
function esc(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;")}
function render(){
const g=document.getElementById("cards"); g.innerHTML="";
P.filter(p=>current==="all"||p.kind===current).forEach(p=>{
const d=document.createElement("article"); d.className="pb"; d.style.setProperty("--c",`var(${p.c})`);
d.innerHTML=`<div class="num">PLAYBOOK ${p.n}</div><h3>${esc(p.name)}</h3><p class="trig">${esc(p.trig)}</p>
<div>${p.bounds.split(" · ").map(b=>`<span class="tag">${esc(b)}</span>`).join("")}</div>
<details><summary>Steps (${p.steps.length})</summary><ol>${p.steps.map(s=>`<li>${esc(s).replace("(new)",'<span class="tag new">new</span>')}</li>`).join("")}</ol></details>
<details><summary>Produces, prevents, checked by</summary><div class="kv">
<b>Who</b><span>${esc(p.who)}</span><b>Produces</b><span>${esc(p.prod)}</span>
<b>Then</b><span>${esc(p.next)}</span><b>Prevents</b><span>${p.prev.map(esc).join("; ")}</span>
<b>Checked by</b><span>${esc(p.check)}</span></div></details>`;
g.appendChild(d);
});
}
const f=document.getElementById("filters");
Object.entries(kinds).forEach(([k,v])=>{const b=document.createElement("button");b.type="button";b.className="chip"+(k===current?" on":"");b.textContent=v;
b.onclick=()=>{current=k;[...f.children].forEach(x=>x.classList.remove("on"));b.classList.add("on");render()};f.appendChild(b)});
render();
const root=document.documentElement;
try{const t=localStorage.getItem("pb-theme");if(t)root.dataset.theme=t}catch(e){}
document.getElementById("theme").onclick=()=>{const dark=root.dataset.theme?root.dataset.theme==="dark":matchMedia("(prefers-color-scheme: dark)").matches;
root.dataset.theme=dark?"light":"dark";try{localStorage.setItem("pb-theme",root.dataset.theme)}catch(e){}};
</script>
</body>
</html>
+1
View File
@@ -36,6 +36,7 @@ one-for-one — `mesh-catalog`, `mesh-sdk` and `mesh-tools` exist where the tabl
| `mesh-surfaces` | 3 | tools, web, cli | | `mesh-surfaces` | 3 | tools, web, cli |
| `mesh-sdk` | — | the stable spine modules build against — the tool-serving SDK harness, the messaging/event framework, the contracts and core primitives. Holds nothing per-module and nothing volatile (ADR 0039). | | `mesh-sdk` | — | the stable spine modules build against — the tool-serving SDK harness, the messaging/event framework, the contracts and core primitives. Holds nothing per-module and nothing volatile (ADR 0039). |
| `mesh-lab` | — | **exists.** The lab — scenario lifecycle, networking, placement. Ships to nobody; runs on a workstation. | | `mesh-lab` | — | **exists.** The lab — scenario lifecycle, networking, placement. Ships to nobody; runs on a workstation. |
| `mesh-delivery` | — | the delivery domain: the parts that build, check and deliver a change by watching and writing keys on the bus, nine modules each whole in it, prefixed `mesh-delivery-` (ADR 0429). Made by the forge module from its config (ADR 0428). |
Tier 4's shape is open, and deliberately so: see ADR 0019 and Tier 4's shape is open, and deliberately so: see ADR 0019 and
research 005. research 005.
+71 -81
View File
@@ -1,100 +1,90 @@
--- ---
layer: as-is layer: as-is
status: implemented status: implemented
code: [hal] code: [mesh-controller, mesh-catalog modules/mesh-delivery, mesh-catalog modules/gitea, mesh-catalog modules/build-agent]
updated: 2026-08-23 updated: 2026-10-17
decisions: decisions:
- "ADR 0010" - "ADR 0239"
- "ADR 0010" - "ADR 0267"
- "ADR 0010" - "ADR 0276"
- "ADR 0282"
- "ADR 0289"
- "ADR 0304"
- "ADR 0350"
- "ADR 0358"
- "ADR 0360"
- "ADR 0364"
- "ADR 0382"
- "ADR 0395"
- "ADR 0398"
--- ---
# Delivery — from a push to a running node # Delivery — from a merge to the nodes, as it runs
One trigger, three silos, and a fan-out point that is the most consequential boundary in the What runs on the mesh's main branches on 2026-10-16, read from the code and the live record by research 055
mesh. (findings 1 to 12). It is what [to-be 47](../01-to-be/47-delivery-from-commit-to-delivered.md) replaces, milestone
by milestone. Until 2026-10-16 this document described the predecessor's pipeline, which no longer runs.
## The trigger ## Who does what
A push to the forge. The forge calls a webhook; the receiving module verifies its signature - **mesh-delivery** owns deliveries and delivery groups (ADR 0239): one delivery per commit, a group per branch
and emits an event. The coordinator resolves which modules the pushed commits affect, orders name shared across repositories. It records each transition in its state on the bus (key-value buckets
them into dependency levels, and creates a pipeline per level. `deliveries` and `groups`), marks pull requests, asks the controller to deliver, and merges a pull request marked
ready at its head once both required checks pass against the trunk's head (ADR 0364) and the operator approved
the merge into main on a channel that proves who answered (ADR 0398 §3).
- **The controller** plans, orders, sends and judges. It computes a delivery plan for a repository and the files a
merge changes, moves only modules whose build source changed with their build dependents (ADR 0267), and
reuses a merge check's build of the same reuse key (ADR 0350).
- **The builders** (holders of the build seat) take build requests and checks from a work-queue stream on the bus
and build inside a toolchain image another module provides, with the network off; on 2026-10-13 no node had Go
or npm installed (issue 626).
- **The forge module** publishes every forge event on the bus through the forge's system webhook since 2026-10-14
(issue 729), and still polls the forge every 30 s, or every 300 s once it hears events. It keeps files of what it
announced so it does not announce twice.
There is no second path. A manual trigger exists for a module the push detection missed, and ## From a merge to a rollout
using it routinely is a sign the detection is wrong rather than a workflow.
Detection is the pipeline's most fragile input. It has failed for reasons that have nothing to - **The merge window.** The controller collects merges into an open batch; each merge restarts the window, which
do with the change: a webhook truncating its commit list on a large merge, a forge address closes after 90 seconds without a merge or 10 minutes after the batch's first merge (ADR 0276). A lone merge is
whose port broke the module-path matching. The characteristic outcome is the bad one — **a cut at once (ADR 0282 §2, as ADR 0304 §1 changed it). The batch and its merges are rows in the controller's
merge that created no pipeline, and nothing said so**. store, cut by a timer every 5 s, with a catch-up timer every 5 minutes for merges no batch holds.
- **The rollout.** A closed batch becomes one rollout, at most one commit per repository, tiered along the module
graph. Rollouts run one at a time: a batch cut while a rollout is open is queued behind it, whatever it shares
with it. Running unrelated rollouts side by side (ADR 0282 §3, ADR 0304 §2) is decided and not built (issue 525). A rollout's builds go to every machine at once, with no first-machine gate, and the rollout ends on
the machines' reports or after fifteen minutes of silence (ADR 0360). A controller timer advances rollouts every
30 s.
- **A merge during a rollout.** Its place in the next batch is a store row the open rollout's holder writes. On
2026-10-16, when another controller worked the plans, a merge was not recorded and waited until a person released
it (issue 921).
- **"Release branches".** A pull request into a branch other than a trunk is judged by the controller built from that
branch (ADR 0358); feature 57 shipped about 37 packages that way over about 2.5 days and held the controller for
55.7 h.
## Three silos ## Checks
Cardinality is the whole point, and the three differ - Every pull request's head gets `mesh/merge-gate` (the composed machines) and `mesh/repo-check` (the repository's
(ADR 0010): `merge-check.sh`), each judged against one trunk commit (ADR 0323). A trunk move asks the check of every active,
cleanly merging pull request again (ADR 0395).
- A delivery group's members are each checked beside their siblings' heads, and the group also gets a composed
check, `mesh/delivery-group`; each member keeps its own gate beside it (ADR 0382).
- The queue's order (delivery builds first, then ready checks, then the rest) is kept by pausing the worker and
deleting and republishing a request (issue 558); an old head's check is ended after asking the forge for the
current head (issue 726).
- mesh-delivery ticks every 10 s, reconciles with the controller every 30 s and polls the forge for ready marks
every minute, although the forge module already publishes labeled, commented and synchronized events.
| Silo | Runs | Where | Does | ## Where the record of what runs lives
|---|---|---|---|
| **build** | once per module feature | the build node | compile and bundle into a self-contained output |
| **publish** | once per module feature | the build node | package that output and upload it; a package-registry feature publishes here |
| **deploy** | once per module feature **per node** | every assigned node | install, configure, start, verify |
Commands and events are addressed per feature, not per module. Builds and build requests, rollouts with their tiers, batches and merges, and assignments are rows in the
controller's store; artifacts are blobs in the artifact store by digest (ADR 0251); packages are versions in the
package registry. The controller writes three record mirrors to the bus: assignments, declared config and
predefined keys; of the five ADR 0289 §7 decided, the nodes, rollouts, builds and settings mirrors are not in the
code. An assignment names its module and node but not the build, artifact or rollout that put it there: which commit
runs where, and which delivery brought it, takes three verbs and a join by hand.
Build hands over a **staged tree**, not a package. Packaging belongs to publish, so a failed No backup is taken before a delivery moves a module that holds data; backups run nightly and on demand (ADR 0214).
upload retries by re-packaging rather than by re-sending something stale, and build never needs
to know how each module composes its artifact. The handover is a tree in a known location,
because the build's own working directory is reference-counted and may be gone by the time a
later stage runs.
## The artifact
The artifact is **build output** — compiled and bundled with its dependency graph inlined —
never a filtered copy of source (ADR 0010).
A deploy is extract-and-run and touches no network.
The consequence is the whole cost of the decision: **anything not in the build output does not
ship.** Every file kind had to be brought into that rule separately, and each was discovered by
something silently not happening after deploy — migrations reading a source layout, provisioning
scripts reading a source layout, selection files never packaged at all.
## The fan-out, and the build node
After publish, work fans out to every assigned node. The build node is the only node that has
already passed through two silos when this happens, and that asymmetry has its own defect
class: anything advancing a node's stage must account for **both** pre-fan-out stages. Code
that knew only about the first parked the build node forever while every other node deployed
cleanly — and the recovery sweeper, which knew the same subset, reported nothing to recover.
A recovery mechanism that knows less than the thing it guards is worse than none, because it
reports success over a stall it cannot see.
## Levels
A level completes before the next begins, so a module builds against its dependencies as they
were just published. The shared library is at level zero, which means anything that breaks it
breaks the first module of every cascade.
## What green proves
**A green pipeline proves transport, not effect.** The stages report that a message was
dispatched and accepted, which is not the same as the thing being running, correct, or present.
This is the mesh's most consistent failure shape and it is not incidental to the design — it is
what the stage reporting currently measures. Documented instances include a service reported
started when the container command merely returned, an image pull failure that did not fail the
deploy, a package install that 404ed from every mirror while the job went green (see
[`04-ISSUES/001`](../../04-ISSUES/001-failed-package-install-reports-success/00-report.md)),
and a node left on old code after a failed artifact download with a version marker that had
already advanced.
A verify stage exists to close this gap. It has been built and, for a period, was never
scheduled, because the coordinator's stage list did not include it.
## What is not covered ## What is not covered
The end-to-end harness for this pipeline has not built since the workspace was removed, and The integration tests run only in each repository's merge check; there are no end-to-end tests of delivery yet,
nothing reports that nothing runs it and the lab is not used (the operator, 2026-10-16).
([`04-ISSUES/005`](../../04-ISSUES/005-pipeline-test-harness-unbuildable/00-report.md)). The
pipeline's coverage is currently assumed rather than checked — which is the same class of claim
this repository exists to make people stop making.
+122 -47
View File
@@ -1,69 +1,144 @@
--- ---
layer: as-is layer: as-is
status: implemented status: implemented
code: [mesh-catalog modules/mesh-console, mesh-tools src/mesh.ts, mesh-tools src/http.ts, mesh-tools src/runtime.ts, mesh-controller internal/broker, mesh-controller cmd/mesh-controller/check.go, mesh-controller cmd/mesh-controller/seatverbs.go] code: [mesh-tools, mesh-host, mesh-controller]
updated: 2026-10-10 updated: 2026-10-17
decisions: decisions:
- "ADR 0152" - "ADR 0152"
- "ADR 0095" - "ADR 0175"
- "ADR 0037" - "ADR 0195"
- "ADR 0197"
- "ADR 0367"
- "ADR 0154" - "ADR 0154"
--- ---
# The mesh MCP server, as it runs # The mesh MCP server, as it runs
**The mesh's tools reach a person through a module the mesh assigned to their machine.** Since **The `mesh-console` container no longer exists.** Read 2026-10-17 from `mesh-tools` (package
2026-09-30 a workstation that is a node can be assigned `mesh-console`; the mesh mints a bus account `node-tools`), `mesh-host` and `mesh-controller` on their forges' main, and from the live mesh
`<node>.mesh-console`, seals its credential to the machine, and the container binds through `mesh_overview`, `mesh_machine`, `mesh_runtimes` and `mesh_describe`. Checked: the
`127.0.0.1:<port>` with the port the mesh assigned for the manifest's declared one. An agent on the catalogue (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
machine is pointed at `http://127.0.0.1:<port>/mcp` and sees the mesh's tools; a person uses the same in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a
endpoint. Nothing on the machine holds a credential a person had to carry. module of that name.
## 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).
**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;
`waits` names the five event families `mesh_wait` may subscribe to (the knowledge base's reviews,
mesh-delivery's transitions, the forge's status, the controller's checked event, and the operator
channel's decision for the agent module) — the bus refuses a subscription outside this list
(ADR 0367 §2); and it
`provides` one provision, the MCP endpoint, at node scope, and `listens` on the port it serves from,
opened only to the machine itself. Its code lives under `node-tools/` in the `mesh-tools`
repository (`internal/console`, `internal/announce`, `internal/bus`).
**One runtime per node, run by the node-engine as a sibling process, never a container**
(ADR 0175). It holds the node's one bus credential and may call every tool on the mesh; its account
publishes nothing else. It binds `127.0.0.1:<port>` and refuses to bind anything else, said before
binding rather than after (`node-tools/internal/console/http.go`, citing ADR 0152): whoever is on
the machine owns the mesh there, and nobody else may reach this port. An agent or a person on the
node is pointed at `http://127.0.0.1:<port>/mcp` once; the `serverInfo.name` the handshake answers
is `mesh`.
## What it answers ## What it answers
`initialize`, `tools/list`, `tools/call`, over HTTP, one JSON body per request, no session and no event `initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`, over HTTP, one JSON
stream. `tools/list` is what the running modules answered: every tool runner built on or after that day body per request, no session and no event stream (`node-tools/internal/console/mcp.go`).
serves a `tools` verb for its module, and the mesh MCP server asks the catalogue for the roster and each module
for its tools. A module that did not answer is named in the list's `_meta.notAnswering`. On the day it
shipped that was 36 of 51 modules — those that serve no tools at all, and those whose rebuilt runtime the
mesh records rather than rolls out — and 62 tools from the rest.
`tools/call` reaches any tool by `<module>.<tool>`, listed or not. The mesh MCP server's grant is `*`, so what it **It announces seven tools, not five** (ADR 0195, extended by ADR 0367): `mesh_overview`,
may call is every tool on the mesh; its account may publish nothing else and subscribes nothing. `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
two module instances (`route-proxy` on two of the nodes) named as not answering rather than silently
absent.
No call is refused for want of a person's approval: a tool runner serves every call it is issued, whoever made **Addresses, exactly as the handshake states them:** `<seat>.<verb>` for a seat held once for the
it, and cannot tell an agent from the operator (ADR 0259 §6, issue 340). An act waits for the operator's warrant mesh (`mesh-controller.status`); `<node>/<seat>.<verb>` for a seat every machine holds
only where its module asks first, in its own code: the agent module's instruction proposals, and the (`<node>/node-packet-filter.rules`); `<node>/<module>.<tool>` for a module assigned to one machine;
controller's settings proposals and the actions it offers on a condition. and `<module>.<tool>` also for a module whose instances are interchangeable. A tool not listed can
still be called by address — listing is discovery, calling is the grant — and a module that is not
interchangeable is refused with the machines it runs on rather than guessed at.
**What exists is what announced itself** (ADR 0197). Every node's tool runner answers the NATS services
protocol's `$SRV.PING`, `$SRV.INFO` and `$SRV.STATS`, one service per process, naming in each
endpoint's metadata which module, seat, scope and machine serves it. `mesh_overview` and
`mesh_machine` gather one request's answers: at least 750ms so a runtime that only just answered
`$SRV.PING` is still waited for, up to 5s for one that said it is there but has not yet sent its
full announcement — a fixed shorter window lost the largest runtime's listing once a live
measurement found how slow its round trip over the bus could be. An announcement unchanged since
last asked (by its digest) is not re-fetched, which is why `mesh_runtimes` can show a runtime
answering in under 100ms for hundreds of kilobytes of discovery.
## mesh_wait
**The seventh tool, added by ADR 0367**, lets a session wait on the bus for an event instead of
polling: a call names one or more granted event families and an optional filter, and either answers
the first match or says it timed out. The tool runner's own bus account hears only the families its
manifest's `waits` list names; an event it hears is never taken as the state — a session reads the
state from the verb that owns it once woken. It is meant to be called in a loop from a background
command, not inline, because one call blocks until it answers or times out.
## The mesh's own verbs ## The mesh's own verbs
*Since 2026-09-30 evening (ADR 0154).* **These already answer directly, not as a stated future.** Beside every assigned module's tools,
The mesh MCP server asks the `mesh-controller` seat's `tools` verb beside the modules and lists every role's the tool runner asks the `mesh-controller` seat for its own verbs the same way (ADR 0154): measured
tools as `<seat>.<verb>` — `mesh-controller.status`, `mesh-controller.push` and the other ten. A call live, the mesh-controller runtime is one instance, mesh-wide, and it answers 63 verbs for that one
to `<prefix>.<name>` reaches the seat when the prefix is a seat declaring that verb, and the module seat (`status`, `push`, `assign`, `build`, `artifacts` and the rest), and the `mesh-controller`
otherwise; `seat:<seat>.<verb>` says so outright. When the controller does not answer, the list *module* answers none of its own — it serves no module tools, only its seat's, exactly as design 33
names `mesh-controller (seat)` as not answering and carries the modules' tools regardless. The described while it was still only designed. The 56-to-93 seat-verb counts each node's tool runner
`mesh-controller` *module* is always named as not answering: it serves no module tools, only its seat's. answers (below) are a different thing: the tool runner serves the verbs of whichever node-scoped
seats are held on that node (`node-backup`, `node-build-agent`, `node-service-manager` among them),
on behalf of the modules holding them, not the mesh-controller seat's — that one is served by the
mesh-controller runtime itself, a separate process, and the tool runner only asks it and relays what
it answers.
## Around it ## Where it runs
- **`invokes`** in a manifest is the grant. It is composed into the bus's user list exactly as a **Every node, not a per-operator assignment.** Measured live: all four of the mesh's machines
person's account is; the mesh MCP server is the only module that declares it. answer a `node-tools` runtime, each with its own module and tool count (between 294 and 312 tools,
- **`module check <file|dir>…`** on the controller's binary judges a manifest with no mesh: the strict 56 to 93 seat verbs, in this reading). This is a fact that shipped past the design it came from: the
parse, every per-manifest problem, and the rules between the manifests given. It prints what it cannot to-be document still describes it as assigned to "whichever machines an operator sits at"; what
judge without a store rather than refusing. The mesh MCP server's own manifest was the first thing checked actually runs is the opposite — every node the mesh holds carries one, because it is core
with it, and the whole catalogue passes. infrastructure the mesh itself depends on (discovery, the controller's own verbs, every module's
- **The person's client remains.** `operator issue` and `mesh tools|call|mcp` with a credential file tools), not a convenience assigned to a workstation. There is accordingly no assign or unassign verb
still work, for a machine that is not a node and for a mesh not yet able to assign anything. for it: removing it from a node is not an operation the controller or the catalogue exposes.
`mesh tools --console <url>` goes through a running mesh MCP server with no credential; it is covered by the
runtime repository's tests and was not exercised on the live mesh.
## What shipped bent ## What this repository's prior text got wrong
- A module registered by hand from the catalogue with `--source <url> --path modules/<m>` records a URL, The document this replaces described `mesh-console` as a catalogue module served by a container and
not a place on the git seat: `--self` takes the forge path form (`<owner>/<repository>`), which the assigned only to workstations. That module and its container are gone; the manifest is not — it
operator did not pass. The rebuild-on-merge matched the URL anyway. moved to `node-tools/module.json`, still declares `invokes: ["*"]`, and gained `waits` and the
- Modules whose upgrade policy is *record* — the forge among them — answered `tools` only once `mcp-endpoint` provision, described above. What also moved is which language builds it: the
something pushed their rebuilt runtime; until then they are listed as not answering while still manifest's `build.artifacts` names a Go binary built from `cmd/node-tools`, and that is what a node
callable. That is the policy doing what it says, not a fault of the mesh MCP server. runs. The repository still carries the earlier TypeScript sources (`node-tools/src/mesh.ts`,
`http.ts`, `runtime.ts`, `announce.ts`, `broker-nats.ts`, `client.ts`, `launch.ts`, `main.ts`,
`mcp.ts`) on main, but the manifest does not build from them. The quirks the prior text recorded
from the `mesh-console` era (a module registered by hand recording its source as a URL; a
*record*-policy module staying listed as not-answering until its rebuilt runtime is pushed) are not
re-checked here: they were about the `mesh-console` container's registration path, which no longer
exists.
## How it is checked
- `mesh_overview` and `mesh_machine <node>`: the live tool and seat-verb counts, and which modules
are named not-answering.
- `mesh_runtimes`: which runtimes answered discovery, how long they took, how large their
announcement was, and whether it was shortened to fit the bus — read this when an address is said
to be missing or on another machine.
- `mesh_describe <address>` for one tool's schema; a call through `mesh_call` reaches the module or
seat over the bus and the answer is its own, unshaped.
- The code: `mesh-tools` (`node-tools/internal/console`, `internal/announce`, `internal/bus`),
`mesh-host` and `mesh-controller`'s core-component handling of `node-tools` (witness, lease,
bus-user naming). Their unit tests hold the loopback-only refusal, the seven-tool handshake, the
discovery gather window and `mesh_wait`'s event-then-read-state rule.
- `knowledge-base.get` with `kind: decision` and number 152, 175, 195, 197, 367 or 154.
+1
View File
@@ -5,6 +5,7 @@ code: [mesh-controller internal/catalogue/state.go, mesh-controller internal/bro
updated: 2026-10-04 updated: 2026-10-04
decisions: decisions:
- "ADR 0201" - "ADR 0201"
- "ADR 0439"
--- ---
# A module's state, as it runs # A module's state, as it runs
+13 -1
View File
@@ -2,9 +2,11 @@
layer: as-is layer: as-is
status: implemented status: implemented
code: [mesh-controller, mesh-tools] code: [mesh-controller, mesh-tools]
updated: 2026-10-16 updated: 2026-10-19
decisions: decisions:
- "ADR 0106" - "ADR 0106"
- "ADR 0436"
- "ADR 0438"
--- ---
# Logins on the bus, as they run # Logins on the bus, as they run
@@ -25,6 +27,16 @@ Nothing marks what a tool does.** Read from the controller's and the tool runner
- **The caller is in the address.** A call's address carries the caller's name, so the runtime takes the caller - **The caller is in the address.** A call's address carries the caller's name, so the runtime takes the caller
from the address, never from the message. For a seat every machine holds, the address also names the machine. from the address, never from the message. For a seat every machine holds, the address also names the machine.
- **A module's process may have a login of its own** (ADR 0436, delivered 2026-10-19 in mesh-controller #390 and
mesh-sdk #36). A `process` resource that declares `bus` runs as a user its module makes and gets one login per
machine, named by machine, module and process. It may emit its declared events, take work from its module's own
work-queue stream through the one consumer the controller makes for it, use its module's states, read the
router's records of its module's asks, call the verbs it declares, and listen only on its own inbox. It can
answer no call and hold no seat. `module issue` delivers its password as the module's own secret
`broker-<process id>`, readable by that user alone. mesh-mail's sender is the first such process: it holds the
mail passwords that the tool runner may not read, and sends each mail the operator approved, which the tools
record as queued before they put it on its work queue (ADR 0438).
## What it cannot do ## What it cannot do
- **No expiry.** A login lives until its record is deleted and the list rewritten on the bus's node. - **No expiry.** A login lives until its record is deleted and the list rewritten on the bus's node.
@@ -0,0 +1,116 @@
---
layer: as-is
status: implemented
code: [mesh-controller, mesh-catalog modules/claude-code, mesh-catalog modules/gitea, mesh-catalog modules/ssh-client]
updated: 2026-10-17
decisions:
- "ADR 0266"
- "ADR 0399"
- "ADR 0409"
- "ADR 0410"
---
# Accounts on a machine, as they run
**A node record names at most two logins: the operator account, always, and an agent account, on one node of
four. Where a node names no agent account, agents run as the operator account. Nothing ties an identity to a
login, and a module that needs one declares it itself.** Read on 2026-10-17 from the controller's and the
catalogue's code on main (both local clones were a few commits behind the forge, so each claim below was
also checked against a live read) and from the live node records. The change planned in ADR 0411 to 0414
replaces this document when it is built.
## The node record
- **The operator account is stated at the controller's terminal** with `node account <node> <account> [home]`.
It is the person's login on that machine. The controller refuses to set it to the node's agent account.
- **The agent account is stated at the controller's terminal only** with `node agent-account <node> <account>
[home]`, and cleared with `--clear`. It is no verb and no setting, so an agent cannot name itself one
(ADR 0266 §1). The controller refuses root, a malformed login, a well-known service account and the
operator account.
- **Five machine facts come from the record.** `account` and `account-home` are the operator's, absent when
the node has no operator account. `agent-account` is the agent account, or the operator account where none is
named; `agent-home` is its home (`/home/<account>` when no home is stated); `agent-root` is `never` only where
the node names an agent account of its own, otherwise empty. Modules read them as `${machine:...}` words.
- **One node of four names an agent account today**, the control node. The workstation, the laptop and the
home server name none: `mesh-controller.node` says there "agents run as the operator account; no agent
account is named", and the plan for the workstation declares the `claude-code` account resource with the
operator's name and no `root` field. The tool runner also receives `MESH_AGENT_ACCOUNT` and `MESH_AGENT_HOME`.
- **Not verified:** whether the mesh-config snapshot carries either account. The controller's code reads both
from its own node table (columns for account, account home, agent account and agent home); no second place
that states them was found.
## What claude-code does with the agent account
- **It declares a `user` resource** named `${machine:agent-account}` with home `${machine:agent-home}` and
`root: ${machine:agent-root}`, and a `~/.claude` directory under that home owned by it, mode 0700. On the
control node the plan holds the resource with `root: never`; elsewhere it holds the operator's account.
Where the account is new, the mesh creates it with a home, its own group and no password (ADR 0266 §2).
- **`claude-agent`** is a file the module writes at `/usr/local/bin/claude-agent`. Run as the agent account
it starts `claude`; run as any other account it runs `sudo -iu <agent account> claude` through that
account's own sudo, carrying the operator's guard override only if it is set in that shell. The file
`/etc/claude-code/agent-account` holds the account's name.
- **The guard of ADR 0266 §28** is the module's hook on every tool call. On a node with an agent account
of its own it refuses each call of a session that runs as another account, with no override, and tells the
session to start with `claude-agent`. Its record goes to the machine's journal. The same guard also refuses
ssh to a mesh machine and writes to `/etc/hosts` and the resolver file (ADR 0245).
- **Not verified live:** this rests on the guard's code and README, not on a refusal seen on a running machine.
## The judgement that the agent account cannot become root
- **The node-engine judges `root: never` on every look**, by reading only: uid 0, a group that grants root,
a sudo, doas or polkit rule, a container runtime's socket or API port, a secret the mesh placed for another
account, a setuid program no package owns, and kernel link protection off (ADR 0266 §12 to §16). A way found,
or a question unanswered, makes the account resource unhealthy. It does not prevent any way to root.
- **The controller's self-check holds** that a node naming an agent account has it judged, in a verdict heard
within 15 minutes, as unable to become root (ADR 0266 §21 to §22). Otherwise it raises the urgent condition
`agent-can-become-root`. `mesh-controller.node` shows the verdict: for the control node it reads "cannot
become root without a person", judged the same day.
- **On the three nodes without an agent account** the check does not apply. The controller instead holds an
urgent condition `root-not-free` on each machine that runs reads or the mail notifier. Two of the three are
silenced until 2026-10-23 on the operator's decision; the home server's is open and not silenced.
## The accounts agents push as on the forge
- **Today the forge has a user per machine**, made by the gitea module's `forge-account` provision and
holding that machine's key, plus a write token the module still mints for each (ADR 0398, ADR 0400). ADR 0410
decides one forge user, `nox-agent`, in the agents' write team, each machine's key titled by machine. No tool
reads the forge's user list, so whether `nox-agent` exists yet is **not verified**.
- **The forge key is meant for the agent account** (ADR 0409 §2), but a module cannot run as that account:
the controller refuses it (issue 989, open). So the `ssh-client` module's key keeper still runs as the
operator account.
- **An ordering deadlock** between `ssh-client`, which wants `forge-account`, and `gitea`, which provides it,
stopped every node's declaration from composing on 2026-10-17 at 00:35 until gitea was registered three
minutes later (issue 986). The four urgent conditions it raised were not observed again but stay raised,
because the controller's declaration check times out and never clears them (issue 992).
Git over ssh was proven from the laptop only; the proof on the other three, and retiring the forge token the
module still places, wait on issues 989 and 991 (the latter in `fixing`).
- **Main stays closed**: a push to the main branch is refused, and a merge waits for a person (ADR 0398).
## Logins on the bus
Not here: see [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md).
## What it cannot do
- **No identity carries a login.** An agent, a person and a module are not tied to an account on a machine;
the node record names a login per node and nothing else.
- **A module declares a login itself.** Each module that needs an account writes a `user` resource of its own,
and nothing checks that two modules agree or that a login exists on every machine that needs it.
- **Agent and operator share a login on three of four machines.** There the agent runs as the operator account,
which may become root; the guard's account rule does not apply, and the exposure is a held condition, not a fix.
- **No per-agent bus password.** Every session on a machine calls through that machine's tool-runner login
(document 16), so one agent's calls cannot be told from another's.
- **Detects, does not prevent.** The judgement above lists the ways to root it looks for; a way it does not list
is not seen (ADR 0266 §17).
## How it is checked
- `mesh-controller.node` with a node name: "agents run as" line, the verdict and when it was judged, the open
conditions (`agent-can-become-root`, `root-not-free`, `uncomposable`).
- `mesh-controller.plan` with a node name: the `claude-code.agent-account` resource and its `root` field; with
`files` set, the files given, including `claude-agent` and `agent-account`.
- `mesh-controller.conditions` for the held conditions; `claude_code_guard` for the guard's record, `claude-code`
tools for its status.
- `knowledge-base.get` with `kind` decision and number 266, 409 or 410; `issue-tracker.get` with 989 or 991.
- The code: the controller's node-record and agent-account files, and the `claude-code` module's manifest and
guard in the catalogue. Their unit tests hold the node record's refusals and the guard's account rule.
+177
View File
@@ -0,0 +1,177 @@
---
layer: as-is
status: implemented
code: [mesh-catalog modules/mesh-mail, mesh-catalog modules/mesh-mail-web, mesh-catalog modules/mesh-mail-notifier]
updated: 2026-10-20
decisions:
- "ADR 0359"
- "ADR 0361"
- "ADR 0378"
- "ADR 0379"
- "ADR 0436"
- "ADR 0439"
- "ADR 0440"
---
# The mail modules, as they run
**One module, `mesh-mail`, holds every mail account and is the mesh's one `mail-store`. A second module,
`mesh-mail-web`, is the mesh's one mail web client. A third, `mesh-mail-notifier`, shows a
desktop notice of new mail on the machines it is assigned to. A caller may read an account's mail, or ask to
send from it, only when `mesh-mail`'s trusted setting `grants` names it for that account — one list per
account, giving both at once, to the same callers. A send is never performed by a tool: it is queued for a
process of its own, which sends only once the operator approves it on Telegram.** Read from `mesh-catalog` on
`origin/main` at `a67f0196` (2026-10-20) and from the live mesh through the mesh MCP server on 2026-10-20.
```
mesh-mail (mail-store, once for the mesh — the control-node)
accounts search read send send-status ── MAIL stream (headers only, 7 days) ──▶ a granted caller
own database (index) · own object-store bucket (attachments) · three key-value buckets (sends, account state, newest mails)
process keep (IDLE + index) · process watch (mail_health) · process send (mesh-mail-send account; holds the passwords)
▲ grants: one list per account, read AND send alike
│
┌────┴─────────────────────────────┬───────────────────────────────┐
mesh-mail-notifier (mail-notifier, mesh-mail-web (mail-webclient,
once per machine — laptop, work- once for the mesh — the control-node)
station) sign-in (Keycloak) ─ pages ─ send ask ─▶ operator's Approve (Telegram)
node-notifier.send on new inbox mail
```
## 1. The modules and their seats
- **`mesh-mail`** claims `mail-store`, held once for the mesh (`accounts`, `search`, `read`, `send`,
`send-status`). It is assigned today to one machine, the control-node (`module.json`; confirmed live,
2026-10-20). ADR 0378 §4 decided to move it to the home server as a step of its own, on the operator's
Approve (issue 803); as built, that move has not happened: the live mesh still runs it, and `mesh-mail-web`
beside it, on the control-node.
- **`mesh-mail-web`** is a module of its own, with its own Go module and source under `modules/mesh-mail-web`;
it claims `mail-webclient`, held once for the mesh. It is assigned to the same machine as `mesh-mail` and runs as its
own account, `mesh-mail-web`. Its manifest declares no database and no store of its own: nothing beyond its
route, its sign-in binding and its own settings on disk.
- **`mesh-mail-notifier`** is a module of its own as well (`modules/mesh-mail-notifier`), and keeps a
byte-identical copy of `mesh-mail`'s `mailevent.go`, held to it by its `copies_test.go`, because each module
builds alone. ADR 0378 §9 and ADR 0440 §1 say both are "built from `mesh-mail`'s source"; as built, they are
separate modules that share none of `mesh-mail`'s own code but that copied file. It claims `mail-notifier`, held once per machine; a
module claiming it needs `node-notifier` held on the same machine (ADR 0378 §7). It is assigned today to two
machines, the laptop and the workstation (live read of each machine's modules, 2026-10-20); the control-node
and the home server hold no instance of it.
- None of the three seats is named `mesh-*`. A caller reaches them by seat (`mail-store.<verb>`,
`mail-webclient.<verb>`, `mail-notifier.<verb>`); `mesh-mail` also answers its own tool names (`mail_accounts`
and the rest) through the same handlers.
## 2. Accounts and the MAIL stream
- **Three accounts**, all of the kind `imap`, each a password-based sign-in given at the desk as the module's
own secret `mail-credential-<account>` — one against the mesh's own mail server, two against outside
providers (ADR 0359 §5, ADR 0361 §1; `mesh-controller.settings` for `mesh-mail`, read live on 2026-10-20). No
account's name or address is repeated in this document.
- **It watches each account and says each mail on the bus.** `mesh-mail` keeps IMAP IDLE on the inbox and polls
the other folders; for each new, changed or removed message it publishes an event on its own stream `MAIL` —
account, folder, message id, date, from, to, subject, size, flags and attachment references, never a body or
an attachment's bytes (ADR 0359 §2). The stream keeps 7 days (`module.json`).
- **A send's own record** lives in its state bucket `sends`, which only the `send` process and the tools write
(`module.json`; ADR 0436 §8).
- **The index and the attachment store are its own.** A row per message and folder lives in its own Postgres
database; attachments live in an object-store bucket, each kept once by its SHA-256 digest (ADR 0359 §3, ADR
0439 §5-§6).
- **Two more buckets answer without the database.** An account bucket holds each account's state and, per
folder, its name and its mail and unread counts; a mail bucket holds the newest mails of each account (up to
200, fewer once there are more than 81 accounts), for a view that need not ask the database (ADR 0439 §2-§4).
`mesh-mail-web`'s accounts page reads the folders from this bucket — a read wider than any one caller's grant
— but only for an account its own grant already let it ask about (ADR 0440 §4).
## 3. `grants` — one list per account, giving both at once
- **The setting.** `mesh-mail`'s trusted setting `grants` names, per account, the callers that may reach it: a
module's name alone (granted wherever it runs), `<machine>.<module>`, or a person. Nobody is granted an
account by default (ADR 0359 §4).
- **One check, every door.** The code that decides whether a caller may read an account's mail (`mail_search`,
`mail_read`, the account's share of `MAIL`) and the code that decides whether it may ask to send from it
(`mail_send`) is the same function, reading the one list (`modules/mesh-mail/cmd/mesh-mail/config.go`'s
`Grants.Allows`, called from `tools.go` for every tool, `mail_send` included, confirmed on `origin/main` at `a67f0196`). There is no
second list and no narrower grant for sending alone: a caller named for an account may do both, and a caller
left out of it may do neither.
- **Who is granted, today, for every account alike** (`mesh-controller.settings` for `mesh-mail`, read live on
2026-10-20): `mesh-mail-notifier` (named alone, so wherever it is assigned it may read); each of the four
machines' own `node-tools` — the control-node's, the laptop's, the workstation's and the home server's, which
is how an agent session on any of them reaches a mail account at all; and the control-node's `mesh-mail-web`.
So the notifier, every machine's agent sessions, and the web client can each both read an account's mail and
ask to send from it, from the one list.
## 4. The send path
- **A send is an ask, never a write by the tool.** `mail_send` (account, to, subject, body) is open only to a
caller the account's `grants` names. It performs nothing itself: it raises an ask on the operator channel
naming the account, the recipients, the subject and the first lines, and returns the ask's id at once (ADR
0361 §2).
- **The operator's Approve queues it once.** On an Approve recorded on a channel that proves who answered,
`mesh-mail`'s tools record the send `approved` and publish it, once, on the work queue `mail-sends`, carrying
only the send's id, its account and its ask id — never the recipients, subject or body, which stay in the
send's own record (ADR 0436 §8).
- **A process of its own performs it.** `mesh-mail`'s process `send` runs as its own account, `mesh-mail-send`,
never the tool runner's account: it alone reads the accounts' passwords. It takes work from the queue,
verifies the operator's warrant again against the router's own record rather than trusting the queued message,
claims the send once, hands it to the account's outgoing server, and records the outcome — retrying a send the
server provably never took, up to five times, each after a longer wait, before giving up (ADR 0436 §2, §7,
§9-§10).
- **`mail_send_status` answers one of a fixed set of states**: waiting for the operator, approved and waiting
for the sender, being sent, waiting for its next attempt, sent, failed (with its reason), refused, not proven
(the handover may or may not have reached the server), declined, expired, or unknown for an id it does not
know or that belongs to another caller's send (`send.go`, `sender.go`; ADR 0436 §12).
## 5. `mesh-mail-web`
- **Its pages**, all behind sign-in: the accounts page (`/`), a folder (`/folder`), search (`/search`), one
mail (`/mail`), an attachment download (`/attachment`), writing a mail (`/write`), its send (`/send`) and its
state (`/send-status`) — plus the sign-in callback (`/signed-in`). Every changing form (today, the send)
carries a token bound to the session (`server.go`, `csrf.go`).
- **Sign-in is Keycloak's.** The module requires `oidc-client` and uses the authorisation-code flow with PKCE;
the identity (the ID token's own username claim) must be in the trusted setting `operators` — nobody by
default, and today one identity, the operator's own (ADR 0440 §3; `mesh-controller.settings` for
`mesh-mail-web`, read live on 2026-10-20). The session cookie is `Secure`, `HttpOnly` and `SameSite=Strict`,
ending 12 hours after sign-in (`auth.go`).
- **Reachable only from the mesh's own machines.** Its route's reach is set to `internal`, so the route proxy
composes only its internal name, never a public one, and the module itself answers any other host 404 (ADR
0440 §2; `server.go`).
- **A mail is shown as text.** `mail_read`'s own HTML-to-text is rendered through `html/template`'s escaping,
never parsed as markup; an attachment is answered only as a download, `Content-Disposition: attachment` and
`X-Content-Type-Options: nosniff`; every page carries a content security policy allowing nothing from
elsewhere (ADR 0440 §5; `mail.go`).
- **It writes nothing of its own.** No database, nothing on disk beyond its own settings; it reaches mail only
through `mail-store`'s verbs, calling as `<machine>.mesh-mail-web`, and reads one thing directly besides —
`mesh-mail`'s account bucket, for folders and counts, of an account its own grant already let it ask about
(ADR 0440 §4, §6).
## 6. `mesh-mail-notifier`
- **One notice per new inbox mail, at most once.** For each account it is granted, on hearing `mesh-mail`'s
`added` event, it shows the sender (name and address), the subject and the account's name through the
machine's `node-notifier`, under its own account (`runs-as`), never the tool runner's.
- **Quiet rules.** It shows nothing for a mail outside the inbox, for a mail from the account's own address, and
for a mail already flagged read when it arrives (`notifier.go`, `mailevent.go`). During an account's first sync
or a resync `mesh-mail` publishes no `added` events, so nothing reaches the notifier then (`notifier.go`'s
opening note, citing `mesh-mail`'s `imap.go`).
- **Folding.** More than three qualifying mails of one account within a trailing 60-second window become one
notice, shown once the window has passed since the fold's own first mail, naming how many and the latest
sender; a message already shown — tracked by its message id, or by its folder, UIDVALIDITY and UID where it
has none — is never shown twice, even redelivered. A notice that fails to send is retried at most once a
minute (`notifier.go`, its constants `FoldOver`, `Window`, `RetryBackoff`).
- **Assignment.** It is assigned today to the laptop and the workstation (live read, 2026-10-20), where it needs
`node-notifier` to be held beneath it.
## 7. Health
- **`mesh-mail`**'s liveness is its `keep` process's own unit health; its watching is judged once a minute by
its `watch` process and read through the tool `mail_health`: `waits` (naming the secret) for an account whose
password was never given, `unhealthy` (naming the account and why) for a connection down or an IDLE silent
for five minutes, healthy otherwise; its `send` process is a plain unit (ADR 0359 §6; `health.go`).
- **`mesh-mail-web`**'s own tool `mail_web_health` reads the one state its HTTP endpoint keeps: still setting up
sign-in, listening, or ended with its error — unhealthy whenever it is not listening (`main.go`).
- **`mesh-mail-notifier`** declares no process resource: its manifest declares only its account and its state
directory. Its bundle is the one process the machine's runtime launches for it and keeps alive, under its own
account (`runs-as`); it serves its tools and runs the watcher beside them (`main.go`'s opening note; commit
12d78520, issue 859). Its tool `mail_notifier_health` is unhealthy once an account's notice
has failed to show for more than ten minutes, and answers only when asked; no check of the node-engine judges
the module by it. A watching process of its own is tracked by issue 841, not yet decided (its `README.md`).
The comment in its `health.go` that still names a process `notify` predates that commit. It keeps one state
bucket, `shown`, for 30 days, to show no mail twice (`module.json`).
+2
View File
@@ -25,6 +25,8 @@ Where the two disagree, the implementation wins and the disagreement is stated.
| [`14-a-modules-state.md`](14-a-modules-state.md) | A module's current state on the bus: what the controller creates, the runtime serves, and the first live use showed | | [`14-a-modules-state.md`](14-a-modules-state.md) | A module's current state on the bus: what the controller creates, the runtime serves, and the first live use showed |
| [`15-the-agent-and-its-licences.md`](15-the-agent-and-its-licences.md) | The operator's agent on every machine and the licence manager that keeps its licences | | [`15-the-agent-and-its-licences.md`](15-the-agent-and-its-licences.md) | The operator's agent on every machine and the licence manager that keeps its licences |
| [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md) | The bus's one list of logins, a login per module assignment, persons limited to their tools, sessions calling through the tool runner's login, and no effect on any tool | | [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md) | The bus's one list of logins, a login per module assignment, persons limited to their tools, sessions calling through the tool runner's login, and no effect on any tool |
| [`17-accounts-on-a-machine.md`](17-accounts-on-a-machine.md) | The operator and agent accounts on the node record, claude-code's own login and guard, the judgement that the agent account cannot become root, and the forge accounts agents push as |
| [`18-the-mail-modules.md`](18-the-mail-modules.md) | mesh-mail's accounts and MAIL stream, the one `grants` list that gives both reading and sending at once, the send path through its own process and the operator's Approve, mesh-mail-web's pages and sign-in, and mesh-mail-notifier's pop-up and its quiet rules |
## What these documents are not ## What these documents are not
+7 -4
View File
@@ -1,12 +1,13 @@
--- ---
layer: to-be layer: to-be
status: proposed status: in-progress
code: code:
- mesh-controller cmd/mesh-builder - mesh-controller cmd/mesh-builder
- mesh-controller internal/builder - mesh-controller internal/builder
- mesh-catalog modules/build-agent - mesh-controller modules/build-agent
updated: 2026-10-08 updated: 2026-10-17
decisions: decisions:
- "ADR 0405"
- "ADR 0262" - "ADR 0262"
- "ADR 0233" - "ADR 0233"
- "ADR 0219" - "ADR 0219"
@@ -30,6 +31,8 @@ decisions:
# Building a module # Building a module
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's builder (internal/builder, cmd/mesh-builder) implements the recipe kinds described here, and the sections marked built and proven live match the code. Left: not broken down into milestones; issue 1072 found it saying proposed.
[`12-a-module-repository`](12-a-module-repository.md) says what a module may build — an image, an [`12-a-module-repository`](12-a-module-repository.md) says what a module may build — an image, an
archive, an upstream mirror — and where the result goes. This says **how a build is modelled**, and archive, an upstream mirror — and where the result goes. This says **how a build is modelled**, and
why the current model does not fit what a module is. why the current model does not fit what a module is.
@@ -138,7 +141,7 @@ need to run. Third-party software still arrives as an image, because that is how
## What this costs, stated before it is chosen ## What this costs, stated before it is chosen
**Every language is permanent.** It needs an SDK — broker client, sealed-credential reading, the **Every language is permanent.** It needs an SDK — broker client, sealed-credential reading, the
event envelope, tool serving — a toolchain image, and a bundler the builder understands. And the event envelope, tool serving — a build module that installs its toolchain on every builder (ADR 0405), and a bundler the builder understands. And the
spine changes rarely but cascades when it does spine changes rarely but cascades when it does
(ADR 0039): with one language a contract (ADR 0039): with one language a contract
change is one edit; with four it is four that must land together, and a mesh whose SDKs disagree change is one edit; with four it is four that must land together, and a mesh whose SDKs disagree
+4 -2
View File
@@ -1,13 +1,13 @@
--- ---
layer: to-be layer: to-be
status: proposed status: in-progress
code: code:
- mesh-sdk src - mesh-sdk src
- mesh-tools src/broker-nats.ts (and broker-amqp.ts until the rollout) - mesh-tools src/broker-nats.ts (and broker-amqp.ts until the rollout)
- mesh-controller internal/link - mesh-controller internal/link
- mesh-controller cmd/mesh-controller (status) - mesh-controller cmd/mesh-controller (status)
- mesh-catalog modules/postgres, modules/keycloak (the Go provisioner loop) - mesh-catalog modules/postgres, modules/keycloak (the Go provisioner loop)
updated: 2026-10-06 updated: 2026-10-17
decisions: decisions:
- "ADR 0224" - "ADR 0224"
- "ADR 0230" - "ADR 0230"
@@ -24,6 +24,8 @@ decisions:
# The module protocol # The module protocol
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: the wire this design describes is in mesh-sdk and mesh-controller's internal/link, and the design's own checks against the code (2026-09-16) agree. Left: not broken down into milestones; issue 1072 found it saying proposed.
**What a module's code and the mesh say to each other.** An SDK is an implementation of this in one **What a module's code and the mesh say to each other.** An SDK is an implementation of this in one
language and nothing more (ADR 0074). language and nothing more (ADR 0074).
@@ -1,11 +1,11 @@
--- ---
layer: to-be layer: to-be
status: proposed status: in-progress
code: code:
- mesh-host internal/bootstrap - mesh-host internal/bootstrap
- mesh-host cmd/mesh-bootstrap - mesh-host cmd/mesh-bootstrap
- mesh-lab test/integration/one-node-mesh.test.ts - mesh-lab test/integration/one-node-mesh.test.ts
updated: 2026-10-06 updated: 2026-10-17
decisions: decisions:
- "ADR 0226" - "ADR 0226"
- "ADR 0067" - "ADR 0067"
@@ -16,6 +16,8 @@ decisions:
# The installation, in full # The installation, in full
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: the node-engine's bootstrap (`mesh-host` internal/bootstrap) carries phases 2 and 3 of the installation, so the steps this text calls manual are partly automated. Left: not broken down into milestones; the text is not yet corrected against the code.
Every step from a machine with nothing to a mesh that maintains itself. Written out explicitly Every step from a machine with nothing to a mesh that maintains itself. Written out explicitly
because it is the procedure everything else depends on, and because the parts that do not exist because it is the procedure everything else depends on, and because the parts that do not exist
yet are easier to see beside the parts that do. yet are easier to see beside the parts that do.
+3 -1
View File
@@ -6,7 +6,7 @@ code:
- mesh-controller - mesh-controller
- mesh-catalog - mesh-catalog
- mesh-lab - mesh-lab
updated: 2026-09-15 updated: 2026-10-17
decisions: decisions:
- "ADR 0067" - "ADR 0067"
- "ADR 0074" - "ADR 0074"
@@ -17,6 +17,8 @@ decisions:
# The work ahead # The work ahead
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Phase 2's blocker (issue 053) is resolved and delivered, and the forge's module declares the package registry in full, Phase 2's done-when. Whether every phase holds is checked under issue 1073, which marks it implemented or names what is left.
Everything decided this cycle and not yet built, in the order its dependencies allow. Each phase Everything decided this cycle and not yet built, in the order its dependencies allow. Each phase
ends at something provable on a running mesh, because a phase that ends at a claim is a phase that ends at something provable on a running mesh, because a phase that ends at a claim is a phase that
went missing without anything complaining. went missing without anything complaining.
+5 -3
View File
@@ -1,8 +1,8 @@
--- ---
layer: to-be layer: to-be
status: designed status: in-progress
code: [] code: [mesh-controller]
updated: 2026-09-25 updated: 2026-10-17
decisions: decisions:
- "ADR 0084" - "ADR 0084"
- "ADR 0027" - "ADR 0027"
@@ -11,6 +11,8 @@ decisions:
# 23 — Choosing a provider # 23 — Choosing a provider
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's internal/catalogue/resolve.go implements the model described here, and designs 26, 27 and 29 rely on it. Not yet checked against this text clause by clause, nor given an as-is: issue 1074.
A provision is named for what the consumer's code is coupled to A provision is named for what the consumer's code is coupled to
(ADR 0027): (ADR 0027):
`postgres-database`, not `database`. That decides *what kind* of provider satisfies a requirement. `postgres-database`, not `database`. That decides *what kind* of provider satisfies a requirement.
@@ -5,7 +5,7 @@ code:
- mesh-controller internal/inventory - mesh-controller internal/inventory
- mesh-controller internal/catalogue - mesh-controller internal/catalogue
- mesh-controller cmd/mesh-controller - mesh-controller cmd/mesh-controller
updated: 2026-10-02 updated: 2026-10-17
decisions: decisions:
- "ADR 0181" - "ADR 0181"
- "ADR 0182" - "ADR 0182"
@@ -14,6 +14,13 @@ decisions:
- "ADR 0051" - "ADR 0051"
- "ADR 0113" - "ADR 0113"
- "ADR 0117" - "ADR 0117"
- "ADR 0399"
- "ADR 0400"
- "ADR 0412"
- "ADR 0415"
- "ADR 0416"
- "ADR 0417"
- "ADR 0422"
--- ---
# 29 — A node has operator accounts, and the mesh owns what lives under a home # 29 — A node has operator accounts, and the mesh owns what lives under a home
@@ -56,6 +63,15 @@ These are a **family**, not one module: an `ssh-client` module, a shell module,
module, each a *universal-tier* consumer of the account fact — assigned wherever a person logs in, module, each a *universal-tier* consumer of the account fact — assigned wherever a person logs in,
which is every node, unlike the graphical stack that a capability gates. which is every node, unlike the graphical stack that a capability gates.
**A folder a module owns says how it treats a file it did not write** (ADR 0416, ADR 0422). Declaring a
directory under a home owns it (ADR 0182 §1), and the owner alone writes there and reacts to every file
that enters it, never losing what the file held. The manifest says how, on the directory itself:
`unknown` is `move-out` (the node-engine moves every path the manifest does not declare and `knows` does
not name into `~/.local/state/mesh-moved/<module>/`, with a list), `module` (the module's own process
reacts, as ssh-client does in `~/.ssh`) or `program` (everything in it is the program's, as in a
service's data directory). `move-out` is only for a folder under a home, the home of the account the
assignment runs for (ADR 0412 §5). A folder many modules write in is not declared at all.
## 3. The whole of `~/.ssh` is the mesh's — with one boundary drawn inside it ## 3. The whole of `~/.ssh` is the mesh's — with one boundary drawn inside it
The predecessor owned a single file (`~/.ssh/config`) and left the rest alone; it drifted, because The predecessor owned a single file (`~/.ssh/config`) and left the rest alone; it drifted, because
@@ -82,6 +98,9 @@ ADR 0117 draws for the uplink and the sshd
module draws for the firewall: **the mesh must never be able to arrange the one failure that severs module draws for the firewall: **the mesh must never be able to arrange the one failure that severs
its own way back in.** The carve-out is not a convenience; it is that rule, in `~/.ssh`. its own way back in.** The carve-out is not a convenience; it is that rule, in `~/.ssh`.
Since ADR 0415 and ADR 0417 the found class no longer holds in `~/.ssh`: ssh-client moves out what it
does not know into a kept folder, and never moves a key or `authorized_keys`, which keeps this boundary.
## 4. Keys are the mesh's to generate — through a CA, and existing keys are adopted, not replaced ## 4. Keys are the mesh's to generate — through a CA, and existing keys are adopted, not replaced
Key *generation* is the mesh's, not each node's improvising its own. The clean form is an **SSH Key *generation* is the mesh's, not each node's improvising its own. The clean form is an **SSH
@@ -220,6 +239,17 @@ each need a decision before the modules that replace the generators can be writt
The modules §2 calls *a family* — the shell, the terminal, the desktop, everything under a home that is not `~/.ssh` — are designed in [37 — The operator's machine](37-the-operators-machine.md), under ADR 0173 to ADR 0177. This document keeps `~/.ssh`, the CA and the roster files. Two things it listed as not built are decided there: user-scoped services (ADR 0177) and the one-off steps a hook used to run (declared state, or a seat's verb). The modules §2 calls *a family* — the shell, the terminal, the desktop, everything under a home that is not `~/.ssh` — are designed in [37 — The operator's machine](37-the-operators-machine.md), under ADR 0173 to ADR 0177. This document keeps `~/.ssh`, the CA and the roster files. Two things it listed as not built are decided there: user-scoped services (ADR 0177) and the one-off steps a hook used to run (declared state, or a seat's verb).
## The forge over SSH — 2026-10-16
`~/.ssh` gains the account's own key for the forge. ADR 0399 decides that each machine's account has an SSH
key made on the machine, that the forge registers its public half on the machine's agent user, and that
`ssh-client` writes the forge's SSH key into `known_hosts`, ssh's configuration entry for the forge and git's
rule that rewrites the forge's `https://` address to its SSH address. ADR 0400 decides how the public half
travels: `ssh-client` writes it to its own state, keyed by node and account, and the forge's provider reads it
there, since a manifest's contribution cannot carry a value made at run time. The forge's SSH address is its
machine's name on the mesh, where its SSH port is open; the key's file is `~/.ssh/mesh_ed25519`, beside the
operator's own keys, which stay theirs (§3). Built under issue 955.
## References ## References
- The gap was found generating `~/.ssh/config` from the *HAL* registry (`hal/terminal`'s - The gap was found generating `~/.ssh/config` from the *HAL* registry (`hal/terminal`'s
@@ -236,3 +266,4 @@ The modules §2 calls *a family* — the shell, the terminal, the desktop, every
- ADR 0117, - ADR 0117,
ADR 0118 — ADR 0118 —
the never-sever-the-channel rule and the found-vs-owned semantics, applied here to `~/.ssh`. the never-sever-the-channel rule and the found-vs-owned semantics, applied here to `~/.ssh`.
- ADR 0399, ADR 0400 — git over SSH with a key made on each machine, and how its public half reaches the forge.
@@ -1,8 +1,8 @@
--- ---
layer: to-be layer: to-be
status: proposed status: in-progress
code: [] code: [mesh-controller]
updated: 2026-10-06 updated: 2026-10-17
decisions: decisions:
- "ADR 0238" - "ADR 0238"
- "ADR 0221" - "ADR 0221"
@@ -15,6 +15,8 @@ decisions:
# 30 — The mesh updates itself on a push # 30 — The mesh updates itself on a push
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's delivery plans, merge gate and merge window (plan.go, release_plan.go, merge_gate.go, merge_window_bus.go, changeplan.go, with their migrations), which design 47 builds on. Not yet checked against this text nor given an as-is: issue 1075.
**Today the mesh does not update itself; a person drives the pipeline by hand, and one class of **Today the mesh does not update itself; a person drives the pipeline by hand, and one class of
change freezes it.** A code change lands in `mesh-controller` or `mesh-catalog`, and getting it onto change freezes it.** A code change lands in `mesh-controller` or `mesh-catalog`, and getting it onto
the machines is a sequence somebody types. The predecessor's pipelines rebuilt and redeployed on a the machines is a sequence somebody types. The predecessor's pipelines rebuilt and redeployed on a
+15 -8
View File
@@ -1,11 +1,13 @@
--- ---
layer: to-be layer: to-be
status: designed status: implemented
code: [mesh-catalog, mesh-tools, mesh-controller] code: [mesh-tools, mesh-host, mesh-controller]
updated: 2026-10-03 updated: 2026-10-17
decisions: decisions:
- "ADR 0197" - "ADR 0197"
- "ADR 0195" - "ADR 0195"
- "ADR 0175"
- "ADR 0367"
- "ADR 0160" - "ADR 0160"
- "ADR 0159" - "ADR 0159"
- "ADR 0152" - "ADR 0152"
@@ -163,11 +165,16 @@ matched it by URL.
## What this does not settle ## What this does not settle
- Narrowing a mesh MCP server's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet. - **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through
- The mesh's own verbs on the bus. Design 33's third family; this document only says where they appear the `mesh-controller` seat, under ADR 0154 — settled, not open. [As-is 13](../00-as-is/13-the-console.md)
once they exist. records it live.
- A person's identity behind the mesh MCP server. The mesh sees the mesh MCP server's account; design 15 keeps the - **Narrowing the tool runner's grant per assignment.** ADR 0046 makes it a setting; nothing reads
question open. one yet. Owned by [design 58](58-agent-work-as-the-meshs-own.md) milestone 6 (issue 883): an
identity asked for each assignment is one of that milestone's parts.
- **A person's identity behind the mesh MCP server.** The mesh still sees only the tool runner's own
account, never the caller's; design 15 raised the question and leaves it open. Owned by the same
milestone (design 58 milestone 6, issue 883): its outcome is that every person, agent and module
becomes an identity with a role, and a call carries which one made it.
## References ## References
@@ -2,8 +2,10 @@
layer: to-be layer: to-be
status: in-progress status: in-progress
code: [mesh-catalog modules/claude-code] code: [mesh-catalog modules/claude-code]
updated: 2026-10-13 updated: 2026-10-17
decisions: decisions:
- "ADR 0411"
- "ADR 0412"
- "ADR 0343" - "ADR 0343"
- "ADR 0274" - "ADR 0274"
- "ADR 0273" - "ADR 0273"
@@ -306,21 +308,27 @@ operator's word — and, for the operator's own home, only at the terminal or on
## 9. The account the agent runs as ## 9. The account the agent runs as
*Added 2026-10-08 by ADR 0266.* *Added 2026-10-08 by ADR 0266; changed 2026-10-17 by ADR 0411 and ADR 0412.*
Everywhere above, "the operator's home" and "the operator account" mean **the account the agent runs as**. Everywhere above, "the operator's home" and "the operator account" mean **the account the agent runs as**:
That is the operator account, unless the node names an **agent account** of its own. The control node does. the machine account of the identity the module runs for (ADR 0412). claude-code declares that it can run for
an agent identity, in its assignment or when a session is started: started for `nox` it runs with the agent's
home; started for the operator, it is the default run, with the operator's configuration. On a
machine where no agent is marked as the agent account, its assignment is the default run, as every module's is.
**The fact.** The operator states the agent account at the controller's terminal. No verb and no setting **The fact.** The machine accounts come from the identity seat (ADR 0411); the operator no longer states the
writes it. The controller resolves the machine facts `agent-account`, `agent-home` and `agent-root`, and gives agent account at the controller's terminal. A machine's agent account is the machine account of the agent the
the tool runner the account and its home. Where the node names none, the facts name the operator account and identity seat marks as the agent account there, on the operator's Approve: `nox` on the control node. There this module's
`agent-root` is empty, so nothing changes there. assignment is run for that agent, and the controller refuses one that is not. The controller resolves the
machine facts `agent-account`, `agent-home` and `agent-root` from it, and gives the tool runner the account and
its home. Where no agent is marked (every other machine today), the facts name the operator account and `agent-root`
is empty, so nothing changes there, though a login `nox` exists for the modules run for it.
**What the module declares.** **What the module declares.**
- A `user` named by `agent-account`, with `root` set from `agent-root`. Where the account is the agent's own, - No login. The mesh makes the agent's machine account (home, own group, no password) from the identity, and the
the mesh creates it (home, own group, no password) and the node-engine judges on every look that it cannot node-engine judges on every look that it cannot become root without a person, by the ways ADR 0266 §12 to §17 list.
become root without a person, by the ways ADR 0266 §3 lists. It detects those ways and no others; it It detects those ways and no others; it prevents nothing. The `user` resource named by `agent-account`,
prevents nothing. which this module declared until ADR 0411, goes.
- `~/.claude` under that account's home, owned by it. - `~/.claude` under that account's home, owned by it.
- `claude-agent`, a command that starts the agent as that account through the operator's own sudo, or - `claude-agent`, a command that starts the agent as that account through the operator's own sudo, or
directly when already run as it. directly when already run as it.
File diff suppressed because it is too large Load Diff
+17 -7
View File
@@ -2,20 +2,23 @@
layer: to-be layer: to-be
status: in-progress status: in-progress
code: [hq 00-META/checks/words.py, mesh-catalog merge-check.sh] code: [hq 00-META/checks/words.py, mesh-catalog merge-check.sh]
updated: 2026-10-11 updated: 2026-10-20
decisions: decisions:
- "ADR 0429"
- "ADR 0244" - "ADR 0244"
- "ADR 0006" - "ADR 0006"
- "ADR 0008" - "ADR 0008"
- "ADR 0302" - "ADR 0302"
- "ADR 0354"
- "ADR 0458"
--- ---
# 49 — The mesh in domains # 49 — The mesh in domains
**Every concept of the mesh belongs to one of eleven domains, which owns its one word. The glossary is **Every concept of the mesh belongs to one of thirteen domains, which owns its one word. The glossary is
organised by them and is the authority; a word it retires is named in the entry that replaced it, with organised by them and is the authority; a word it retires is named in the entry that replaced it, with
where it is retired; and two checks hold the documents and the tools' descriptions to it.** where it is retired; and two checks hold the documents and the tools' descriptions to it.**
(ADR 0244; the eleventh, Media, ADR 0302.) (ADR 0244; the eleventh, Media, ADR 0302; the twelfth, Config, ADR 0354; the thirteenth, Mail, ADR 0458.)
A **domain** is an area of the mesh that owns a set of concepts: inside it each concept has one word, A **domain** is an area of the mesh that owns a set of concepts: inside it each concept has one word,
and the domain decides what that word means. The glossary and the domain decides what that word means. The glossary
@@ -31,12 +34,14 @@ shape — what each is for, where its records live, and how they depend on one a
| **Placement** | to decide, send and apply what each node runs, and say why | the controller's `inventory` database | | **Placement** | to decide, send and apply what each node runs, and say why | the controller's `inventory` database |
| **Provisioning** | to resolve which module serves a provision for which consumer, and wire the two | the controller | | **Provisioning** | to resolve which module serves a provision for which consumer, and wire the two | the controller |
| **Identity and access** | to issue, hold, rotate and check credentials and grants | the controller's `identity` and `licences` databases, and the vault | | **Identity and access** | to issue, hold, rotate and check credentials and grants | the controller's `identity` and `licences` databases, and the vault |
| **Change and delivery** | to take one commit from its pull request to every node that should run it, and put it back when it fails | the `mesh-delivery` module, and the controller's planner and rollout | | **Delivery** | to take one commit from its pull request to every node that should run it, and put it back when it fails | the `mesh-delivery` module and the controller's planner and rollout today; from each release of ADR 0429 on, the parts of the repository `mesh-delivery` it builds |
| **Health and repair** | to notice what is wrong, say it once, repair what may be repaired unattended, and record what was done by hand | the controller's condition store | | **Health and repair** | to notice what is wrong, say it once, repair what may be repaired unattended, and record what was done by hand | the controller's condition store |
| **Data** | to know every item of data a module holds, how precious it is, and keep it recoverable | the manifests' data declarations, and every node's `node-backup` seat | | **Data** | to know every item of data a module holds, how precious it is, and keep it recoverable | the manifests' data declarations, and every node's `node-backup` seat |
| **Connectivity** | to make every node and module reachable by name where it should be, and unreachable where it should not | the controller | | **Connectivity** | to make every node and module reachable by name where it should be, and unreachable where it should not | the controller |
| **Operator and conversation** | to let the mesh and its operator tell, ask and answer, and act only on an answer it can trust | the router, and the controller for authorising asks | | **Operator and conversation** | to let the mesh and its operator tell, ask and answer, and act only on an answer it can trust | the router, and the controller for authorising asks |
| **Media** | to find, fetch and file copies of the works a person watches, listens to and reads, each in the library its kind names | the `media-importer` module's settings and memory, and the servarr apps' own libraries | | **Media** | to find, fetch and file copies of the works a person watches, listens to and reads, each in the library its kind names | the `media-importer` module's settings and memory, and the servarr apps' own libraries |
| **Config** | to hold, check and resolve every value a module reads that someone may change, and say where each resolved value came from | the `mesh-config` module, in its buckets on the bus, with its store as the history behind them |
| **Mail** | to hold the operator's mail accounts, say each mail on the bus, note each mail for the operator and tell them of the mail that matters, and send a mail only on the operator's Approve | `mesh-mail` (its index database, attachment bucket and account buckets) and `mesh-mail-reader` (its database of mail entries, mail groups and reading lessons) |
Beside them, **the record** is this repository's own vocabulary — research, decisions, designs, Beside them, **the record** is this repository's own vocabulary — research, decisions, designs,
issues, playbooks, checks — listed because its words meet the mesh's. issues, playbooks, checks — listed because its words meet the mesh's.
@@ -61,7 +66,7 @@ a change of meaning upstream ripples down, never up. Arrows point downstream.
+-------------> Health and repair <-------------------+ +-------------> Health and repair <-------------------+
| | | |
v v v v
Change and delivery Operator and conversation Delivery Operator and conversation
| ^ | ^
+-------------------+ +-------------------+
(a held delivery waits for a person's word) (a held delivery waits for a person's word)
@@ -70,7 +75,12 @@ a change of meaning upstream ripples down, never up. Arrows point downstream.
Media stands beside the diagram, below Module alone: its words are about what a person watches, not Media stands beside the diagram, below Module alone: its words are about what a person watches, not
about the mesh's machinery, and no other domain reads them. about the mesh's machinery, and no other domain reads them.
Change and delivery sits low because it reads health and data; in time it runs first, and asks Placement Mail stands below the diagram's foot: it is downstream of Identity and access (the operator grants each mail
account to a caller) and of Operator and conversation (a mail send is an ask, a mail signal is a notice through
the operator channel), and upstream of nothing. It is the operator's own correspondence, which the mesh holds and
reads for them, not the mesh talking about itself, so it is not part of Operator and conversation (ADR 0458).
Delivery sits low because it reads health and data; in time it runs first, and asks Placement
to send. An authorising answer acts in whichever domain asked, through that domain's own verb: the to send. An authorising answer acts in whichever domain asked, through that domain's own verb: the
conversation owns the asking, never the act. conversation owns the asking, never the act.
@@ -84,7 +94,7 @@ ADR 0006 named seven contexts of the controller. Each is a domain now, under the
| config | split: settings to Module and Placement, secrets to Identity and access | | config | split: settings to Module and Placement, secrets to Identity and access |
| connectivity | Connectivity | | connectivity | Connectivity |
| provisioning | Provisioning | | provisioning | Provisioning |
| delivery | Change and delivery — owned by the `mesh-delivery` module, not by the controller (ADR 0239) | | delivery | Delivery — owned by the `mesh-delivery` module, not by the controller (ADR 0239); named so by ADR 0429 §2, its parts living in the repository `mesh-delivery` |
| observability | Health and repair: the mesh built conditions, not "alerts", and half of the domain is repair | | observability | Health and repair: the mesh built conditions, not "alerts", and half of the domain is repair |
| identity | Identity and access, with the licences store the seven did not name | | identity | Identity and access, with the licences store the seven did not name |
@@ -1,8 +1,8 @@
--- ---
layer: to-be layer: to-be
status: designed status: in-progress
code: [] code: [mesh-controller]
updated: 2026-10-10 updated: 2026-10-17
decisions: decisions:
- "ADR 0289" - "ADR 0289"
- "ADR 0201" - "ADR 0201"
@@ -12,6 +12,8 @@ decisions:
# 54 — The controller's records on the bus # 54 — The controller's records on the bus
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's record mirrors (internal/recordmirror, cmd/mesh-controller/record_mirrors.go, internal/inventory's record mirrors), the mechanism specified here. Not yet checked mirror by mirror; not broken down into milestones.
**What each node runs, how far each rollout has got, what each build is doing and what each setting holds **What each node runs, how far each rollout has got, what each build is doing and what each setting holds
can be watched on the bus, and what is watched never says something the controller's store does not** can be watched on the bus, and what is watched never says something the controller's store does not**
(ADR 0289, (ADR 0289,
+118 -10
View File
@@ -1,15 +1,19 @@
--- ---
layer: to-be layer: to-be
status: designed status: in-progress
code: [] code: [mesh-catalog, hq]
updated: 2026-10-12 updated: 2026-10-18
decisions: decisions:
- "ADR 0290" - "ADR 0290"
- "ADR 0321" - "ADR 0321"
- "ADR 0423"
- "ADR 0424"
--- ---
# 55 — The knowledge base # 55 — The knowledge base
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built and live since 2026-10-11: the knowledge base is the module mesh-knowledge in mesh-catalog, holding every decision record and research effort (issues 425 and 426, resolved and delivered). Left: the later slices, issues 428 and 429.
**The mesh keeps its decision records and research efforts whole in a module of its own, `mesh-knowledge`, one **The mesh keeps its decision records and research efforts whole in a module of its own, `mesh-knowledge`, one
instance on the control-node, holding the seat `knowledge-base`: each record's number, status, topic, typed links, instance on the control-node, holding the seat `knowledge-base`: each record's number, status, topic, typed links,
its clauses with their own status, and its reasoning, with every version kept. A record is proposed, checked when it its clauses with their own status, and its reasoning, with every version kept. A record is proposed, checked when it
@@ -26,7 +30,8 @@ gets, in one call under 4 KiB, the clauses in force, the records they come from,
read the reasoning behind each. read the reasoning behind each.
This document designs the first slice in detail, tracked by issue 425. The slices after it are in This document designs the first slice in detail, tracked by issue 425. The slices after it are in
[§ 9](#9-the-slices-after-the-first). [§ 9](#9-the-slices-after-the-first), which also designs the third: designs and the glossary's entries become records,
and the knowledge base works out each design's state (ADR 0423, ADR 0424).
## 1. The parts ## 1. The parts
@@ -39,9 +44,12 @@ This document designs the first slice in detail, tracked by issue 425. The slice
├ verbs ask, get, holds, links, history, reserve, propose, import, return ├ verbs ask, get, holds, links, history, reserve, propose, import, return
│ served by the control-node's tool runner │ served by the control-node's tool runner
├ events proposed, reviewed, accepted, changed, imported, drafted, confirmed, returned ├ events proposed, reviewed, accepted, changed, imported, drafted, confirmed, returned
└ consumes the router's warrants: the operator's Approve for a record that needs it (§ 5) ├ consumes the router's warrants: the operator's Approve for a record that needs it (§ 5)
└ consumes the delivery module's delivery-done announcement: a design's state (§ 9)
mesh-issues (control-node) the counters for decision and research numbers (§ 6) mesh-issues (control-node) the counters for decision and research numbers (§ 6);
the issues' design links, read for a design's state (§ 9)
mesh-delivery (its seat) announces each delivery done, on the bus (§ 9)
claude-code (same node) claude_code_judge: review a proposal (§ 5), draft and confirm clauses (§ 7) claude-code (same node) claude_code_judge: review a proposal (§ 5), draft and confirm clauses (§ 7)
records (any node) records_read at a commit: the import's source (§ 8) records (any node) records_read at a commit: the import's source (§ 8)
messenger (control-node) the router: the operator's ask and its warrant messenger (control-node) the router: the operator's ask and its warrant
@@ -55,9 +63,11 @@ the records module, as the issue tracker's import does.
## 2. What one record holds ## 2. What one record holds
- `number` and `kind` (decision or research), its key together; `title`, `topic`, `status`, `date`, `deciders`; - `number` and `kind` (decision or research; with the third slice also design and glossary entry, § 9), its key
together; `title`, `topic`, `status`, `date`, `deciders`;
- its **reasoning**, in sections: for a decision, context, the options weighed, consequences, how it is checked, - its **reasoning**, in sections: for a decision, context, the options weighed, consequences, how it is checked,
references; for a research effort, its overview and each of its documents by name; markdown, as written; references; for a research effort, its overview and each of its documents by name; for a design, its `to-be`
section and, where something of it runs, its `as-is` section (§ 9); markdown, as written;
- for a decision, its **clauses** (§ 3); - for a decision, its **clauses** (§ 3);
- its **links**, typed, each stored once and read from both ends; - its **links**, typed, each stored once and read from both ends;
- its **versions**: every change is a new version with who made it, when, and why in one line; `history` lists them - its **versions**: every change is a new version with who made it, when, and why in one line; `history` lists them
@@ -190,11 +200,89 @@ field by field and section by section, and the result is written on issue 426.
|---|---|---|---| |---|---|---|---|
| 1 | this document: the module, the import, clauses, answers, `propose`, its own review and acceptance | the acceptance sentence above, live, and a record proposed, reviewed and accepted without a pull request | issue 425 | | 1 | this document: the module, the import, clauses, answers, `propose`, its own review and acceptance | the acceptance sentence above, live, and a record proposed, reviewed and accepted without a pull request | issue 425 |
| 2 | the decision records and research efforts leave this repository; path mentions become "ADR NNNN" and "research NNN"; playbooks 01 and 02 changed | this repository holds no decision record, and `get` answers every one | issue 426 | | 2 | the decision records and research efforts leave this repository; path mentions become "ADR NNNN" and "research NNN"; playbooks 01 and 02 changed | this repository holds no decision record, and `get` answers every one | issue 426 |
| 3 | designs, playbooks and glossary entries as kinds | `holds` answers for a design's decisions and a glossary word | issue 428 | | 3 | designs and the glossary's entries as records, moved in one delivery, and each design's state worked out by the knowledge base (§ 9.1 to § 9.3; ADR 0423, ADR 0424) | `get` answers design 47 with its sections and links, a word's entry answers from the knowledge base, and a design's state follows its issues' deliveries without anyone writing it | issue 428 |
| 4 | meeting notes, drawings and blog posts | not decided; needs its own decision | issue 429 | | 4 | meeting notes, drawings and blog posts | not decided; needs its own decision | issue 429 |
Numbers from the issue tracker, which `reserve` needs, are the tracker's own slice, tracked by issue 424. This Numbers from the issue tracker, which `reserve` needs, are the tracker's own slice, tracked by issue 424. This
repository keeps no copy of the records, generated or otherwise (issue 435, answered by the operator). repository keeps no copy of the records, generated or otherwise (issue 435, answered by the operator). Playbooks
stay in this repository as files: they are how work is done here, not records of what was decided (research 061).
### 9.1 Designs and glossary entries as records
**A design** is a record of the kind `design`, named "design NN" and keeping its number (ADR 0423). Its text is its
sections: the `to-be` section, what is being built, and the `as-is` section, what runs. Each of the as-is documents
becomes the as-is section of the design it describes, never a record of its own: the import matches 3 of the 18 by
name and 15 by topic, and shows the operator the list before it runs. Its owning repositories are its field `code`;
its decisions, and every design it names, are `cites` links. It holds no state written by anyone (§ 9.2). A new
design takes its number from `reserve` and is written with `propose`, and is reviewed as every record is (§ 5).
**A glossary entry** is a record of the kind `glossary entry`, one per entry of the glossary page, kept whole: its
head word or words (an entry may name several with one meaning, as research effort, graduation, hand-off and playbook
do), its domain, its meaning, its *Not:* line with each retired word's scope, and its identifier line. Each row of
the page's homonym table becomes an entry of its own, naming the qualified form each domain uses. The entries replace
the page as the authority on words (ADR 0423, changing ADR 0244).
**The readers change with them.** Everything that read the page or the design folders reads the records instead:
| Reader | Reads, after the move |
|---|---|
| the knowledge base's own rule on a retired word (§ 4) | the *Not:* and identifier lines of the entries, no longer the page through the records module; it judges a design when it is proposed, as it judges a decision |
| hq's word check | the entries' *Not:* and identifier lines, and its uniqueness check (a head word heading two entries, or also retired) over the entries; it covers what stays in this repository, and designs leave its coverage |
| the catalogue's repository check | the retired words of no scope or *(tools)* from the entries; its copied list is retired, so hq has no copy to compare |
| hq's cycle check | the design records, for every design naming its decisions, not files under `03-DESIGN/` |
Each of the four fails, saying so, when the knowledge base does not answer, and never passes on nothing.
**Links become links between records, and the old paths go.** At the import every file link inside a design becomes
a link between records, which the rule on a link to nothing then checks (§ 4). The links from documents that stay in
this repository are rewritten to name the design ("design 47"). No page stays at an old path: afterwards this
repository holds no file under `03-DESIGN/` and no `00-META/glossary.md`.
**It moves in one delivery**, as the records did: the import, the two kinds, the four readers, the rewritten links,
the playbooks' steps that write a design, and the instruction text sessions read, which names the glossary's place.
A reader left behind would pass on nothing, so none moves alone.
### 9.2 A design's state
**The knowledge base says a design's state, and nobody writes it** (ADR 0424): proposed, designed, in progress or
implemented, kept matching what runs. No person, session or document sets it, and `propose` refuses a design that
carries one.
It reads two things. The issues are those the issue tracker holds with a `design` link to the design. Whether one
is done comes from its deliveries: an issue is done when it is resolved and the delivery its fix went out in reached
`delivered`, whether a hotfix alone or a delivery group. The delivery module announces each delivery done on the
bus, and on each announcement the knowledge base works the state of every design that issue links out again.
```
issue tracker ── issues with a design link ──┐
├──▶ knowledge base ── works out ──▶ the design's state
mesh-delivery ── "delivery done", bus ───────┘ (served by get, ask)
```
**The rules.**
| Case | State |
|---|---|
| a decision the design names is not accepted | proposed |
| no linked issue done, or no linked issue at all | designed |
| some linked issues done | in progress |
| every linked issue done | implemented |
Three kinds of issue count apart: an issue closed won't fix is left out; an issue resolved with nothing to deliver,
a fix of documents only, is done when resolved; and a new issue linked to an implemented design makes it in progress
again, until that issue is done. Milestones are not read: a milestone is a group of a design's issues delivered
together, and the state depends only on the issues and their deliveries.
**Imported designs say what they lack.** A design imported with no linked issue shows `no issues linked yet` beside
designed, until an issue is linked to it; the import names each such design by number. Linking their issues is
tracked by issue 428.
### 9.3 Its place beside design 47
The delivery-done announcement this slice reads is design 47's
([47 — Delivery](47-delivery-from-commit-to-delivered.md)): its hotfixes and delivery groups, and each delivery's
end at `delivered`. This slice is an extension after design 47's last milestone. It changes none of design 47's
milestones and nothing of how a delivery runs; it only listens for the announcement.
## 10. How it is checked ## 10. How it is checked
@@ -212,6 +300,26 @@ repository keeps no copy of the records, generated or otherwise (issue 435, answ
| A record written in this repository meanwhile writes its clauses | `records.py`, check `clauses`, on every hq pull request, until the records left this repository (issue 426); since then `records.py`, check `no-records`, refuses any record written here | | A record written in this repository meanwhile writes its clauses | `records.py`, check `clauses`, on every hq pull request, until the records left this repository (issue 426); since then `records.py`, check `no-records`, refuses any record written here |
| Live | the acceptance sentence, asked from a session outside this repository | | Live | the acceptance sentence, asked from a session outside this repository |
**The third slice's acceptance tests** (§ 9.1 to § 9.3; research 062's direction), what its build proves before it
is done:
1. After the import, `knowledge-base.get` of design 47 returns its sections and its links, and every link from a
design resolves to a record: none is refused by the rule on a link to nothing.
2. This repository's main holds no file under `03-DESIGN/` and no `00-META/glossary.md`, and its link check passes
with the 76 links from documents that stay here rewritten.
3. hq's cycle check and word check read the knowledge base, and fail, saying so, when it does not answer.
4. A proposal holding a retired word is refused after the move, the word read from the entries; the entries are
175 and hold 183 head words and 29 retired words, 0 unparsed, both counts checked.
5. In the module's tests, each state rule of § 9.2 gives its state from fixture issues; live, an issue linked to a
design whose hotfix is delivered turns the design implemented within a minute of the delivery being done, and
a new issue linked to it turns it in progress.
6. The import reports every design with no linked issue as `no issues linked yet`, by number.
7. The instruction text sessions read names the glossary's new place.
8. Every entry of the glossary's Config section is filed under the Config domain, and the domains the entries name
are the twelve of ADR 0354.
9. Tests 1 to 8 pass together after the one delivery, run against main and the live knowledge base, with nothing
in this repository, the catalogue or the instruction text pointing at a removed path.
## What it leaves out ## What it leaves out
Semantic or vector search; a browser view; line comments on a proposal (the judgement's findings name the section and Semantic or vector search; a browser view; line comments on a proposal (the judgement's findings name the section and
@@ -1,8 +1,8 @@
--- ---
layer: to-be layer: to-be
status: designed status: in-progress
code: [] code: [mesh-catalog, mesh-controller]
updated: 2026-10-13 updated: 2026-10-17
decisions: decisions:
- "ADR 0316" - "ADR 0316"
- "ADR 0317" - "ADR 0317"
@@ -12,6 +12,8 @@ decisions:
# 56 — The mail client, set up by the mesh # 56 — The mail client, set up by the mesh
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: issue 586, this design's core, is resolved and delivered (hq#336, mesh-catalog#262, mesh-controller#286), and mesh-catalog's betterbird module has every piece named here. Not yet proven live end to end, nor given an as-is: issue 1076.
**On every node that holds a display server, the `betterbird` module installs Betterbird from a pinned **On every node that holds a display server, the `betterbird` module installs Betterbird from a pinned
upstream release, ships the Owl add-on with it through Betterbird's enterprise policy, and declares the upstream release, ships the Owl add-on with it through Betterbird's enterprise policy, and declares the
operator's mail accounts without their credentials. A password or token never passes through the mesh: each operator's mail accounts without their credentials. A password or token never passes through the mesh: each
@@ -1,8 +1,8 @@
--- ---
layer: to-be layer: to-be
status: designed status: in-progress
code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog] code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog]
updated: 2026-10-14 updated: 2026-10-17
decisions: decisions:
- "ADR 0348" - "ADR 0348"
- "ADR 0349" - "ADR 0349"
@@ -61,6 +61,10 @@ both, by the form of its manifest, and the old forms stay accepted until the las
Inside the release this still holds: the media catalogue and the applications kept in their own Inside the release this still holds: the media catalogue and the applications kept in their own
repositories move after it, so the old forms stay accepted until they have (R1). repositories move after it, so the old forms stay accepted until they have (R1).
**Where it stands, 2026-10-17** (the Milestones section below). The packages' branches were merged into
main of mesh-controller, the node-engine (`mesh-host`) and mesh-catalog on 2026-10-16. T6 moved after them, as
planned above, and is resolved. B3 was left out as well, so B3, R1 and R2 are what is left.
## What exists already, measured ## What exists already, measured
Counted 2026-10-14 at mesh-controller `1601cb1`, the node-engine (`mesh-host`) `1a26778`, mesh-sdk `a2d2af0`, mesh-catalog Counted 2026-10-14 at mesh-controller `1601cb1`, the node-engine (`mesh-host`) `1a26778`, mesh-sdk `a2d2af0`, mesh-catalog
@@ -333,6 +337,17 @@ removed code.
amended, the 67 documents that still say "setting" in running prose are reworded so the glossary can amended, the 67 documents that still say "setting" in running prose are reworded so the glossary can
retire the word (F4 left it off the *Not:* line for them), design 49 lists the Config domain, ADR 0348's and 0349's built status is said, and this document's status becomes `implemented`. retire the word (F4 left it off the *Not:* line for them), design 49 lists the Config domain, ADR 0348's and 0349's built status is said, and this document's status becomes `implemented`.
## Milestones
The packages above were built as one delivery, not milestone by milestone; the milestones group them by what
each makes true. Whether a milestone's issues are delivered is read from the issue tracker (playbook 07).
| # | Outcome | Packages | Issues |
|---|---|---|---|
| 1 | Config, provisioning, secrets, bindings, collections and runtime asks run the new way; the old forms are still accepted | every package but T6, B3, R1, R2 | 647, 650 to 679 but 659, 681, 684, 685 |
| 2 | The media catalogue and the applications carry their file text as templates | T6 | 659 |
| 3 | The other providers answer their consumers; the old forms are refused; the record says what runs | B3, R1, R2 | 680, 682, 683 |
## What is deliberately not here ## What is deliberately not here
- **Secrets in a module's own code** wait on issue 569 (a module's tools run as the tool runner's - **Secrets in a module's own code** wait on issue 569 (a module's tools run as the tool runner's
@@ -2,7 +2,7 @@
layer: to-be layer: to-be
status: designed status: designed
code: [mesh-catalog, mesh-media-catalog, mesh-sdk, mesh-tools, mesh-controller, mesh-host, mesh-lab] code: [mesh-catalog, mesh-media-catalog, mesh-sdk, mesh-tools, mesh-controller, mesh-host, mesh-lab]
updated: 2026-10-16 updated: 2026-10-17
decisions: decisions:
- "ADR 0383" - "ADR 0383"
- "ADR 0384" - "ADR 0384"
@@ -12,6 +12,10 @@ decisions:
- "ADR 0388" - "ADR 0388"
- "ADR 0389" - "ADR 0389"
- "ADR 0390" - "ADR 0390"
- "ADR 0411"
- "ADR 0412"
- "ADR 0413"
- "ADR 0414"
--- ---
# 58 — Agent work as the mesh's own # 58 — Agent work as the mesh's own
@@ -21,8 +25,10 @@ bus by a thread seat; a thread handler on an always-on node runs the agent harne
of its own, with the thread's own branch on the forge as its workspace; the agent works through a workspace toolkit of its own, with the thread's own branch on the forge as its workspace; the agent works through a workspace toolkit
and named tools, never a general command runner. Every person, agent and module is an identity held by one identity and named tools, never a general command runner. Every person, agent and module is an identity held by one identity
seat, with a role made of grants; the bus checks each login by its signature, and the tool runner checks each call seat, with a role made of grants; the bus checks each login by its signature, and the tool runner checks each call
against the role, tool by tool, by the effect every tool carries.** against the role, tool by tool, by the effect every tool carries. An identity is also its logins: the mesh makes each
(ADR 0383 to ADR 0390, from research 053.) person's and agent's machine account on every machine it is present on, gives each its own bus password, and runs a
module for an identity only where the module is set to; everything else runs as the operator.**
(ADR 0383 to ADR 0390, from research 053; ADR 0411 to ADR 0414, from research 057.)
**Acceptance.** The operator starts a thread from the terminal client, an agent on an always-on node works it in a **Acceptance.** The operator starts a thread from the terminal client, an agent on an always-on node works it in a
sandbox, asks a question the operator answers in place, and the thread moves to another node and carries on with sandbox, asks a question the operator answers in place, and the thread moves to another node and carries on with
@@ -106,6 +112,95 @@ agent carries a token the identity seat signed for that agent, and the tool runn
refusal names the missing grant; every call is recorded with its identity and thread. A session on an operator's refusal names the missing grant; every call is recorded with its identity and thread. A session on an operator's
machine calls as the operator's person identity or as an agent, never through the machine's tool-runner login. machine calls as the operator's person identity or as an agent, never through the machine's tool-runner login.
### 5.1 An identity on the machines (ADR 0411, ADR 0414)
A person or agent identity also holds a **login name**, the **machines** it is present on and, for a person, the
login name kept on a machine where it differs. The seat starts with two: the operator's person identity and `nox`, the one
agent; every piece of agent work is a thread of `nox` (§2), and a second agent identity is made only when a rule needs
another role. The agent is `nox` everywhere: its login, its forge account (the forge account `nox-agent` renamed,
keys and team kept), Keycloak, mail and the bus. A login or service account the mesh makes for the operator from now on carries the operator's identity name;
the operator's existing logins and forge account are kept as they are. On a machine where agents ran as the
operator's login, that login stays the operator's and `nox` gets a login of its own; nox's bus account is `agent.nox`. `nox` is also the
operator's terminal command (`mesh-cli`), and prose says which is meant.
An identity's login on one machine is its **machine account**. The controller composes the machine account of every
identity present on a machine into that machine's declaration, and the node-engine applies it with its `user`
resource. A login that exists is adopted under its name, uid and home; the mesh never renames or deletes one. An
agent's machine account has its own home and group, no password, no root group and no sudo rule, and the node-engine
judges on every look that it cannot become root without a person, by the ways ADR 0266 §12 to §17 list. No module declares a login for a
person or an agent: claude-code's `agent-account` resource goes, and the catalogue's check refuses a manifest that
declares one. The node record's operator account and agent account are read from the identities, no longer stated
at the controller's terminal or in the mesh-config snapshot; the machine facts `account`, `agent-account` and their
homes keep their names. A machine names an agent account only where the identity seat marks an agent as its agent
account: the control node, marked `nox`, where the guard and the licence stay as they are. The other machines are
marked with none, so the guard stays off there and the operator's sessions keep the mesh's tools, while the login `nox` serves the
modules run for `nox`. Where a person or an agent is present, its kept logins and the agent account mark change only
through a verb of the identity seat that only the operator calls, on an Approve; an assignment never sets the mark,
because an agent may call the verb that changes an assignment (ADR 0266 §4). The verb, and the controller at composition,
refuse an agent whose login on a machine would be `root`, a person's login there or a well-known service account
(ADR 0266 §5). The self-check judges every agent login,
marked or not. The seed: the operator on every machine, with the operator's existing login kept where it differs from the
identity's name; `nox` on every machine, the agent account on the control node.
```
identity seat controller each machine (node-engine)
───────────── ────────── ──────────────────────────
person operator, machines, composes, per machine: the operator's login (adopted)
the login kept on each the machine accounts present nox, root: never (adopted on the control node,
agent nox, its machines the facts account, agent-account made elsewhere)
each identity's bus password ──► its secret, owned by its login, 0600
```
### 5.2 Run for an identity (ADR 0412)
Every module runs as the operator account of its machine, as today, with no setting; running for the operator is that
default run. A manifest may declare that its module can **run for** an agent identity, at assignment, at start or both: serve one identity with that identity's machine account, home and bus password.
An assignment of such a module may name the identity it runs for, and the module may let the one who starts it
choose: claude-code started for the operator is the default run with the operator's configuration, and started for `nox`
it runs for `nox` with the agent's home. A start
goes down only: a module is started for an identity by a process already running as that identity's login, or by the
operator's own login for an agent; an agent never starts one for a person, and the module refuses it, naming who asked
and for whom. A module may be assigned to one machine once per identity it runs for there, each its own process under
its own login, and the controller keys an assignment by machine, module and identity. The first two to declare it are
claude-code and ssh-client. On a machine the identity seat marks with an agent account, claude-code's assignment is
run for that agent (`nox` on the control node), so its licence and home scope stay in the agent's home, and the controller refuses
one there that is not. ssh-client keeps its default assignment for
the operator wherever `nox` is present and gains a second one run for `nox`, which makes the agent's forge key in the
agent's home and closes issue 989; gitea's provider registers that key on the forge account `nox`. A module run for an identity calls as that
identity and gets no module identity of its own (this narrows ADR 0386 §4, which holds for every other assignment). A module with an account of its
own (`runs-as`, ADR 0259 §8) cannot declare it, and a run for an identity with no machine account on that machine is
refused at composition and at start. ADR 0266's guard
stands: on the control node, a session started for the operator gets the operator's configuration and its tool calls are still
refused.
### 5.3 A bus password per identity (ADR 0413)
Each person and agent identity has a bus account of its own (`person.<name>` for the operator, `agent.nox`), whose memberships are made
from its role, and its password is a secret owned by its machine account at mode 0600 on each machine it is present
on, which no other account but root can read. A person's password is placed only on a machine that names an agent
account of its own judged unable to become root, by a verdict at most fifteen minutes old; on the other machines, where agents can still run as the operator,
it is not placed, and the controller says so. A module run for an identity reaches the bus with that password, can do
only what the identity's role allows, and every call is recorded as that identity's. Nox's role never holds an approval
grant, a trusted party's grant or a dangerous tool named in it (ADR 0387 §4 does not apply to it), and the identity seat
refuses one; each dangerous act of nox's is asked one by one. The break-glass agent (§6) keeps its own rules and gets
no login and no bus password from this. This changes ADR 0259 §8, which kept every bus credential out of an agent's account; the rest of §8 stands.
The password lives in today's list of bus accounts and is replaced by a signed login for the same role at the bus
switch (§7, §8).
### 5.4 Traced to GENESIS
Checked on 2026-10-17 (playbook 02, step 1); no conflict was found.
- [Mission](../../00-META/mission.md): agents "hold identity", and there is one kind of participant, some of them
human; persons and agents are held alike, with the same fields, and differ in what their role allows, which the
mission calls modality. "The mesh brokers credentials": the bus password per identity and the service accounts made
from it. "The mesh owns the truth": logins are derived from identities, no longer stated per node. "Dogfood": logins
go through the node-engine's existing resource.
- [Context](../../00-META/context.md): "Identity is mesh-internal", with no directory and no SSO, so the identity seat
holds it and Keycloak only receives accounts; "Nodes are personal" and the mesh is a guest, so existing logins are
adopted, never renamed; human agents are few, often one, so the seat holds two identities and one agent.
- [Effect](../../00-META/effect.md): a human agent's environment is part of the mesh, its login included.
## 6. Effects, asks and break-glass (ADR 0387) ## 6. Effects, asks and break-glass (ADR 0387)
Every seat verb and module tool is one permission and carries one **effect**, set in its module's manifest: Every seat verb and module tool is one permission and carries one **effect**, set in its module's manifest:
@@ -156,12 +251,36 @@ carry them:
`research/053-signed-bus-logins`, loses nothing and leaves every identity on the new bus. `research/053-signed-bus-logins`, loses nothing and leaves every identity on the new bus.
5. The acceptance sentence at the top, end to end. 5. The acceptance sentence at the top, end to end.
From research 057 (ADR 0411 to ADR 0414):
6. The identity seat lists the operator and `nox` with their machines, kept logins and the control node's agent account mark; a
create or a presence change by any caller but the operator's Approve is refused.
7. On every other machine a login `nox` exists; on the control node the login `nox` is adopted with its uid and home unchanged;
on every machine the node-engine judges `nox` unable to become root; a login `nox` given a sudo rule in a test is left
unaltered and the self-check raises `agent-can-become-root` for it, on a machine that marks no agent account too;
the operator's existing logins are
unchanged.
8. No manifest in the catalogue declares a login for a person or an agent; the catalogue's check refuses one.
9. For every machine, the declaration before and after differs, for the modules not set to run for an identity, only
in the machine accounts' `user` resources (now the controller's, and `nox` added on the other machines); claude-code on the control node, run for `nox`, places the same
files in the same home; on the other machines the guard stays off and the operator's sessions keep the mesh's tools.
10. A session started for `nox` on the control node gets the agent's home configuration and one started for the operator the
operator's, with no tool call through; a session as `nox` that starts claude-code for the operator is refused; with
ssh-client run for `nox`, nox's key is on the forge account `nox` and the session pushes a branch over SSH as `nox`.
11. Nox's bus password is owned by `nox` at mode 0600 on every machine nox is present on; a call with it outside
nox's role is refused; a grant test finds no approval grant, trusted party's grant or dangerous tool in nox's
role; the operator's password is placed only on a machine with an agent account judged root-free within fifteen
minutes, and the controller says so for every other.
12. The forge account `nox-agent` is renamed `nox`, keeps every machine's key, stays in the agents' write team, and
every `forge-account` entry names `nox`.
## 11. Milestones ## 11. Milestones
Each milestone is one feature step (playbook 07), usable as soon as its delivery plan is done, and starts when Each milestone is one feature step (playbook 07), usable as soon as its delivery plan is done, and starts when
the previous one's is. What it replaces is in the as-is documents the previous one's is. What it replaces is in the as-is documents
[15 — The agent and its licences](../00-as-is/15-the-agent-and-its-licences.md) and [15 — The agent and its licences](../00-as-is/15-the-agent-and-its-licences.md) and
[16 — Logins on the bus](../00-as-is/16-logins-on-the-bus.md). The issues are opened once the operator confirms [16 — Logins on the bus](../00-as-is/16-logins-on-the-bus.md), and for milestones 11 to 14
[17 — Accounts on a machine](../00-as-is/17-accounts-on-a-machine.md). The issues are opened once the operator confirms
the milestones, and their numbers written here. the milestones, and their numbers written here.
**Confirmed by the operator on 2026-10-16.** One issue per milestone: 1 is issue 878, 2 is 879, 3 is 880, 4 is **Confirmed by the operator on 2026-10-16.** One issue per milestone: 1 is issue 878, 2 is 879, 3 is 880, 4 is
@@ -186,3 +305,18 @@ and the issue tracker takes only registered owners. The building waits until the
The front ends' later parts, the inference seat and the other agent harnesses are tracked by issue 850; the The front ends' later parts, the inference seat and the other agent harnesses are tracked by issue 850; the
seat-sense "role" in older designs by issue 872. seat-sense "role" in older designs by issue 872.
**Milestones 11 to 14, from research 057, confirmed by the operator on 2026-10-17.** One issue per repository
part, opened at the confirmation, so a fixing agent per repository can start when its step does. Milestone 11
waits for nothing; 12 waits for milestone 6.
| # | Outcome, in one sentence | Proves | Repositories and their parts (issues) | Depends on |
|---|---|---|---|---|
| 11 | The agent's forge account is `nox`, with its keys and its place in the agents' write team kept | test 12 | mesh-catalog gitea: the rename, keys registered on `nox`, every `forge-account` entry (993) | — |
| 12 | The mesh makes the operator's logins and nox's on every machine from the identities, and judges nox unable to become root everywhere | tests 6 to 9 | identity module: logins, machines, kept logins, the agent account mark, the operator's verb (994); the node-engine (`mesh-host`): existing logins adopted, every agent login judged (995); mesh-controller: machine accounts composed, node accounts read from the identities, the terminal lines retired (996); mesh-catalog: claude-code declares no login, the catalogue's check refuses one (997) | 6 |
| 13 | The operator and nox each reach the bus with their own password, held to their role; the operator's only where no agent runs as the operator | test 11 | mesh-controller: the operator's and nox's bus accounts, secrets at 0600, the fifteen-minute verdict (999); identity module: nox's role refuses approvals and dangerous tools (998); until milestone 7 builds the asks, a dangerous act of nox's is refused, not asked | 12 |
| 14 | The operator starts a claude-code session for nox or for themself, and nox pushes over SSH with its own key | test 10 | mesh-controller: run for, assignments keyed by identity, the refusals (1001); mesh-sdk: calls as the identity, a start goes down only (1002); claude-code: started for nox or for the operator (1003); ssh-client and gitea: nox's own key, closing issue 989 (1004) | 11, 13 |
The agent's and the operator's accounts in Keycloak and mail (ADR 0414 §2 and §3) follow milestone 13 and are
tracked by issue 1000, which needs a design section first. A second agent identity is made only when a rule
needs another role (ADR 0414 §1); no milestone makes one.
@@ -0,0 +1,308 @@
---
layer: to-be
status: in-progress
code: [mesh-catalog]
updated: 2026-10-20
decisions:
- "ADR 0440"
- "ADR 0378"
- "ADR 0379"
- "ADR 0359"
- "ADR 0361"
- "ADR 0138"
- "ADR 0167"
- "ADR 0439"
- "ADR 0201"
- "ADR 0436"
- "ADR 0458"
- "ADR 0459"
- "ADR 0472"
- "ADR 0461"
- "ADR 0462"
- "ADR 0278"
---
# 59 — The mail web client
**The operator's mail, every account of it, in a browser on the mesh's own machines. A module of its own,
`mesh-mail-web`, holds the seat `mail-webclient`; it renders pages on the server in Go and reaches mail only through
`mail-store`'s verbs, under a caller the operator grants account by account. Its route reaches the private network
only, under its internal name, and the operator signs in with Keycloak. A mail written there goes out only on the
operator's Approve, as every mail the mesh sends does.**
(ADR 0440, building on ADR 0378 §5 and ADR 0379 §2.)
**Then the model reads the mail for the operator.** A module of its own, `mesh-mail-reader`, holds the seat
`mail-reading`: it reads each new mail in batches, keeps a mail entry for it (its mail group, one line, its mark),
tells the operator on Telegram of the mail that matters in a mail signal, shows a scam warning on the page only, and
learns from the operator's corrections as reading lessons; it never changes the mailbox and keeps no mail text.
The pop-up for every new mail retires once mail signals
work. Later, once the mesh's threads exist, the operator asks questions about their mail in a mail conversation, a
thread with a mail context. Betterbird is removed once mail reading works on the laptop and the workstation.
(Research 066; ADR 0458 to 0462. The words are the glossary's Mail domain.)
**Acceptance.** In a browser on the laptop and on the workstation, the operator signs in, browses, searches and reads the mail of
every account `mesh-mail` holds, opens an attachment, and writes a mail that goes out on their Approve on Telegram;
the same page is not reachable from a machine outside the private network. A new mail that matters reaches the
operator's phone as one mail signal and stands at the top of the mail page in its mail group; a mail that does not
matter gives no pop-up and no mail signal anywhere.
## 1. The parts
```
browser (laptop, workstation) ── https://mail.<node>.internal ──▶ route proxy (internal reach only, the mesh's own certificate)
│
▼
mesh-mail-web (seat mail-webclient, once for the mesh)
│ sign-in: oidc-client ──▶ Keycloak
│ pages rendered on the server, a mail shown as text
▼ over the bus, as caller <machine>.mesh-mail-web
mail-store (mesh-mail): mail_accounts · mail_search · mail_read
mail_send ──▶ the operator's Approve (Telegram)
mail_send_status
mesh-mail's account bucket (ADR 0439 §2): folders and their counts
```
- **`mesh-mail-web`** is built from `mesh-mail`'s source, as `mesh-mail-notifier` is, and is assigned to the machine
that runs `mesh-mail` (the control-node today). It keeps no mail: no database, nothing on disk (ADR 0440 §1, §4).
- **`mesh-mail`** gains its account bucket (ADR 0439 §2, issue 1255) and a caller check on `mail_send_status`
(ADR 0440 §7, issue 1261), both released with `mesh-mail-web`. It
answers `mesh-mail-web` only for the accounts its trusted setting `grants` gives `mesh-mail-web` (ADR 0359 §4,
ADR 0378 §13). `mail_send_status` becomes part of `mail-store`'s protocol (ADR 0440 §7).
- **The browser never reaches the bus.** The bus has no WebSocket listener or sign-in for a person yet (research 036,
gap G1), so every page is HTML from `mesh-mail-web` (ADR 0440 §5).
## 2. Reachable from the private network only (ADR 0440 §2)
The operator's rule of 2026-10-15: a web UI for mail is reached only over the mesh's private network, never from
outside. Two things hold it:
1. **Its route's reach is `internal`** (ADR 0138 §7). Reach is a setting of the machine a module runs on, keyed by
port, and with none the route proxy composes a public name as well as the internal one. So `mesh-mail-web`'s
`reach` is set to `internal` on its machine before it is assigned there. The route proxy then composes only
`mail.<node>.internal`, serves it only to the mesh's own machines (ADR 0167 §6), and certifies it with the mesh's
own authority.
2. **The module answers only its internal name.** A request whose host is not `mail.<node>.internal` is answered
404, so a public name composed by mistake shows nothing.
Measured on 2026-10-19: the laptop and the workstation hold the mesh's authority (`mesh-internal-ca.crt`) as a trust anchor, and
Firefox on both reads the system's trust through p11-kit, so the page opens there without a warning. Away from the
private network (the laptop travelling without its tunnel) the page is unreachable; Betterbird stays the offline client
until it is removed (milestone 3, §11).
## 3. Signing in (ADR 0440 §3)
`mesh-mail-web` requires `oidc-client` from Keycloak, as grafana does, and uses the authorisation-code flow. It answers
no page, attachment or send to a browser that has not signed in, and lets in only the identities its trusted setting
`operators` names (nobody by default). The session cookie is `Secure`, `HttpOnly` and `SameSite=Strict` and ends
after 12 hours. Every form that changes something (today only the send) carries a token bound to the session.
The operator chose this on 2026-10-19: without it, any process on any mesh machine, agents included, could read every
account through the page and get around `grants`.
## 4. The pages
| page | what it shows | how |
|---|---|---|
| accounts | every granted account, its state, its folders and their counts | `mail_accounts`; the folders from `mesh-mail`'s account bucket, only of an account `mail_accounts` answered (the bucket's read is wider than the grant, ADR 0440 §4) |
| folder | one folder's mails, newest first, in pages of 50 | `mail_search` with the folder; the next page asks for mails before the last one shown |
| search | mails across every granted account by words, sender, recipient, date, attachment | `mail_search`'s filters (issues 1228-1232) |
| mail | one mail: its headers, its body as escaped text, its attachments | `mail_read` |
| attachment | one attachment, as a download | `mail_read` with the attachment's key |
| write | from (a granted account), to, subject, body; after sending, the Approve's state | `mail_send`, then `mail_send_status` |
- **A mail is shown as text** (ADR 0440 §5): `mail_read` turns HTML into text, the page escapes it, and nothing
remote is loaded. An attachment answers `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`.
Every page carries a content security policy that allows no script or style from elsewhere.
- **A send is an ask** (ADR 0361 §2): the page says the ask is raised, then follows `mail_send_status` (ADR 0436
§12's states, now part of `mail-store`'s protocol, ADR 0440 §7) until a final one: sent, failed, refused, not
proven, declined, expired or unknown. The operator's Approve comes on Telegram, as for every mail the mesh sends.
- **The seat's verbs** (ADR 0378 §5): `open` answers the page's address, and with an account and a mail that mail's
page; `status` answers whether it serves and which accounts it is granted.
## 5. One list per account, reading and asking to send alike (ADR 0472)
`mesh-mail`'s trusted setting `grants` stays one list of callers per account, giving both reading (that account's
`MAIL` events, `mail_accounts`, `mail_search`, `mail_read`) and asking to send (`mail_send`, `mail_send_status`):
the operator's Approve on every send is the guard (ADR 0472 §1, §2, which supersedes ADR 0460's split; issue 1227
will not be done). `mesh-mail-reader` is named in `grants` for every account after its delivery, on the operator's
Approve, and asks to send nothing: its manifest invokes no send verb (ADR 0472 §3). Who may call `mail-reading`'s
own verbs is its own trusted setting `callers`, `mesh-mail-web` by default (ADR 0459 §2).
## 6. Mail reading (ADR 0459)
```
mail-store (mesh-mail) ── MAIL: mail.<account>.message.added ──▶ mesh-mail-reader (seat mail-reading, once for the mesh)
▲ │ its own database: mail entries, mail groups,
└──────────── mail_read (named in grants) ────────────────┤ reading lessons; never a body
│ batches of ≤ 10 mails ──▶ claude_code_judge (within ≤ 40 s)
│ claude_code_judged
├──▶ operator-channel.tell (a mail signal, §7)
▼
mesh-mail-web ── entries · groups · correct · lessons · undo · status ──▶ the mail page
```
- **One module, one seat.** `mesh-mail-reader`, built from `mesh-mail`'s source like `mesh-mail-web`, claims
`mail-reading` (declared in `mesh-mail`'s manifest beside the three seats of ADR 0379 §4) and runs once, on the
machine that runs `mesh-mail`. It hears `added` events under its own `runs-as` account and the operator's reading
grant (ADR 0378 §13), and moves, flags, deletes and sends nothing.
- **A mail entry per mail, never the body.** Its own database (`postgres-database`) keeps each mail's mail group,
a one-line summary, its mark (*matters*, *scam warning* or neither) and why, and a due date where the mail gives
one, beside the mail groups and the reading lessons.
- **Batches.** One judgement reads at most 10 mails, and every part of its question has a bound, so it never
passes 55 KiB of the judgement's 64 KiB: the framing that a mail is data and never an instruction, at most 4 KiB;
the mail groups, at most 4 KiB, the largest first; the reading lessons that apply, at most 40 within 12 KiB,
sender and mail group first, then the newest, under the framing that a reading lesson outranks anything a mail
says; then each mail, at most 3.5 KiB (its headers at most 512 bytes, its text cut to its first 3 KiB), a mail in
a Junk folder said to be so. A batch asks for up to 40 s with `within` and collects with
`claude_code_judged` (built under issue 508, recorded by ADR 0459 §14). Research 066 measured 1.9 to 3.1 s per
mail, so a batch of 10 takes about 19 to 31 s. A batch starts at least 160 s after the previous one started, so
an issue tracker's check that waits behind a batch on the same machine ends within 115 s, inside its two minutes
(ADR 0278 §2), which research 066's 15 mails and 120 s back to back would not; mail reading then reads up to 225
mails an hour (ADR 0459 §15).
Batches stay judgements; whether they become threads is issue 1417.
- **Reading lessons.** When the operator changes a mail's mark or mail group on the page, `mesh-mail-web` calls
`correct` and the correction is kept at once as a reading lesson (headers, the change, when; never body text),
with no further Approve: it is the signed-in operator's own act. The page lists the reading lessons, undoes one
(later mail reading only), and shows reading lessons per 100 read mails per week, which answers whether mail
reading learns the operator's way.
- **The page.** Above the classical list it shows the mail that matters at the top, the mail groups, and the scam
warnings in a place of their own, each with why; the classical list (accounts, folders, search, one mail) stays
and always opens. `mesh-mail-web` reads these through `mail-reading`'s verbs and still keeps no mail state
(ADR 0378 §5 and ADR 0440 §4 as ADR 0459 §13 changes them); it writes the operator's corrections with
`correct` and `undo`.
- **It fails loudly.** Its own health check is unhealthy when a judgement is refused, times out or answers out of
shape, with the cause and since when; the controller raises its condition, the page shows the cause above the
classical list instead of mail groups, and no mail signal is sent. On recovery the mail that arrived meanwhile is
read in arrival order from its durable consumer on `MAIL` (kept 7 days). A warning before a licence runs out is
issue 1409.
## 7. Mail signals, and the pop-up retired (ADR 0461)
- **One tell per batch.** A batch that marks mail as mattering sends one `operator-channel.tell`: the headline
"1 mail matters" or "N mails matter", a first line naming the mail with the nearest due date (sender's name,
summary, due date), a second naming the next and, with more than two, ending "and N-2 more on the mail page".
When the hourly cap of 10 refuses one, its mails stay at the top of the page and the next mail signal counts
them.
- **Words only.** No link, mail address, host name or path: the router refuses a message holding any, and the mail
page opens only on the mesh's own machines. The reader checks its words with `messenger_check` first and, refused,
sends the safe form "N mails matter: open the mail page".
- **At once, at any hour**; the phone's own quiet hours decide. **Never** for a scam warning, and never while mail
reading is unhealthy.
- **The pop-up retires.** Once mail signals pass acceptance test 13 on the real accounts, `mesh-mail-notifier` is
unassigned from the laptop and the workstation on the operator's Approve and leaves `grants`; `mail-notifier`
stays declared and unheld, and nothing takes it over (ADR 0379 §3, §7). Betterbird's own pop-ups stay off by its
setting until it is removed (ADR 0379 §6). Issue 1418; issue 801 closes as replaced.
## 8. The mail conversation (ADR 0462)
A mail conversation is a thread (ADR 0383), run by the thread handler (ADR 0384), with a mail context: the framing
that a mail is data, the tools `mail_search` and `mail_read` only, under a caller of the thread's own named in `grants`
(ADR 0472 §4; issue 1419), the operator's real senders and folder names, and the reading lessons. The operator writes a mail
question on the mail page; each answer names the mails it found, and a question with no answer says so.
`mesh-mail-web` creates and follows the thread over the bus on its server side; the page loads one script from its
own origin and talks only to it (content security policy: script from that origin, `connect-src 'self'`), so the
browser never reaches the bus. The thread keeps the text of each mail it read for good, as every thread is kept (ADR
0383 §3), which the operator accepted; mail reading keeps none. In the same milestone `mail_search` matches folder
names without regard to case and describes its three `-any` switches as true or false only. It is built after design
58's milestone 3 (threads, issue 880) and once issue 1419's caller is decided.
## 9. What stays outside
Each is an issue with an owner:
- a reply, cc, bcc and attachments on a sent mail: issue 1257 (it needs `mail_send` to take them first);
- a page that shows new mail without a reload, from ADR 0439's buckets: issue 1258;
- drafting a mail, the rest of issue 1259 (its triage is mail reading, its search the mail conversation), and writing
to the mailbox: issue 730;
- a task list for the operator: issue 1396; the search faults found by research 066: issues 1400 and 1401;
- the work account: a later decision of its own (ADR 0359 §7).
## 10. The build's acceptance tests
Tests 1 to 9 are milestone 1's; tests 10 to 17 are research 066's eight, in its order.
1. A request without a session, for a page, an attachment or a send, is redirected to sign-in; an identity not in
`operators` is answered 403; a send form without its token is answered 403; the session cookie is `Secure`,
`HttpOnly` and `SameSite=Strict` and ends 12 hours after sign-in.
2. A request whose host is not the internal name is answered 404.
3. Against a stand-in `mail-store` and a filled account bucket on the real bus, the pages call only
`mail_accounts`, `mail_search`, `mail_read`, `mail_send` and `mail_send_status`, list folders from the bucket,
page by date, and a send shows its state; the manifest declares no database and no writable directory.
4. A mail whose body holds `<script>` and a remote image is shown as text; an attachment is a download with
`nosniff`; every page carries a content security policy allowing only its own origin.
5. After a mail is indexed, `mesh-mail`'s account bucket changes only the keys that describe it, within ADR 0439's
bounds (issue 1255's own tests).
6. The declaration for its machine composes `mesh-mail-web`'s route with its internal name only (`mesh-controller.plan`).
7. `mail_send_status` answers each state of ADR 0436 §12, and `unknown` for an id it does not know and for another
caller's send (a unit test of `mesh-mail`, issue 1261).
8. The seat's verbs: `open` answers the page's address, and with an account and a mail that mail's page; `status`
answers whether it serves and the accounts it is granted (a unit test of `mesh-mail-web`, issue 1262).
9. The acceptance sentence at the top, live: in a browser on the laptop and on the workstation, for every account.
10. A new mail on each account has a mail entry, in a mail group, within 5 minutes of its mail event; a test searches
`mesh-mail-reader`'s database for a known body line and finds none.
11. The reader asks no `mail_send` for any account, and no mail is moved, flagged or deleted by it.
12. (Milestone 4.) On the mail page, a mail question by a person, by a time range and by "what needs a reply" finds
its target mails, research 066's three failed kinds included; a question with no answer says so; the conversation
is a thread readable through the thread seat; and the browser makes no request but to the mail page's own origin.
13. A batch with one mail that matters sends exactly one `tell` naming it, with no URL, mail address or host name in
its words, and the mail is at the top of the page; a sender whose name carries a host name still gives a
mail signal, in the safe form.
14. Research 066's 20 steering mails, read again, mark no injected words as mattering, and every phishing decoy that
is marked is a scam warning, which sends no `tell`.
15. A reading lesson given on a real mail changes how the next mail of that sender is noted; undone, it no longer
does; the page lists it.
16. With the judgement refused (the reader's licence binding removed in the integration test), the condition is
raised with its cause, the page shows it over the classical list, no `tell` is sent, and after recovery the mail
that arrived meanwhile is read in arrival order.
17. With `mesh-mail-notifier` unassigned, a new mail that does not matter shows nothing on the laptop or the
workstation, and one that matters gives one mail signal.
Tests 1 to 4 are one integration test that starts the module whole against the real bus (playbook 07 step 3 item 4);
tests 5 and 7 are `mesh-mail`'s own and test 8 `mesh-mail-web`'s, unit tests; test 6 is read before the module is
assigned; test 9 is proven live after the delivery.
Tests 10's and 11's checks, 13 (with a stand-in router) and 16 are integration tests that start
`mesh-mail-reader` whole against the real bus and store, with stand-ins for the seats it asks (issues 1431, 1524); the batch's budget, the seat's verbs and the
mail signal's words are unit tests; tests 10, 13, 14, 15 and 17 are proven live after milestone 2's delivery, test 13
before `mesh-mail-notifier` is unassigned; test 12 with milestone 4.
## 11. Milestones
| # | Outcome, in one sentence | Proves | Repositories and their parts | Depends on |
|---|---|---|---|---|
| 1 | In a browser on the laptop and the workstation, the operator signs in and browses, searches and reads every account's mail with its attachments, and writes a mail that goes out on their Approve | tests 1 to 9 | mesh-catalog: `mesh-mail`'s account bucket (issue 1255, ADR 0439 §2); the module `mesh-mail-web` (manifest, sign-in, the pages, the send, the seat's verbs, the integration test) | — |
| 2 | Each new mail is read into a mail group on the mail page; the mail that matters reaches the operator's phone as one mail signal, a scam warning shows on the page only, the operator's corrections become reading lessons, and the pop-up for every new mail is gone | tests 10, 11, 13 to 17 | mesh-catalog: the seat `mail-reading` declared in `mesh-mail`'s manifest; the module `mesh-mail-reader` (batches, mail entries, reading lessons, mail signals, health, the integration test); `mesh-mail-web`'s mail groups, mail signals, scam warnings and reading lessons; `mesh-mail-notifier` unassigned (issue 1418) | 1 |
| 3 | Betterbird is gone from the laptop and the workstation, its profile and mail kept in the home | ADR 0379 §2 | mesh-catalog: Betterbird unassigned on the operator's Approve, then the `betterbird` module removed from the catalogue (issue 806) | 2 |
| 4 | On the mail page the operator asks questions about their mail and gets the mails that answer them, in a mail conversation kept as a thread | test 12 | mesh-catalog: `mesh-mail-web`'s mail conversation as a client of the thread seat; the thread's mail tools under a caller of their own (issue 1419); `mesh-mail`'s folder case and `-any` switches | 2 (the reading lessons); design 58's milestone 3 (issue 880) |
**Milestone 1, confirmed by the operator on 2026-10-19, is delivered;** its live proof on the laptop and the
workstation (a search, an attachment, one send on the Approve) is still the operator's. Its issues, one repository
part each, in their order: 1255 (`mesh-mail`'s account bucket), 1261 (`mail_send_status` answers only its caller),
1262 (the module), 1263 (sign-in), 1264 (accounts, folders and mails), 1265 (search), 1266 (a mail and its
attachments), 1267 (writing a mail), 1268 (the integration test). After its delivery four values were set, each on
the operator's Approve: `mesh-mail-web`'s `reach` on its machine (`internal`), its assignment there, its
`operators`, and its caller in `mesh-mail`'s `grants` for every account.
**Milestones 2 to 4, confirmed by the operator on 2026-10-20.** Their issues, one repository part each, in their
order (each depends on the one before unless said):
- Milestone 2 (its order is said for each): 1426 (the seat `mail-reading` and the module `mesh-mail-reader`), 1517
(its database; after 1426), 1518 (hearing new mail, catching up in arrival order; after 1517), 1519 (its health;
after 1517), 1427 (a batch's question within its bounds; after 1426), 1520 (running batches; after 1427, 1518 and
1519), 1521 (the verbs `entries`, `groups`, `status`; after 1520), 1428 (`correct`, `lessons`, `undo`; after 1521),
1429 (a mail signal's words; after 1427), 1522 (sending it; after 1429 and 1520), 1430 (what the mail page shows;
after 1428, 1521 and 1522), 1523 (corrections and reading lessons on the page; after 1430 and 1428), 1431 (the
reader's integration test; after 1430 and 1522), 1524 (the integration test of failing loudly; after 1431 and
1523), 1418 (`mesh-mail-notifier` unassigned; after 1524, the delivery and test 13 live). Issues 1517 to 1524 were
split from 1426 to 1431 at the prepare step; issue 1227 (the grants split) will not be done (ADR 0472).
- Milestone 3: 806 (Betterbird removed; after 1418).
- Milestone 4: 1433 (`mail_search`'s folder case and `-any` switches; depends on none), 1419 (the thread's
caller; a decision first, after design 58's milestone 3, issue 880), 1432 (the mail conversation;
after 1431, 1419 and issue 880).
Milestone 2 is one release of `mesh-catalog`: the new seat, the new module and the page change ship together, and
`mesh-mail-reader` is named in `grants` after that delivery, on the operator's Approve; `mesh-mail-notifier` is unassigned only after test 13 passes live. Milestone 3 is issue 806.
ADR 0379 §2's bar for removing Betterbird is milestone 1's; keeping Betterbird until mail reading (milestone 2) works,
and not until the mail conversation, is the lead's proposal, for the operator to confirm with the milestones
(playbook 04, step 5). Milestone 4 waits for threads and for the decision that names its thread's caller (issue 1419, which needs a decision first).
+214
View File
@@ -0,0 +1,214 @@
---
layer: to-be
status: in-progress
code: [mesh-catalog, mesh-sdk, mesh-controller]
updated: 2026-10-21
decisions:
- "ADR 0464"
- "ADR 0467"
- "ADR 0468"
- "ADR 0474"
- "ADR 0475"
---
# 60 — The brother module
**One module serves the operator's Brother multifunction device: its scanner and its printer are one machine on
the network, so one module, `brother`, holds both seats, `scanner` and `printer`. It scans the glass or the
document feeder into a PDF, keeps every scan in its own bucket and announces it on the bus, prints a document in
one call, reads the device's state and its admin pages, restarts it, and changes the settings on the admin pages
where a mistake cannot lock the operator out or open the device. Only the module's device process ever holds the
device's admin password or the bucket's key.**
(ADR 0464, ADR 0467 and ADR 0468 are in force; ADR 0468 is being built, milestones F1 to F3 below. Proposed, and
not in force: ADR 0475 for the feeder, multi-page and several-document scans; ADR 0474 for
reading every page, configuring, the refused pages and the restart.)
**Acceptance.** The operator, or an agent through the mesh MCP server, scans a document from the glass and finds it
cropped in the scan folder; scans five sheets from the feeder on both sides and finds one PDF of ten pages; prints
that PDF from a file in one call; reads any admin page (ADR 0474, as proposed); changes a setting on an allowed page and is told the old and the new
value; and is refused, with the reason, when asking to change the admin password or the network settings.
## 1. The parts
```
caller (an agent, a module, the operator)
│ scanner.scan · scanner.status · scanner.list · scanner.document
│ printer.status · printer.print · printer.upload · printer.settings
│ printer.configure · printer.restart
▼
brother — tool side (served by the node's tool runner, as the operator account)
│ holds no credential
│ status reads: IPP and eSCL, no login ───────────────────────────▶ the device
│ every other verb: an ask on the work queue device-asks
▼
brother — device process (an account and a bus account of its own)
│ holds the admin password and the bucket's key, and nothing else does
├── eSCL ─────────────────────────────▶ the scanner
├── IPP ─────────────────────────────▶ the printer
├── HTTPS to the pinned certificate ───▶ the admin pages
├── S3 ─────────────────────────────▶ the module's bucket (minio)
└── its answer in the state device-answers ──▶ back to the tool side
```
The two seats are held once for the mesh. A seat names the role, so a later scanner or printer module claims the
same seat and no caller changes (ADR 0467 §2).
The module has two halves. The **tool side** answers the verbs. It runs as the node's operator account, so the
scans it files in the scan folder are the operator's, and it never opens a credential. The **device process** runs
as an account of the module's own with a bus account of its own (ADR 0436 §1), holds no seat and serves no tool. It
alone reads the admin password and the bucket's key, and it alone does the work that needs either. A verb that
needs a credential puts an ask on a work queue and reads the device process's answer back from the module's state,
waiting at most the verb's bound (ADR 0468 §1). The reads that need no credential, the device's state over IPP and
the scanner's over eSCL, stay on the tool side.
The admin password is the operator's own, given once at the operator's desk and kept in the vault; the mesh never
generates or rotates it (ADR 0467 §4). The device process speaks HTTPS to the admin pages and trusts only the
certificate whose SHA-256 fingerprint is pinned in the module's config; any other certificate is refused in the
handshake, before a password is sent (ADR 0467 §3). The module's pin tests check it: a certificate with another
fingerprint is refused and nothing is sent.
## 2. Scanning
What ADR 0464 decided is the glass, one page, one PDF page. ADR 0475 (proposed) widens it to the feeder, to several
documents on the glass and to documents of several scans; until it is accepted, only ADR 0464 holds.
- **The glass.** One page over the whole glass. The document lies on the lid, which is a white of its own, so the
module measures the lid where no document lies and cuts the document out of it with a small margin. When it finds
no document, or one that fills the glass, it keeps the whole page and says it did not crop (ADR 0464 §3).
- **Several documents on the glass** (ADR 0475, proposed). Documents lying apart on the glass are each cut out and
filed as a PDF of their own.
- **One document of several glass scans** (ADR 0475, proposed). A caller starts a document, adds a glass scan to it
as often as it needs, and finishes it, and the pages become one PDF. A document left unfinished is filed whole
after 30 minutes, so no scan is lost.
- **The feeder** (ADR 0475, proposed). The feeder scans whole sheets, one side or both, and every page of the run
goes into one PDF, in the order the scanner gave them: one object in the bucket and one announcement, with its
page count. A feeder page is never cropped. A run that is cut off before its end is an error, never a short PDF
said as success; a unit test checks it.
- **A long run** (ADR 0475, proposed). A feeder run may take longer than a call through the mesh MCP server may
wait. Such a call answers that the scan started, with an id, and the caller reads its outcome by that id. A scan
that was announced is always filed.
- **The real resolution.** The module reads the resolution from the image it receives, never from what it asked: a
scanner may answer a request for 300 dpi with 200 dpi (ADR 0464 §2).
**A lesson for every device the mesh speaks to.** The scanner ignored every scan setting at first: whatever the
request asked, it ran its own default job. The cause was the request's body: the device ignores a body that holds a
line break, and the same request written on one line is honoured. A device's protocol implementation may be
stricter, or stranger, than its standard; when a device ignores a well-formed request, the form of the request
(line breaks, whitespace, the order of fields) is the first suspect, and a capture of what a working client sends
is the evidence.
## 3. Printing
A caller prints in one call: `printer.print` takes exactly one of a key in the bucket (`object`), a file on any
machine of the mesh (`path` with `node`) or a web link (`url`), with the copies, the sides and the paper (ADR 0468
§5). A file on a machine is sent into the bucket by that machine's node-files `upload`, which refuses the secret
places a read refuses, and printed by its key (ADR 0468 §9). A web link is fetched by the device process, at most
60 s, 100 MiB and 5 redirects, and never from an address inside the mesh or a private network (ADR 0468 §6). A
document that is neither PDF nor JPEG is turned into a PDF by the seat `document-converter`, which only-office holds
(ADR 0468 §7). The device process then sends the document to the printer in a format the printer itself lists as
accepted, read before the job.
**No document crosses the bus.** The bus carries a document's key, size, SHA-256 and type; the bytes move over
HTTP between a party and the object store, on a signed link of at most 10 minutes for one object and one method
(ADR 0468 §3). The device process signs links for the module's own steps with its binding's key; every other
party asks `object-store.link`, which minio serves and signs with a key limited to that bucket, for a caller that
brother's `shares` names (ADR 0468 §8). The parts verbs `printer.upload` and `scanner.document` still serve until
issue 1569 removes them.
## 4. The bucket and the events
Every scan is kept once in the module's own bucket under its SHA-256, and announced with the event
`brother.scan.added` once it is stored: its key, size, page count, size in millimetres, resolution and colour mode,
never the image. The scan folder is one consumer of the stored scans: once the scan is stored and announced, the tool side
reads it by a link the device process signed and files it there as the operator; the device process never writes
into the operator's home (ADR 0468 §4). The module deletes nothing from the bucket; the operator removes what they no longer want
(ADR 0468 §2, §4).
The other events are `brother.printer.restarted`, when the device answers again after a restart (ADR 0467 §6), and
`brother.printer.configured`, one for each setting changed (section 5).
## 5. Settings: read and configure
**Reading.** `printer.settings` reads one admin page: the device process logs in, reads the page and logs out. It
answers the page's forms with each field's current value (a radio group's checked button, a checkbox's state, a
select's chosen option) and the values each field allows; the page's links and its hidden fields only on ask. A
password field's value is never given. ADR 0467 §5 decides the read for the pages of the scan and restriction
settings; ADR 0474 (proposed) widens it to every page, and the menu page's links are the map of the rest.
**Configuring** (ADR 0474, proposed). `printer.configure` takes one page and the fields to set on it, by name. The device process logs in
and reads the page as the printer serves it, with a fresh form token. It checks each named field: the field exists
in one form of the page, and its value is allowed for its type (one of a radio group's or a select's values, on or
off for a checkbox, a text within its maximum length). A password, hidden or file field cannot be set. When any
check fails, nothing is sent and the answer names each refused field with what it allows. Otherwise the process
posts the form as the page served it: its own hidden fields, the named fields at their new values, and every other
field at its current value, so nothing the caller did not name changes. It reads the page back, answers each changed
field's old and new value, and says a field the printer did not take as not taken. Then it logs out.
```
caller ──configure {page, set}──▶ tool side ──ask──▶ device process
login ─▶ read page (form, token, current values)
check every named field ──✗──▶ refused, nothing sent
post the whole form ─▶ read back ─▶ logout
◀──answer: old → new per field
brother.printer.configured, one per field
told to the operator: page, fields, who asked
```
**What may not be changed** (ADR 0474, proposed). Some pages are refused, because a mistake on them locks the operator out or opens the
device to the network: the login password, the user restriction and setting lock, the reset menu, the firmware
update and its setup, the security settings, every wired, wireless and Wi-Fi Direct network page, the protocol and
interface pages, and every security page (certificates, TLS, IPsec, the IP and MAC filters). The list is in the
module's code and its tests, not in a config value, so no call can widen it; it is checked against the page asked
and against the address the form posts to; a unit test refuses each listed page. The operator changes such a page in a browser at the device itself.
**Who asked, and who is told** (ADR 0474, proposed). Every change names who asked, the caller the bus proved for the call; a call with no
proven caller is refused. Each changed field is announced as `brother.printer.configured` with the page, the field,
the old and the new value, who asked and when, and the operator is told through the operator channel, once per call.
No password is ever in an answer, an event, a message or a log; a unit test reads each of them for it.
**Restarting.** `printer.restart` is decided by ADR 0467 §5; that it goes through the device's reboot page, posting
that page's own form, is ADR 0474's, proposed. It is the one call that posts to the reboot page, which configure refuses. It is
refused while the device holds a print or scan job, and waits at most five minutes for the printer to answer again
(ADR 0467 §5).
**The module's own defaults stay config.** The scan resolution, colour mode and cropping are the module's config
values, held by the config module and changed through its `config` verb (ADR 0348); they are not trusted values.
Configure changes the device's pages and never the module's config (ADR 0474, proposed), which a unit test checks.
## 6. Health
The device process's health is its unit. The module's health, as ADR 0475 proposes, compares the certificates the
printer serves, without a login, with the pinned one, and is unhealthy, naming the new fingerprint, when the printer
serves one that is not pinned; a printer that does not answer is healthy with a note, and with no printer address set
the module waits for that setting. Until that is accepted, a changed certificate shows only when a verb is refused. A certificate the device
serves that is not pinned is refused, and the refusal names the served and the pinned fingerprint. A device serves
a new certificate after a reset, a firmware update or a certificate's renewal, so a refusal is either that or
another machine answering in its place; only the operator can tell which, and pins the new fingerprint as a config
value. A firmware that moves an admin page is said as a page not found, never as success: a unit test replays a
missing page and expects the error.
## 7. Still open
- **The parts verbs.** `printer.upload` and `scanner.document` carry a document over the bus in parts; issue 1569
removes them once their callers use links, and revises ADR 0475 to ADR 0468.
- **Several printers.** The `printer` and `scanner` seats take one holder; issue 1602 asks for the decision that lets
a second printer join.
- **Restart is not built yet.** `printer.restart` is decided (ADR 0467 §5) and follows `printer.configure`.
- **Not built yet either:** several documents on the glass, a document of several glass scans and the long run's id
(ADR 0475, proposed). The print of a machine's file is ADR 0468's, built in milestone F3.
## Milestones
The milestones build ADR 0468, feature `files-through-the-store`. They are cut by release order: the catalogue's
merge check reads manifests with the controller the mesh runs, and the controller vendors the SDK, so a new
manifest field ships SDK first, then the controller and every other strict reader of manifests (the build modules,
the delivery's indexer), then the catalogue (playbook 07, *a new manifest word*).
| # | Outcome | Proves | Repository | Issues |
|---|---|---|---|---|
| F1 | A manifest may name who else may read or write its bucket, and every module has one helper that signs and streams a store link. Usable only by F2 and F3. | ADR 0468 §3, §8 (the field) | mesh-sdk | 1591 (`shares` on a requires of s3-bucket), 1592 (link signing and streaming) |
| F2 | The controller accepts `shares` and writes it into the consumer's grant. Usable only by F3. | ADR 0468 §8 | mesh-controller | 1593 (take the SDK); depends on F1 |
| F2b | The build modules read a manifest that names `shares`, so a module carrying it builds. Usable only by F3. | ADR 0468 §8 | mesh-catalog | 1657 (mesh-build-go and mesh-build-node take the SDK); depends on F1; the delivery's indexer takes the same SDK in design 47's own work |
| F3 | The operator prints a stored scan, a file on any machine (a .docx on a laptop among them) or a web link in one call, and scans land in the scan folder from the store; no document these verbs move crosses the bus (the parts verbs stay until issue 1569). Depends on F2 and F2b. | the acceptance above; ADR 0468 §1–§9 | mesh-catalog | minio: 1594 (a signing key per bucket), 1516 (`object-store.link`, retiring the root-signed link); mesh-files: 1597 (upload, download); only-office: 1595 (`document-converter.convert`), 1596 (the converter process); brother: 1598 (print by key or a machine's file), 1599 (print a web link), 1600 (convert before printing), 1601 (the scan folder from the store) |
Order inside F3: minio, then mesh-files and only-office, then brother; one pull request of `mesh-catalog`.
@@ -0,0 +1,75 @@
---
layer: to-be
status: designed
code: [mesh-catalog]
updated: 2026-10-21
decisions:
- "ADR 0479"
- "ADR 0468"
- "ADR 0440"
---
# 61 — The print and scan page
**A web page where the people the operator names sign in with Keycloak, print a PDF or a photo and scan from the glass,
and where only the operator manages the printer. It is a module of its own, `print-and-scan`, that
knows only the seats `printer`, `scanner` and `object-store`, so any module holding the printer and scanner seats
works with it unchanged.** (ADR 0479, accepted 2026-10-21.)
**Acceptance.** A person named in `users`, on a phone that is not a machine of the mesh, signs in, uploads a PDF
and finds it printed, and scans the glass and downloads the PDF; that person is refused the printer's settings and
its restart, which a person named in `managers` may use; a browser that has not signed in sees nothing but the
sign-in.
## 1. The parts
```
a browser (a phone or laptop, inside the mesh or not)
│ HTTPS, through the route proxy's public name, signed in with Keycloak
▼
print-and-scan — renders the pages on the server, holds a bucket of its own
│ printer.status · printer.print · printer.settings · printer.configure · printer.restart
│ scanner.scan · scanner.status · object-store.link
▼
the holders of the printer, scanner and object-store seats ── signed links ──▶ the object store
```
The page claims the seat `print-and-scan`, held once for the mesh, with the verbs `open` and `status`. It calls
only seat verbs, never a module's own tool, and knows no device's protocol (ADR 0479 §1).
## 2. Who may do what
The page requires a Keycloak client, as the mail web client does (ADR 0440 §3). Two trusted settings name the
identities let in, by the ID token's user name: `users` print and scan, `managers` also manage. Both are empty by
default. A browser that has not signed in is sent to the sign-in; an identity in neither setting gets a refusal page
that names no holder, document or setting (ADR 0479 §2, §3).
Its route is public, so a person reaches it from a device that is not a machine of the mesh; this is where it
differs from the mail web client, whose route is internal only (ADR 0479 §3).
## 3. Documents
A document goes from the browser to the page over HTTPS, and from the page into its own bucket by a link the object
store signs; it never crosses the bus, the page keeps none on its disk, an upload is bounded at 100 MiB (refused by its declared length, or cut at the bound, which stores nothing), and a signed link never reaches a browser, because the links name the store's
internal name (ADR 0468 §3, ADR 0479 §4). The page takes PDF and JPEG only, judged by their first bytes; any other
type is refused before anything is stored, and converting other types waits on issue 1650 (ADR 0479 §4). To print,
the page names its own bucket and the key; its bucket shares reading with the printer seat, and the printer's holder
asks the object store for the link itself, a process of the holder matching the seat (ADR 0479 §5). A scan is read from the scanner's bucket, which shares reading with the
page, and streamed to the browser as a download; the page offers the feeder and both sides only once a decision in
force gives them to the scanner seat's scan (ADR 0479 §6).
## 4. Managing the printer
Every user sees the printer's state. Reading and changing its settings and restarting it are the printer seat's
verbs, for managers only (ADR 0479 §7). The printer's holder sees the page as its caller, so the page records the
person: before each such call it announces who asked, what and when, and after it the outcome. Restarting is ADR
0467's, and so is reading the settings, which ADR 0474 proposes to widen; changing them is proposed in ADR 0474,
which is accepted and built before the page (ADR 0479 §9).
## 5. Still open
- **Several printers.** The page serves the seats' one holder; issue 1602 asks for the decision that lets a second
printer join, after which the page lets the user choose (ADR 0479 §8).
- **Other types.** A Word, spreadsheet or web document waits on issue 1650's decision (ADR 0479 §4).
- **Order.** The page is built after design 60's milestones F1 to F3 and after ADR 0474 is accepted and built
(ADR 0479 §9).
@@ -0,0 +1,98 @@
---
layer: to-be
status: proposed
code: [okioki]
updated: 2026-10-21
decisions:
- "ADR 0481"
- "ADR 0440"
- "ADR 0479"
---
# 62 — The bookkeeping portal
**A web page on the private network where the operator, signed in with Keycloak, sees the business's income and
expenses as the bookkeeping service holds them now, and what is still to do per quarter. The `okioki` module that
talks to the service moves, whole, from the laptop to the home server, and the page, `okioki-web`, runs beside it.
Bookkeeping data stays on hardware the operator owns.** (ADR 0481, accepted.)
**Acceptance.** The operator, signed in on a machine of the mesh, opens the page's internal name and sees income and
expenses per month and per quarter, the same period a year before beside them, including an invoice made in the
bookkeeping service minutes before; sees the to-do of a quarter, with a quarter the accountant has submitted shown
as locked; and a browser that has not signed in, or an identity not named in `operators`, sees nothing but the
sign-in or a refusal.
## 1. The parts
```
the operator's browser (a machine of the mesh)
│ HTTP through the route proxy's internal name, signed in with Keycloak
▼
okioki-web — renders the pages on the server, keeps no bookkeeping data
│ over the bus (NATS): bookkeeping.summary · bookkeeping.todo · bookkeeping.documents · bookkeeping.transactions · bookkeeping.document
▼
okioki — the one session with the bookkeeping service, on the home server
│ HTTPS, the service's own web API, as its web app calls it
▼
the bookkeeping service (OkiOki)
```
`okioki` claims the seat `bookkeeping`, held once for the mesh, and serves the verbs the page calls beside the tools
it has (`okioki_todo`, `okioki_link`, …). `okioki-web` claims `bookkeeping-web` with `open` and `status`, and calls
only `bookkeeping` verbs, over the bus, never a module's own tool, as the print and scan page calls only seats (ADR 0479 §1).
`okioki-web` opens no connection to the bookkeeping service and holds none of its secrets or settings: only `okioki`
holds the service's password and session (ADR 0481 §1, §7). Both are Go, and both live in the `okioki` repository.
## 2. Where it runs
Both modules run on the home server, which is always on and is the operator's own hardware (ADR 0481 §1). The
laptop sleeps and travels, so a page there would be away whenever the laptop is. `okioki` runs once: the service
password is sealed on the home server for `okioki` alone, given once at its desk prompt, and the laptop's `okioki` is retired on
the operator's Approve once the home server's answers.
Uploading no longer reads a folder on the laptop. `okioki_upload` takes a scan from the scanner's bucket by its key,
or a mail attachment by its account, message and name, and reads it over the bus (ADR 0481 §3); a scan made on any
machine then reaches the service without being copied between machines.
## 3. Who may see it
The home server's route `reach` is set to `internal` before the page is assigned there, so the route proxy composes
only the internal name, and the page answers 404 to any other host (ADR 0440 §2, ADR 0481 §6). It requires
`oidc-client` from Keycloak, as the mail web client does (ADR 0440 §3). A trusted setting `operators` names the identities let in by the ID token's user name; it is empty by
default, so nobody is let in until the operator names themself. Nothing about the page is public.
## 4. What it shows
- **Overview.** Income (outgoing invoices) and expenses (incoming invoices and receipts, refunds subtracted), excluding
VAT, per month and per quarter, the same period a year before beside it; wages and money in and out of the business
account from the bank, by the operator's own tags. `bookkeeping.summary` computes it in `okioki` from the service's
documents and transactions, so the page does no bookkeeping arithmetic of its own.
- **To-do.** Open payments and documents per quarter, as `okioki_todo` reads them.
- **Documents and transactions.** Searchable lists for a period; a document opens as the file the service keeps,
streamed through `bookkeeping.document` over the bus in numbered parts, as `scanner.document` answers (part, parts,
size, SHA-256, data): `okioki` fetches the file from the service, and the page never follows the service's own link.
The page only reads: it calls no verb that changes the service, and keeps no copy (ADR 0481 §4). Keeping the history in a database of the mesh's own is the own
bookkeeping research (issue 1457), and a decision of its own.
## 5. What the accountant has done is never undone
Every tool of `okioki` that changes the service, whoever calls it, reads the booking or document first and refuses
one the accountant has reviewed, approved for sending or booked, naming it and why, and sends nothing (issue 1608,
ADR 0481 §5). The page shows such items as locked.
## 6. In what order it is built
1. **`okioki` on the home server**: the seat `bookkeeping` with its read verbs, uploads from the scanner's bucket and
from mail, the accountant guard (1608); assigned to the home server, its password given at its desk prompt, the
laptop's retired on the operator's Approve.
2. **The read-only page**: `okioki-web` with the overview, the to-do and the lists, behind Keycloak.
3. **Changes from the page** (linking, tagging, marking paid, closing) are not part of this design: they need a decision
of their own (ADR 0481 §4), and would go through the same guarded tools.
## 7. Still open
- **The route proxy on the home server** did not answer its tools on 2026-10-21; whether it serves an internal name
there is checked before step 2.
- **The guard's fields**: which of the service's fields an accountant's submission sets is read from the service
before 1608 is built.
+4
View File
@@ -58,6 +58,10 @@ document is written and this one's status becomes `implemented`.
| [`56-the-mail-client-set-up-by-the-mesh.md`](56-the-mail-client-set-up-by-the-mesh.md) | **Designed.** On every node with a display server, the `betterbird` module installs Betterbird from a pinned upstream release mirrored into the artifact store, ships the Owl add-on pinned and installed by Betterbird's enterprise policy, and declares the operator's mail accounts in a trusted setting without credentials; each account signs in once at a person's first use, and one the profile already holds is adopted by its address | ADR 0316, ADR 0317 | | [`56-the-mail-client-set-up-by-the-mesh.md`](56-the-mail-client-set-up-by-the-mesh.md) | **Designed.** On every node with a display server, the `betterbird` module installs Betterbird from a pinned upstream release mirrored into the artifact store, ships the Owl add-on pinned and installed by Betterbird's enterprise policy, and declares the operator's mail accounts in a trusted setting without credentials; each account signs in once at a person's first use, and one the profile already holds is adopted by its address | ADR 0316, ADR 0317 |
| [`57-building-config-and-provisioning.md`](57-building-config-and-provisioning.md) | **Designed.** The work of ADR 0348 and ADR 0349 as 37 packages, each an issue with its proof: foundations, template files, config in its own module, the provisioner and its client, secrets in the vault, bindings from providers, collections, a module's runtime readers, and removing the old forms; at most five worked at once | ADR 0348, ADR 0349 | | [`57-building-config-and-provisioning.md`](57-building-config-and-provisioning.md) | **Designed.** The work of ADR 0348 and ADR 0349 as 37 packages, each an issue with its proof: foundations, template files, config in its own module, the provisioner and its client, secrets in the vault, bindings from providers, collections, a module's runtime readers, and removing the old forms; at most five worked at once | ADR 0348, ADR 0349 |
| [`58-agent-work-as-the-meshs-own.md`](58-agent-work-as-the-meshs-own.md) | **Designed.** Agent work as the mesh's own: a thread seat keeps every thread whole on the bus; a thread handler runs the harness headless in a sandbox per session, the workspace on the thread's own forge branch; a workspace toolkit and named tools, no general command runner; one identity seat for persons, agents and modules, roles of grants, every tool an effect, signed bus logins, and the switch to them one identity at a time | ADR 0383, ADR 0384, ADR 0385, ADR 0386, ADR 0387, ADR 0388, ADR 0389, ADR 0390 | | [`58-agent-work-as-the-meshs-own.md`](58-agent-work-as-the-meshs-own.md) | **Designed.** Agent work as the mesh's own: a thread seat keeps every thread whole on the bus; a thread handler runs the harness headless in a sandbox per session, the workspace on the thread's own forge branch; a workspace toolkit and named tools, no general command runner; one identity seat for persons, agents and modules, roles of grants, every tool an effect, signed bus logins, and the switch to them one identity at a time | ADR 0383, ADR 0384, ADR 0385, ADR 0386, ADR 0387, ADR 0388, ADR 0389, ADR 0390 |
| [`59-the-mail-web-client.md`](59-the-mail-web-client.md) | **In progress.** The operator's mail, every account, in a browser on the mesh's own machines: a module of its own, `mesh-mail-web`, holds `mail-webclient`, renders pages on the server and reads and sends only through `mail-store`'s verbs under a caller granted per account; its route reaches the private network only, the operator signs in with Keycloak, and a mail goes out only on the operator's Approve; then Betterbird is removed | ADR 0440, ADR 0378, ADR 0379 |
| [`60-the-brother-module.md`](60-the-brother-module.md) | **In progress.** One module, `brother`, holds the `scanner` and `printer` seats for the Brother multifunction device: scans from the glass (cropped) or the feeder (one PDF), keeps each scan in its bucket and announces it, prints in one call, reads the admin pages and changes settings on those where a mistake cannot lock the operator out, each change recorded and told; only its device process holds the credentials | ADR 0467, ADR 0464, ADR 0468, ADR 0474 |
| [`61-the-print-and-scan-page.md`](61-the-print-and-scan-page.md) | **Designed.** A web page, behind a Keycloak sign-in and on a public name, where the people the operator names print a PDF or a photo and scan the glass, and only managers manage the printer: a module of its own, `print-and-scan`, that calls only the printer, scanner and object-store seats, so any holder of those seats works with it; documents go from the browser to the page and by signed links to the store, never over the bus | ADR 0479, ADR 0468, ADR 0440 |
| [`62-the-bookkeeping-portal.md`](62-the-bookkeeping-portal.md) | **Proposed.** A web page on the private network, behind the operator's Keycloak sign-in, showing the business's income, expenses and to-do as the bookkeeping service holds them now; `okioki` moves whole to the home server and the page `okioki-web` runs beside it, calling only the `bookkeeping` seat; nothing the accountant reviewed or submitted is ever changed | ADR 0481, ADR 0440, ADR 0479 |
## Not yet written ## Not yet written
+5 -2
View File
@@ -10,7 +10,7 @@ Before changing anything here, read the playbooks in
amendment, issues, build handoff, constitution sync) is documented there, and agents operate amendment, issues, build handoff, constitution sync) is documented there, and agents operate
through them. Thin skills in `.claude/skills/` wrap these playbooks for invocation through them. Thin skills in `.claude/skills/` wrap these playbooks for invocation
(`hq-new-research`, `hq-graduate`, `hq-new-issue`, `hq-diagnose`, `hq-amend-design`, (`hq-new-research`, `hq-graduate`, `hq-new-issue`, `hq-diagnose`, `hq-amend-design`,
`hq-handoff`, `hq-sync-constitution`, `hq-status`); each defers to its playbook as `hq-handoff`, `hq-sync-constitution`, `hq-status`, `hq-cleanup`); each defers to its playbook as
authoritative and adds only the mechanical scaffolding. authoritative and adds only the mechanical scaffolding.
## The development cycle ## The development cycle
@@ -55,7 +55,10 @@ in running prose; a quotation keeps its words.
## Ground rules ## Ground rules
- **Markdown only** — but for the checks in `00-META/checks/` and `merge-check.sh` at the root, which - **Markdown only** — but for the checks in `00-META/checks/` and `merge-check.sh` at the root, which
runs them on every pull request as its `mesh/repo-check` (ADR 0238). runs them on every pull request as its `mesh/repo-check` (ADR 0238), and
`00-META/process/playbooks.html`, a drawn overview of the playbooks kept for a later docs site (the operator,
2026-10-19); a change to a playbook's steps updates it in the same pull request. Nothing checks that
yet: issue 1222 asks for the check.
No new top-level folders without explicit confirmation. No new top-level folders without explicit confirmation.
- **Status lives in YAML frontmatter** — on design docs (`layer`, `status`, `code`, `updated`, - **Status lives in YAML frontmatter** — on design docs (`layer`, `status`, `code`, `updated`,
and `decisions` as "ADR NNNN"). A decision record's and a research effort's status live in the knowledge and `decisions` as "ADR NNNN"). A decision record's and a research effort's status live in the knowledge
+1 -1
View File
@@ -40,7 +40,7 @@ Implementation lives in the code repositories — see
## Rules ## Rules
- Markdown only. - Markdown only, but for the checks and one drawn overview of the playbooks (`00-META/process/playbooks.html`).
- No new top-level folders without explicit confirmation. - No new top-level folders without explicit confirmation.
- Knowledge flows `GENESIS → RESEARCH → DESIGN`. Research graduates into design only after - Knowledge flows `GENESIS → RESEARCH → DESIGN`. Research graduates into design only after
analysis against GENESIS confirms alignment, and only through a recorded decision. analysis against GENESIS confirms alignment, and only through a recorded decision.