Compare commits

...
Author SHA1 Message Date
jschoubben 37b7bed247 Design 42 zfs-owns-zfs: A-46, the zfs findings in the state list the conditions keeper reads 2026-11-13 03:25:14 +01:00
jschoubben 8bd11f7532 Design 42 zfs-owns-zfs: A-45, every long-running unit declares its health
mesh/delivery merged: source.main moved to novox/hq@bba961f9fd86
mesh/check being carried to 8bd11f7532e2 on bba961f9fd86 | from a5eae7aa0a68
2026-11-13 02:00:15 +01:00
jschoubben a7831db516 Design 42 zfs-owns-zfs: A-43 no wait in a hand act, A-44 the stream-publish capability 2026-11-13 01:51:17 +01:00
jschoubben ca142d4b98 Design 42 zfs-owns-zfs: A-42, a tools bundle publishes onto its own events stream by name 2026-11-13 01:46:09 +01:00
jschoubben 230dc73bc1 Design 42 zfs-owns-zfs: A-41, the keeper publishes on its events stream's subject 2026-11-13 01:29:19 +01:00
jschoubben f7d066fb32 Design 42 zfs-owns-zfs: A-39 and A-40, met in develop 2026-11-13 01:10:19 +01:00
jschoubben 0f9ab08e2d Design 42 zfs-owns-zfs: A-38, a process's bus names what its module asks for in requires 2026-11-13 01:01:16 +01:00
jschoubben 53c407350f Design 42 zfs-owns-zfs: A-36 corrects A-30's remove, A-37 the root processes reach the bus through a keeper 2026-11-13 01:00:37 +01:00
jschoubben 4001fd3515 Design 42 zfs-owns-zfs: A-33 to A-35, met in develop 2026-11-13 00:53:58 +01:00
jschoubben be36341681 Design 42 zfs-owns-zfs: A-32 corrects A-10, findings in today's state form 2026-11-13 00:53:58 +01:00
jschoubben fec5bc42dc Design 42 zfs-owns-zfs: A-31, the run reads the ledger through its module's tool 2026-11-13 00:53:58 +01:00
jschoubben 27fd5ff860 Design 42 zfs-owns-zfs: A-30, the snapshots ledger in the ledger's own forms 2026-11-13 00:53:58 +01:00
mesh-tools a5eae7aa0a Merge pull request 'Design 42 zfs-owns-zfs: /etc/hostid by the apply, and the lead's answers A-1 to A-26 (issue 1979)' (#599) from design/42-zfs-hostid into main 2026-11-12 23:53:39 +00:00
jschoubben 4cb9efadae Design 42 zfs-owns-zfs: A-27 to A-29, met in develop
mesh/delivery merged: source.main moved to novox/hq@a5eae7aa0a68
mesh/check being carried to 4cb9efadae24 on a5eae7aa0a68 | from 283363d5cf3a
2026-11-13 00:28:08 +01:00
jschoubben 2c129789ff Design 42 zfs-owns-zfs: A-26, a module declares what another resource needs before it
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 2c129789ffe1 on 283363d5cf3a | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 46s on 4…
2026-11-13 00:19:30 +01:00
jschoubben 8c98c27a5e Design 42 zfs-owns-zfs: A-25, the form search finds ${config-json} 2026-11-13 00:09:44 +01:00
mesh-tools 283363d5cf Merge pull request 'Design 47 G: lead answers A-321 and A-322, the watchman follows wants by seat' (#598) from design/47-G-lead-answers-25 into main 2026-11-12 23:07:47 +00:00
jschoubben 435fe73a0f Design 42 zfs-owns-zfs: the lead's answers to the prepare's 24 questions, each against the principles 2026-11-13 00:04:41 +01:00
jschoubben 04de5c3b30 Design 42 zfs-owns-zfs: the apply writes /etc/hostid and reads it back, as ADR 0679 version 17 says
No template writes the four binary bytes of /etc/hostid, so it is no resource
and the composer cannot see a second writer: the apply guards it by the pool's
label, reads it back, is unhealthy on a mismatch, and keeps the file on leave.
2026-11-12 23:59:15 +01:00
jschoubben 78fedf86aa Answer A-322: the watchman takes seat events per seat, the branches follower takes registration-changed
mesh/delivery merged: source.main moved to novox/hq@283363d5cf3a
mesh/check being carried to 78fedf86aa81 on 283363d5cf3a | from e97647cd1805
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 23:46:10 +01:00
jschoubben cc4da43b3c Answer A-321: the watchman awaits a followed state by its seat and keeps seeded keys until the holder is read
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 23:41:03 +01:00
mesh-tools e97647cd18 Merge pull request 'Design 47 G: A-319 and A-320, the check runs unit and integration tests only' (#597) from design/47-G-lead-answers-24 into main 2026-11-12 22:37:17 +00:00
mesh-tools b9cd0bc492 Merge pull request 'Design 42: the zfs module owns a node's ZFS and changes nothing that is not set (ADR 0679, issue 1979)' (#596) from design/42-zfs-owns-zfs into main 2026-11-12 22:34:20 +00:00
jschoubben 06fd6f26b1 Test 36's end-to-end proof belongs to the test lab, not to a live node
mesh/delivery merged: source.main moved to novox/hq@e97647cd1805
mesh/check being carried to 06fd6f26b1bf on e97647cd1805 | from b9cd0bc492ac
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 23:21:35 +01:00
jschoubben dec17874ab Test 36 is proven by each module's integration test and one rehearsal; no check starts real builds
The operator, 2026-11-12: an integration test mocks every external dependency and tests the internal wiring.
Withdraws section 637 and A-315 (A-320).

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 23:21:12 +01:00
jschoubben 224348ff4d Design 42 zfs-owns-zfs: answer review round 1 against ADR 0679 version 11
mesh/delivery merged: source.main moved to novox/hq@b9cd0bc492ac
mesh/check being carried to 224348ff4d69 on b9cd0bc492ac | from 9ebf6e58f0d7
Cites the record's final clauses, drops the catalog check and every
write of the package's files, and writes the lead's answers as design
lines: a run that never waits on a scrub, waits ended by events,
ZFS snapshots kept in the ledger before any removal, hand acts as unit
instances, subjects per kind, the follower's findings, the readers of
the new manifest words and one node-engine integration test.
2026-11-12 23:20:51 +01:00
jschoubben 4d7fc66110 Answer A-319: entry-again also asks the shadow's entries again
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 23:13:12 +01:00
mesh-tools 9ebf6e58f0 Merge pull request 'Design 47 G: lead answer A-318, what keeps its module form in G' (#595) from design/47-G-lead-answers-23 into main 2026-11-12 22:12:37 +00:00
jschoubben 071192e586 Answer A-318: what keeps its module form in G, listed
mesh/delivery merged: source.main moved to novox/hq@9ebf6e58f0d7
mesh/check checking 071192e586b1 on 9ebf6e58f0d7
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 23:08:51 +01:00
jschoubben f26c8afc20 Design 42 zfs-owns-zfs: schedule the run in list form, so nothing waits on the calendar fix
Today's node-engine writes a cron step as an invalid calendar but a
minute list as a valid one, so the scheduled run declares
0,15,30,45 and the milestone no longer waits on another session's fix.
2026-11-12 23:02:13 +01:00
jschoubben 0ea81e0e96 Design 42 zfs-owns-zfs: answer the open question in the node-engine, name issue 1979
The lead answered that a resource following a set value and one line
per list entry are built in the node-engine in this milestone, so the
module's files, units and handlers are placed only once their value is
set, as ADR 0679 holds. The cron-to-calendar fix leaves the milestone
and stays an ordering edge the scheduled run waits on.
2026-11-12 22:57:56 +01:00
mesh-tools 1de7bc38c8 Merge pull request 'Design 47 G: lead answer A-316, a candidate carries the parts of the modules it adds' (#594) from design/47-G-lead-answers-22 into main 2026-11-12 21:55:25 +00:00
jschoubben 5c766b7c8c Answer A-317: the assigner's main process alone writes a proposal's candidates
mesh/delivery merged: source.main moved to novox/hq@1de7bc38c8d9
mesh/check checking 5c766b7c8ce2 on 1de7bc38c8d9
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:54:24 +01:00
jschoubben 294a30b82b A-316: the assigner's seat defines the two properties itself, held to the want's by a fixture
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 294a30b82b7a on aef65c2f1e7a
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:46:38 +01:00
jschoubben f6cb279eea Answer A-316: a candidate carries the parts of the modules it adds
mesh/delivery closed: a newer head of the same pull request
mesh/check checking f6cb279eeaec on aef65c2f1e7a
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:45:27 +01:00
mesh-tools aef65c2f1e Merge pull request 'Design 47 G: lead answer A-313, over-bound step answers in the steps state's own objects store' (#593) from design/47-G-lead-answers-21 into main 2026-11-12 21:41:15 +00:00
jschoubben 6772904668 Design 42: the zfs module owns a machine's ZFS and changes nothing unset (ADR 0679)
Adds row 12 and a section to the main file, and the milestone part
zfs-owns-zfs with numbered lines and its three prerequisites, so a
builder takes ownership of ZFS's packages, files, units and one pool's
devices, properties and scheduled ZFS snapshots without a choice of
its own. Leaves one design question open (a resource that follows a
value only once it is set). Adds ZFS snapshot to the glossary beside
the two snapshots it already names.
2026-11-12 22:32:54 +01:00
jschoubben fdac1264ac Answer A-315: test 36 takes the builds of before G from the mesh and starts them as every module starts
mesh/delivery merged: source.main moved to novox/hq@aef65c2f1e7a
mesh/check checking fdac1264acd0 on aef65c2f1e7a
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:29:02 +01:00
jschoubben 3acb50433a Answer A-314: the build modules' steps state moves into requires
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 3acb50433aaf on 0bf9465cbab6
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:28:07 +01:00
jschoubben a6d455f59c Answer A-313: over-bound step answers live in the steps state's own objects store
mesh/delivery closed: a newer head of the same pull request
mesh/check checking a6d455f59cfb on 0bf9465cbab6
A-309 named an age option the objects template does not have.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:26:49 +01:00
mesh-tools 0bf9465cba Merge pull request 'Design 47 G: lead answers A-306 to A-309 from review round 2' (#592) from design/47-G-lead-answers-20 into main 2026-11-12 21:25:58 +00:00
jschoubben aad0eafa79 Use the glossary's words in A-311
mesh/delivery merged: source.main moved to novox/hq@0bf9465cbab6
mesh/check checking aad0eafa793e on 0bf9465cbab6
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:20:55 +01:00
jschoubben 5744cc0472 Answer A-311 and A-312, and give A-310's shape
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 5744cc047254 on 4eba1f451be9
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:20:34 +01:00
jschoubben 47bb7409be Answer A-310: a compose request's manifest names its fingerprint beside its digest
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 47bb7409be46 on 4eba1f451be9
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:19:36 +01:00
jschoubben 383f21ed49 Answer A-309: a build module's outputs objects live as long as their step keys
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 383f21ed4906 on 4eba1f451be9
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:03:26 +01:00
jschoubben 1b9c63e947 Answer A-307 and A-308: the node-engine gains no uses, and its watch of the indexer's modules goes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:02:57 +01:00
jschoubben edb7030102 Answer A-306: a module G creates is held by the test budget, not by section 626
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 22:00:16 +01:00
mesh-tools 4eba1f451b Merge pull request 'Design 47 G: lead answer A-304, the checker puts a phase's asks at once' (#591) from design/47-G-lead-answers-19 into main 2026-11-12 20:51:13 +00:00
jschoubben dce214b7f5 Answer A-305: the pipeline runner's health is its unit, like its siblings'
mesh/delivery merged: source.main moved to novox/hq@4eba1f451be9
mesh/check checking dce214b7f566 on 4eba1f451be9
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 21:47:56 +01:00
jschoubben a0147f94af Answer A-304: the checker puts a phase's asks at once and holds no step
mesh/delivery closed: a newer head of the same pull request
mesh/check checking a0147f94af01 on a70316b51236
Try 4 and 5 ran their steps one at a time because the checker waited on each ask before putting the next.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 21:35:48 +01:00
mesh-tools a70316b512 Merge pull request 'Design 47 G: correct lead answer A-303; playbook 07: the lead reads what an answer names first' (#590) from design/47-G-lead-answers-18 into main 2026-11-12 19:36:39 +00:00
jschoubben 26faa9981f Playbook 07: the lead reads what an answer names before it answers
mesh/delivery merged: source.main moved to novox/hq@a70316b51236
mesh/check being carried to 26faa9981fab on a70316b51236 | from ea3b790f1dc2
Three lead answers in one day named a structure from memory and needed a correction once a developer read the code.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:32:42 +01:00
jschoubben a4f9d43037 Correct A-303: the reach's watches use the seat form, only the stuck key takes the real bucket
mesh/delivery closed: a newer head of the same pull request
mesh/check checking a4f9d43037e0 on ea3b790f1dc2
The view's kept reach holds no bucket name, so no kept entry depends on the holder's real bucket.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:31:31 +01:00
mesh-tools ea3b790f1d Merge pull request 'Design 47 G: the lead's answers A-298 to A-303' (#589) from design/47-G-lead-answers-17 into main 2026-11-12 19:07:45 +00:00
jschoubben 9a14624b69 Answer A-303: seat-form pictures; the stuck key and reach history name the holder's real bucket at run time
mesh/delivery merged: source.main moved to novox/hq@ea3b790f1dc2
mesh/check being carried to 9a14624b6926 on ea3b790f1dc2 | from 023a355cdb2c
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:00:56 +01:00
jschoubben d02f00bce7 Answer A-302: the indexer follows assignments through FollowSeat too; the holder loop goes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:00:56 +01:00
jschoubben 480a857635 Answer A-301: the view's answers tool is the SDK's first-view tool with its callers list
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:00:56 +01:00
jschoubben 4fa5891697 Correct A-300: the view's tools keep assigner_read_wants, since a first view does not read by key or history
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:00:56 +01:00
jschoubben f522c2114d Answer A-300: the view's tools read the wants through the holder's first-view tool
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:00:56 +01:00
jschoubben 5c98c86d70 Answer A-299: the view's answers take one key form per asks kind; the kinds are seven
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:00:56 +01:00
jschoubben b75c04f038 Answer A-298: one shared follower of the seat-holders view in the catalog seat's client
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 20:00:56 +01:00
mesh-tools 023a355cdb Merge pull request 'Design 47 G: §717 (the views are named by their seats) and the lead's answers A-294 to A-297' (#588) from design/47-G-lead-answers-16 into main 2026-11-12 17:59:25 +00:00
jschoubben b03f5193b8 Design 47 G §717: a follower hears its seat's holder from the seat-holders view
mesh/delivery merged: source.main moved to novox/hq@023a355cdb2c
mesh/check being carried to b03f5193b8c4 on 023a355cdb2c | from 7247b92fd6b1
A seat's view is said on its holder's module subject, so a follower must know
the holder; asking for it every 5 seconds was polling and kept a holder that
moved. It now follows the seat-holders view as A-295's holder function does,
reached from the catalog half's claim, and re-follows when the holder changes.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 18:54:41 +01:00
jschoubben 80863372dc Design 47 G §717: the status view's and the assigner's views are named by their seats
Developer J27 met, building A-210, a status view that claims no seat and an
assigner holding two seats, both followed by the teller by module name. ADR
0540 §1 already names the seat mesh-delivery-view; G defines it with its one
view, puts the assigner's asks and wants on mesh-wants, and has a follower find
the holder in the background so no start stops for it.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 18:54:41 +01:00
jschoubben 01fdde3701 Answer A-297: the runner's step ask id takes the usual form, repeatable per placement
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 18:54:41 +01:00
jschoubben ef76908e0d Answer A-296: seat forms in the manifests, own buckets from the credential's module
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 18:54:41 +01:00
jschoubben eed5be9244 Answer A-295: a callers list names a seat, resolved from the seat-holders view the SDK follows
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 18:54:41 +01:00
jschoubben 12c9c1efcf Answer A-294: mesh-wants' step keeps no token; step-progress names the caller its definition lists
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 18:54:41 +01:00
mesh-tools 7247b92fd6 Merge pull request 'Design 50: the own resolver's file waits longer than its pass over a silent mesh resolver (ADR 0678, issue 1748)' (#587) from design/50-resolver-wait into main 2026-11-12 17:51:40 +00:00
jschoubben ce0287dd2d Keep design 50 to what ADR 0678 asks, and hold the wait at 3 s per try
mesh/delivery merged: source.main moved to novox/hq@7247b92fd6b1
mesh/check being carried to ce0287dd2d87 on 7247b92fd6b1 | from 0d04a16560e4
A restart of resolved and a timed ask at the own resolver answered in 44-46 ms
with the first mesh resolver silent, so no read catches the pass today: the
value stays ADR 0678's until a read of resolved's server switches exists, and
the proof is the Go build steps at the next silence. The phases table and the
as-built notes go back to main: a to-be design states no live state.
2026-11-12 18:06:41 +01:00
jschoubben 067de23181 Give the own resolver's file a wait longer than its pass over a silent mesh resolver
ADR 0678: on a machine holding node-resolver the file names the own resolver
alone, so a program gave up after about 2 s while resolved still waited on a
silent first mesh resolver (issue 1748). Design 50 section 2 now gives the
file timeout:3 attempts:2 and how the value follows the measured pass; the
new milestone part resolver-wait holds the lines that build it, putting the
design in the shape of a main file and milestone parts.
2026-11-12 18:05:05 +01:00
mesh-tools 0d04a16560 Merge pull request 'Design 47 G: the lead's answer A-293' (#586) from design/47-G-lead-answers-15 into main 2026-11-12 16:24:57 +00:00
jschoubben 7c533965bf Amend A-293: a later done end deletes the refused key a failed end wrote
mesh/delivery merged: source.main moved to novox/hq@0d04a16560e4
mesh/check checking 7c533965bfa6 on 0d04a16560e4
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 17:11:35 +01:00
jschoubben f99c0c9e29 Amend A-293: the newest build is the last trunk build-ended event heard for the module
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 17:09:10 +01:00
jschoubben 66d37b8f30 Answer A-293: in by-entries mode a failed newest build is said as a refused key on the builder's build-ended event
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 17:05:37 +01:00
mesh-tools 852a0d1163 Merge pull request 'Design 47 G: the lead's answers A-291 and A-292' (#585) from design/47-G-lead-answers-14 into main 2026-11-12 16:00:57 +00:00
jschoubben cbbf170718 Answer A-292: a node-engine's failed say is said again within the link, loudly
mesh/delivery merged: source.main moved to novox/hq@852a0d1163d3
mesh/check checking cbbf17071862 on 852a0d1163d3
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 16:53:42 +01:00
jschoubben 3c357ee1f1 Answer A-291: a process receives a chunked seat answer through one SDK helper
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 16:46:26 +01:00
mesh-tools 43612bf1be Merge pull request 'Design 47 G: the lead's answers A-287 and A-288' (#582) from design/47-G-lead-answers-13 into main 2026-11-12 15:34:11 +00:00
mesh-tools 5f35cd2e32 Merge pull request 'Design 47: the lead's answers for a placed module whose build failed (a small fix on main, issue 1966)' (#584) from design/47-unpublished-placed-module into main 2026-11-12 15:28:10 +00:00
jschoubben 048545b266 Answer A-290: a step answer over the steps state's bound is kept as an object the key names
mesh/delivery merged: source.main moved to novox/hq@43612bf1be0a
mesh/check checking 048545b266bb on 43612bf1be0a
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 16:27:32 +01:00
mesh-tools 7792e96670 Merge pull request 'Design 39 parts: the lead's answers D3 (a list of a place a secret is kept, for §15) and D4 (the database §13 and §14 read)' (#583) from design/39-list-secret-place into main 2026-11-12 15:27:07 +00:00
jschoubben 34660dd256 Correct A-289: the unpublished count holds no package, since no reachable case leaves a reserving build's package unpublished
mesh/delivery closed: a newer head of the same pull request
mesh/check being carried to 34660dd2565f on 7792e9667042 | from 63ccbe512bc0
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 16:17:06 +01:00
jschoubben 84faadbe38 Answer A-289: a build that reserves no version leaves its package done; a package is published by its own language's seat
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 84faadbe382e on 63ccbe512bc0
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 16:12:45 +01:00
jschoubben 11d257516a Design 47 fix: U8, the refused key is written on the failed build's event
mesh/delivery merged: source.main moved to novox/hq@5f35cd2e324a
mesh/check checking 11d257516a51 on 5f35cd2e324a
2026-11-12 15:56:00 +01:00
jschoubben 99d01964ef Design 47: the lead's answers for a placed module whose build failed, a small fix on main 2026-11-12 15:49:05 +01:00
jschoubben da6c3ab163 Answer A-287 and A-288: a kept announcement names every declared state; a refusal's test holds its meaning
mesh/delivery closed: a newer head of the same pull request
mesh/check pass da6c3ab16305 on 63ccbe512bc0 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 52s on 4…
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 15:47:09 +01:00
jschoubben 1995bd1247 Design 39 parts: the lead's answer D4, the database §13 and §14 read is the one postgres made
mesh/delivery merged: source.main moved to novox/hq@7792e9667042
mesh/check being carried to 1995bd1247be on 7792e9667042 | from 63ccbe512bc0
2026-11-12 15:45:59 +01:00
mesh-tools 63ccbe512b Merge pull request 'Design 39 bus-and-switch: the lead's answer C5, how a carried switch is read' (#581) from design/39-carried-switches into main 2026-11-12 14:42:06 +00:00
jschoubben 1a6a72dc02 Design 39 parts: the lead's answer D3, a list of a place a secret is kept, for §15's check 2026-11-12 15:41:47 +01:00
jschoubben 45ee24759b C5 cites the principles that settle it: failure is loud, the simplest thing
mesh/delivery merged: source.main moved to novox/hq@63ccbe512bc0
mesh/check checking 45ee24759b07 on 63ccbe512bc0
2026-11-12 15:37:49 +01:00
jschoubben 8326a3b57d Design 39 bus-and-switch: the lead's answer C5, how a carried switch is read 2026-11-12 15:37:25 +01:00
mesh-tools e81a8d62a5 Merge pull request 'Design 47 G: the lead's answers A-284 to A-286' (#580) from design/47-G-lead-answers-12 into main 2026-11-12 14:17:58 +00:00
jschoubben 41dd30fe43 Amend A-286: a check step that errors after its fetch merged still answers its fetch
mesh/delivery merged: source.main moved to novox/hq@e81a8d62a591
mesh/check checking 41dd30fe436e on e81a8d62a591
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 15:16:53 +01:00
jschoubben c7850092fa Correct A-285: the group's packages are group-packages, since packages already holds the Go versions
mesh/delivery closed: a newer head of the same pull request
mesh/check checking c7850092fa08 on e1a9a0f2d238
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 15:16:30 +01:00
jschoubben 9dfde6fca1 Answer A-285 and A-286: a fetch carries the group's packages as a list; a failed test never hides the validator
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 9dfde6fca119 on e1a9a0f2d238
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 15:15:44 +01:00
jschoubben a8da15caff Answer A-284: a group's check builds a TypeScript member against the group's own packages
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 15:08:26 +01:00
mesh-tools e1a9a0f2d2 Merge pull request 'Design 47 G: the lead's answers A-281 to A-283' (#579) from design/47-G-lead-answers-11 into main 2026-11-12 14:04:49 +00:00
jschoubben de676868c6 Correct A-282: the step bound moves with the step by a revision-checked update, never a delete
mesh/delivery merged: source.main moved to novox/hq@e1a9a0f2d238
mesh/check being carried to de676868c65a on e1a9a0f2d238 | from a412c0760ba4
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 14:36:17 +01:00
jschoubben da9b672747 Answer A-282 and A-283: a step bound is created once; a template state's max-mib reaches its bucket
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 14:36:17 +01:00
jschoubben 14f908926e Answer A-281: the switch runs once and marks it, so §716's deliveries never become records at a start
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 14:36:17 +01:00
mesh-tools a412c0760b Merge pull request 'Design 25 bus-state: the lead's answer L1, a merged candidate's manifest is newly accepted' (#578) from design/25-merged-verdict into main 2026-11-12 12:28:30 +00:00
jschoubben ff30ac166e Design 25 bus-state: the lead's answer L1, a merged candidate's manifest is newly accepted
mesh/delivery merged: source.main moved to novox/hq@a412c0760ba4
mesh/check being carried to ff30ac166ecc on a412c0760ba4 | from a45b31fdaf2e
2026-11-12 13:20:17 +01:00
mesh-tools a45b31fdaf Merge pull request 'Design 39 parts: the licence manager is built of its parts, and the finished carry-over and its database leave' (#576) from design/39-parts into main 2026-11-12 12:08:26 +00:00
mesh-tools 5235c00630 Merge pull request 'Design 47 G: §716 (a group's members and bases before the take-in) and the lead's answers A-274 to A-280' (#577) from design/47-G-lead-answers-10 into main 2026-11-12 12:05:39 +00:00
jschoubben 6141d7b5d1 Design 39 parts: the lead's answers D1 (the vault reading) and D2 (§17's wording)
mesh/delivery merged: source.main moved to novox/hq@a45b31fdaf2e
mesh/check checking 6141d7b5d177 on a45b31fdaf2e
2026-11-12 12:21:10 +01:00
jschoubben 2132bb711d Merge §716 (group members before take-in) into the lead answers; A-279 in its form after §716
mesh/delivery merged: source.main moved to novox/hq@5235c0063024
mesh/check being carried to 2132bb711d4e on 5235c0063024 | from f9bdd3b1d1ce
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:55:00 +01:00
jschoubben f74f9b19be Design 47 G §716: the gate keeps a group's members and their bases from the announcement
Before any take-in the gate held no group: its records open at the take-in, and from G it writes
`merges` no more, so the forge bridge's §715 read of each member's base through `groups` had
nothing to answer from. ADR 0666 §13 has the gate form the group; §716 keeps it in the states
`deliveries` and `groups` the gate already holds (§613), heard on the seat git's pull-updated,
pull-merged and pull-closed, and says who deletes each key and how `groups` answers.
A-236 now points to it, and A-279 is carried over from the answers branch in the form §716 makes
it, its Approve-entry form dropped.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:54:16 +01:00
jschoubben ac294e7b86 Design 39 parts: build the licence manager as four parts of one manifest and let the finished carry-over leave
mesh/delivery closed: a newer head of the same pull request
mesh/check pass ac294e7b8628 on f9bdd3b1d1ce | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 91s on 4…
The operator asked for the manager built as clean as possible, one manifest of several parts. The seat's verbs stay
on the runtime of its own, since a process account answers none; the tools that only read get a bundle of their own
so they answer while the manager fails; the carry-over ran live, so its code and the store database's and grant-key's
requirements leave now and retire on the operator's Approve. The glossary names the two senses of part.
2026-11-12 11:49:19 +01:00
jschoubben 3c40a6e6c8 A-280: the code name in a code span
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:47:35 +01:00
jschoubben 77379f62ce Answer A-280: a bundle's findings follow its membership's finding-events stream
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:47:17 +01:00
jschoubben 15d73fe09c A-279's form that stands: the base is kept on the merges entry the Approve makes, copied at take-in
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:42:50 +01:00
jschoubben b9f7ff6054 Correct A-279 again: the base comes from the git seat's pull-updated, kept on the pull's record entry
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:40:20 +01:00
jschoubben 3d4691c544 Correct A-279: a member's base is read from its delivery, which the gate already keeps
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:38:26 +01:00
mesh-tools f9bdd3b1d1 Merge pull request 'Design 20 and playbook 06 say what the manifest validator accepts, and how a module is built of parts' (#575) from design/20-manifest-as-judged into main 2026-11-12 10:38:25 +00:00
jschoubben 6fc3ccda4b Answer A-279: a member's base is its pull request's base from the forge message
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:37:27 +01:00
jschoubben 658d33892c Write design 20 and playbook 06 from what the manifest validator accepts
mesh/delivery merged: source.main moved to novox/hq@f9bdd3b1d1ce
mesh/check checking 658d33892c5e on f9bdd3b1d1ce
The module-development documents described a manifest the mesh no longer
reads: four languages where the builder builds two, a Go bundle with no
system, a scheduled process the guide never said how to write, the old
kept-data sections as the norm, and the controller as the one refusing.
They now name each part of a module and its manifest form as the
manifest validator, the builder and the node-engine read it, and the
playbook lists where those parts still disagree, so an author writes
the form every part takes.
2026-11-12 11:36:48 +01:00
jschoubben efcc065f8f Answer A-278: the seat-definition function lives once in mesh-sdk
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:28:25 +01:00
jschoubben f9aa43bf09 Answer A-277 in the glossary's word: the manifest validator
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:26:13 +01:00
jschoubben 5266949a59 Answer A-277: the approvals state's 64 MiB is a declared max-mib the judge bounds
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:25:55 +01:00
jschoubben 3631a151a3 Answer A-276: a pipeline-waits finding names its awaited fingerprint in its evidence
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:19:18 +01:00
mesh-tools 216bc8ad8b Merge pull request 'Design 39 bus-and-switch: C4, room and the switch read the weekly window alone' (#574) from design/39-licence-weekly-only into main 2026-11-12 10:18:42 +00:00
jschoubben e98e675673 Answer A-275: the node-engine reads invokes with the SDK's one reader
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:06:14 +01:00
jschoubben c94a65f2d8 Answer A-274: the finding writer takes its subjects from the part's own binding
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 11:00:39 +01:00
jschoubben 6738de67e3 Design 39 bus-and-switch: C4, room and the switch read the weekly window alone
mesh/delivery merged: source.main moved to novox/hq@216bc8ad8b26
mesh/check checking 6738de67e30b on 216bc8ad8b26
2026-11-12 10:50:52 +01:00
mesh-tools 01ecd2426a Merge pull request 'Design 47 G: §708 to §715, a requirement's provider, the revert ask, the validation before take-in and non-trunk branches' (#573) from design/47-G-AS14-AS17 into main 2026-11-12 09:48:23 +00:00
jschoubben 7540c9aa6b Shorten §710's live option to the router's 24-character bound
mesh/delivery merged: source.main moved to novox/hq@01ecd2426aa8
mesh/check checking 7540c9aa6b30 on 01ecd2426aa8
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 10:34:52 +01:00
jschoubben 9fc2bd73fb Answer IX5 to IX7 in design 47 G: the validation's run, its pending wait, and every branch's merges and groups
A-213 sent the validation before take-in and the per-branch take-in to design lines: the indexer needs
the run's job and verdict, what holds a revision while the run is out, and how a branch other than
the trunk gets its merges and its groups, none of which a line said.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 10:34:52 +01:00
jschoubben 09c9dc49f7 Answer IX8 in design 47 G: an acting rule gives kind 5 from its holder to the entries it acts on
The indexer writes every edge of the catalog graph, and kind 5 had no line saying which steps give it,
between which artifacts, or what fills a catalog entry's acts, which the runner and the builder read.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 10:34:52 +01:00
jschoubben 29b498c59d Answer AS14 and AS17 in design 47 G, so the assigner resolves providers and asks a held revert without choosing
§708 says the named seat is found by its definition's delivers and its holder from the placements' holds, so no
entry field is added for it; §709 carries the manifest's contributes into the catalog entry and gives the edge kind
contribution its contract meaning; §710 writes the held revert's ask in the backup ask's shape, with one message to
the operator.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 10:34:33 +01:00
mesh-tools 1f368771f6 Merge pull request 'Design 47 G: the lead's answers A-148 to A-273' (#572) from design/47-G-lead-answers-9 into main 2026-11-12 09:28:54 +00:00
mesh-tools 9d82c2dee8 Merge pull request 'Design 39 bus-and-switch: C1, the licence manager runs as its own account, service and tools in its own runtime' (#571) from design/39-licence-own-process into main 2026-11-12 09:28:00 +00:00
jschoubben 162f0520a3 Answer A-273 (corrects A-272): the gate channel reads the router's record by §700's one path
mesh/delivery merged: source.main moved to novox/hq@1f368771f698
mesh/check checking 162f0520a3d1 on 1f368771f698
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 10:18:46 +01:00
jschoubben 3b383de72e Design 39 bus-and-switch: C3, the manager's logs in the logs section main issues
mesh/delivery merged: source.main moved to novox/hq@9d82c2dee8d7
mesh/check being carried to 3b383de72e5b on 9d82c2dee8d7 | from 2d8ff36d03ab
2026-11-12 10:18:38 +01:00
jschoubben c8c6f4344d Answer A-270 to A-272: the approvals state's key lifetime, the runner's approvals answer, the gate channel's path
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:56:12 +01:00
jschoubben 220d2fbe69 Answer A-268 (corrects A-263) and A-269: the Go module is read where it is built; a push's head is its merge
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:55:02 +01:00
jschoubben e58be12323 Answer A-267: the composition step's progress names the runner as its asker
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:53:17 +01:00
jschoubben 5f6d71da21 Answer A-266: no holder names the gate's approvals verb
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:51:28 +01:00
jschoubben fa839454f6 Answer A-265: the tool runner answers a bundle's marked seat verb as AnswerSeat does
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:50:04 +01:00
jschoubben da260f0835 Answer A-264: a marked verb's additions are made where every definition is resolved
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:44:44 +01:00
jschoubben 9de6b0fb15 Answer A-261 to A-263: a request's seats by digest, the step's beat, a Go bundle's package
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:44:29 +01:00
jschoubben 5b39ffcfd0 Answer A-260: the trunk's head for §298's third edge is the highest held source revision
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:42:07 +01:00
jschoubben 1ce07690a4 Answer A-258 and A-259: §298's order edges name seats and are acting waits
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 1b37bb8d29 Answer A-257: the tool runner sends a bundle's chunked answer
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 57b261840b Correct A-251: the finding id is an escaped key name
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 70bfb72c78 Answer A-255: the process gate's bound key, header and act
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben ff669cc720 Answer A-254: a run's step-ended carries its answer
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 491e606472 Answer A-253: a chunked answer reaches a bundle as file
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben a7e339d7df Answer A-252: the composition step's machines, cases and asks
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben d40854a073 Answer A-251: the branch-unprotected finding's scope and id
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 637df7d374 Answer A-250: delivered-again is an ask the assigner's process judges
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 7e9bb2dab4 Answer A-249: the tool runner takes a chunked answer whole for a bundle
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 20e3e1a3cd Answer A-248: the run asker names its asker
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 41b2345ba4 Answer A-247: a validation says it was accepted; its answer comes in step-ended
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben c80b5eedd2 Answer A-246: delivery waits kept in their own state
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 52dc32cb07 Answer A-245: order edges under rule 7; state forms bump the version
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 2904b60c1e Answer A-244: wait held is a header; the held answer a field of its own
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 3b309fa61e Answer A-243: one composition request per machine; every run input any size
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 534fd88be4 Answer A-242: tiers from the assigner's difference; the view keeps waits
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 92358ddfd7 Answer A-241: the validation always judges against the whole seat list
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 0363e6f5f1 Answer A-240: the assigner hears its run's step-ended
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben a50c26ecea Answer A-239: the composition says the withheld subjects
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 88c44c095e Answer A-238: a run ask may name no head
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 04955c3112 Answer A-237: a run's input is any size, served in chunks
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben eba38e5ed9 Answer A-236: pushed's head and before; groups member gains base
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben b69d526139 Answer A-235: a run's end is said by the runner as a group-ended
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 653d2a84d9 Answer A-234: a catalog revision names its definitions
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 777bf7d281 Answer A-233: an age-kept stream is read whole at every start
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben f8986a5541 Answer A-232: the validation run rides the check queue with the usual id
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 1c29c4bc56 Answer A-231: an asker names its queue in invokes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 24a0fc7272 Answer A-230: a restore point names the bus names it took
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 931c0878c1 Answer A-229: an entry's requires names the optional ones too
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 0dbff50da4 Answer A-228: the gate reads everything through interfaces in its own terms
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben e8a2134d94 Answer A-227: merged gains base, as §714 says; A-221 and A-223 withdrawn
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 96404d8acf Answer A-225 and A-226; code-span a module name in A-224
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 091551e053 Answer A-224: one approval gate in mesh-sdk for both gates
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 3f0f6483c8 Answer A-223: the pull request's own branch is head-branch
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 4fff5cdfab Answer A-222: a failed adopted event is asked again; runs taken up at start
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben ae6407fb71 Answer A-221: branch is the watched branch on every forge message
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben d4a14a7765 Answer A-220: the serving parts widens compares
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 1fa531268b Answer A-219: the gate takes the operator channel as an interface; only Act moves
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben beac2c0170 Answer A-218: pipeline-waits clears when the new runner runs healthy
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben ba7e8a2dfb Answer A-217: what a revert is, and where the running manifest comes from
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben b85f659f1a Answer A-216: build.ended keeps its last end per fingerprint with no age bound
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben fffad9f578 Answer A-214 and A-215: fill through state.info; what widens
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 5f761c16cd Answer A-213: the indexer asks the validation run; the rest to design lines
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 69fdbc7cb3 Answer A-212: kind 2 is a needs reference; kind 5 to a design line
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben fbb60b952c Answer A-211: the assigner's put waits in a state of their own
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben a56c9b6262 Answer A-210: other modules found as seat holders when called; callers by seat
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben ab03e427f4 Answer A-209: a digest in a key is escaped; test states refuse what the bus refuses
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben af25c1424f Answer A-208: the indexer writes the edges; every branch takes in as the trunk does
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 456a1eae45 Answer A-207: the runner's recoveries state; widens from revision and grants-for
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 18ecf1c47f Answer A-206: the runner's release verb, its wait finding and clock
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben d60302b2bd Answer A-205: grants-for reads its candidate through revision
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 6e65fe2246 Answer A-204: the assigner keeps its said put waits on the bus
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben fc7e52c404 Answer A-203: the forms of the withheld path
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 98d7be07fd Answer A-202: a pipeline says the publish that started it
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 7843f56522 Answer A-201: the assigner keeps a run's candidate graph until the run ends
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 8e767e10fb Answer A-200: a record joins its take-in by its milestone
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben e0f23683b0 Answer A-198 and A-199: the act's form in mesh-sdk; AdoptEvent for the candidates hook
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 9adfad1108 Answer A-197: the node-engine raises its own findings, the watchman reads none of its state
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 24df2be69a Answer A-196: an empty status never asks the operator for approval
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 2188dfaffe Answer A-195: the runner's pipeline wait
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 19a1d9342e Answer A-194: the indexer's runs get a read and a drop tool
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 18a5617fa4 Answer A-193: how a run is kept, placed and said
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 39b4149687 Answer A-192: the watchman hears waits and running, reads no other state
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 88e0ca6f2d Answer A-191: a failed pipeline with no known author has no owner
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben d01e1121f8 Answer A-190: the indexer's kept calls are open while what they serve stands
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 0d153c98e0 Answer A-189: version-delivered and version-released read whole at start
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 92709ec311 Answer A-188: a take-in writes its entries into the gate's record
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 2f5268d97a Answer A-187: the consumer ledger keeps a message's first delivery time
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 0a10272f3d Answer A-186: the seat server takes the definition from its caller's seat list
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben ae6d3ddecc Answer A-185: the catalog client hands a candidate's revision to a hook
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 8834af8bd0 Answer A-184: main's subject keeps the checker's asks until it goes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 1bfbeda9ec Answer A-183: an unenforced mark is refused by both validators
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 23be8beda9 Answer A-182 in a new part: a superseded step's drop is asked where it stands
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben c518e59b9b Answer A-181: build.ended kept last per fingerprint, read whole at start
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 7f7eefdb44 Answer A-180: a run is placed on its candidate's catalog revision
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben f053779abe Answer A-179: the seat server refuses the unproven; the grants hold the rest
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 07aa46a02d Answer A-178: the view and the teller say their answers with a read tool and marks
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 9c6a6d2f00 Answer A-177: a compiled verb carries its approval mark
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 0afac8d52e Answer A-176: one mesh-sdk helper writes a process's consumer facts
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 65dcc83bf1 Answer A-175: the teller and the view follow each other's answers by views families
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 4c7b86af2c Answer A-174: the indexer keeps its runs; an unwatched branch takes the trunk's view
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben db726d734e Answer A-172 and A-173: the teller's merge path goes; the view per branch
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben d8dade2563 Answer A-171: a pinned catalog revision names its branch
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 17f8ec2fbb Answer A-170: an ask's kind is in its body, the subject names queue and asker
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben a2f2e5b98d Answer A-169: one key form for every holder of pins
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 19f969b2db Answer A-168: marked waits take the union of every branch's newest entry
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben b13dc2bfc4 Answer A-167: the assigner's composition step, held entries, reverts
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 7ebdfb3278 Answer A-166: the runner keeps when, the health works out the age
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben d71bd5d0fa Answer A-165: seat servers apply the class defaults
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 28c38dc2b9 Answer A-164: runner_health reads the consumer ages its process writes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 01d8328758 Answer A-163: an ask's asker is its seat's name, checked against the definition's callers
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben ea4cb84ce1 Answer A-162: build.ended for every end, the assigner reads no builder bucket
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 7098d550f3 Answer A-161: the teller's events carry the fields it says
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben e89d0ce830 Answer A-160: a run's candidate from the branch take-in of its members
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 644a9484ab Answer A-159: resolved names the commit and the digest of what was laid
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben a6ffb0a7a1 Answer A-158: a marked call answers {answer, approval}
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 4020ff8b70 Answer A-157: warranted asked again on the holder heard; call-unanswered on a bound key
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben b054da9141 Answer A-156: distribution's build modules are the build seats' claimers
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 95e47c2776 Answer A-155: restic's put-off wait is a bound key on the bus
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 79d8ec7a52 Answer A-154: the approval mark's findings scope and id
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 9a90bec951 Answer A-153: a subscription's key resolves through mesh-sdk's EventSubjects
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben 82c483fccc Answer A-152: the toolchain digest is the sha256 of the program run
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:28 +01:00
jschoubben df569ad63a Answer A-151: the build modules read the artifact store through its provision
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:27 +01:00
jschoubben 9e737e7c4c Answer A-150: restic's night process gets its own bus
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:27 +01:00
jschoubben 855149b25b Answer A-149: the node-engine's own name from its applied store at start
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:27 +01:00
jschoubben 2b13097956 Answer A-148: a trunk fault that fails a release's check rides that release
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 09:41:27 +01:00
mesh-tools 2d8ff36d03 Merge pull request 'Design 47 G: the lead's answers A-112 to A-147' (#570) from design/47-G-lead-answers-8 into main 2026-11-12 08:40:47 +00:00
jschoubben 00c436dd15 Design 39 bus-and-switch: C2, the manager replaces a key pair it cannot read
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 00c436dd1580 on 2d8ff36d03ab | carried from 044b913a9e50 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | co…
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 09:36:39 +01:00
jschoubben 12a98c2183 Design 39 bus-and-switch: C1, the licence manager runs as its own account, its service and its tools in its own runtime
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 12a98c21832e on 044b913a9e50 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 62s on 4…
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 09:16:05 +01:00
jschoubben d16d23e2a2 Answer A-147: a recovery act during a move is refused, naming both fingerprints
mesh/delivery merged: source.main moved to novox/hq@2d8ff36d03ab
mesh/check being carried to d16d23e2a27a on 2d8ff36d03ab | from 044b913a9e50
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 0d19a01ea7 Answer A-146: the reader release carries the foundation part's exception
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 294df2d72e Answer A-145: the second reader release carries only §621's words
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben be5ab83015 Answer A-144: a client imports its own seat only; the key codec and source follower move to the domain SDK
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 47583173db Answer A-143: the recovery module's waits are bound keys
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben f9bb4a6d78 Answer A-142: a missed step-ended reaches the watchman by its durable consumer
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 61b2a273f2 Answer A-141: the recovery module acts on its own stop
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 54f6d4bb5d Answer A-140: the wait after a restore's cancel, a busy keeper
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 4e121e37cd Answer A-137 to A-139: what a keeper's restore sets aside, the recovery bounds, chunks from mesh-sdk
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 8392ec5c2d Answer A-136: the watchman's steps read goes by §483
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 7b03a6831b Answer A-135: the digests state takes the template's fixed cap
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 1869e6b0a2 Answer A-134: binding.changed carries its consumer; the vault adopts it
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 5b1d068de3 Answer A-133: the recovery module's manifest follows the validator
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:16 +01:00
jschoubben 8d5fcb8236 Answer A-132: the running fingerprint from the node-engine's running event
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben c764f46577 Answer A-131: the first view of a seat's view is the holder's first-view tool
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 53f119fb48 Answer A-129 and A-130: kept-points says the unrestored asides; the recovery settings' names
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 29692693fa Answer A-128: a read in seat form is its own kind in mesh-sdk; reword to the validator
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 401f5dcb8b Answer A-126 and A-127: one seat-holder helper; serves beside the bus only
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben a1faef315f Answer A-123 to A-125: aside's names, a list, the restore in mesh-sdk
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben aef9bf8453 Answer A-122: the image build act's bound is the other build seats'
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben b42864542a Answer A-121: an old section's bus names are the bus composer's
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben fe138a638c Answer A-120: the early pass takes the node's record from the engine's own statement
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 17c9525ebd Answer A-119: the builder's floor from the catalog half, the assignments view in seat form
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben cb0efaf9a6 Answer A-118: bound keys in the builder, the outgoing checker as it is
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 6af2aecb73 Answer A-117: the node-engine's early pass, its entry-not-found, why on recovery endings
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 363da8fbab Answer A-116: a read of a seat holder's state names the seat
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben c4ad757265 Answer A-115: serves belongs to the process, tools stay the bundle's
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 168e41fd69 Answer A-114: wanted keeps each want's last word with no age limit
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 1ff3066bab Answer A-113: a failed restore restarts the newer build; no move without its restore point
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
jschoubben 69771a4d8e Answer A-112: a process reads its settings from a declared file
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 03:28:15 +01:00
mesh-tools 044b913a9e Merge pull request 'Design 39 bus-and-switch: the lead's answer B44, the carry-over carries live leases' (#569) from design/39-licence-answers-8 into main 2026-11-12 00:06:34 +00:00
jschoubben 28cd959de4 Design 39 bus-and-switch: the lead's answer B44, the carry-over carries live leases
mesh/delivery merged: source.main moved to novox/hq@044b913a9e50
mesh/check checking 28cd959de439 on 044b913a9e50
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 01:05:36 +01:00
mesh-tools 93f8ef4a2f Merge pull request 'Design 39 bus-and-switch: the lead's answer B43, a first view keeps a binding an event wrote after it was asked' (#568) from design/39-licence-answers-7 into main 2026-11-12 00:02:16 +00:00
jschoubben 87f86772f6 Design 39 bus-and-switch: the lead's answer B43, a first view keeps a binding an event wrote after it was asked
mesh/delivery merged: source.main moved to novox/hq@93f8ef4a2f7c
mesh/check being carried to 87f86772f60f on 93f8ef4a2f7c | from 7092d2afa9a7
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:57:58 +01:00
mesh-tools 7092d2afa9 Merge pull request 'Design 39 bus-and-switch: the lead's answer B42, the vault asks one first view again by name' (#567) from design/39-licence-answers-6 into main 2026-11-11 23:39:30 +00:00
jschoubben 4e2bf72219 Design 39 bus-and-switch: the lead's answer B42, the vault asks one first view again by name
mesh/delivery merged: source.main moved to novox/hq@7092d2afa9a7
mesh/check checking 4e2bf72219b5 on 7092d2afa9a7
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:38:33 +01:00
mesh-tools 627ef84453 Merge pull request 'Design 39 bus-and-switch: the lead's answer B40, a licence's target says where consumers go next' (#566) from design/39-licence-answers-5 into main 2026-11-11 23:32:37 +00:00
jschoubben 34f5ee62d2 Design 39 bus-and-switch: the lead's answer B41, the whole test reads the condition through the seat's contract
mesh/delivery merged: source.main moved to novox/hq@627ef8445326
mesh/check being carried to 34f5ee62d2a2 on 627ef8445326 | from 84e9f8a1929d
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:27:38 +01:00
jschoubben 4f19bdf6fb Design 39 bus-and-switch: the lead's answer B40, a licence's target says where consumers go next
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 4f19bdf6fb2b on 84e9f8a1929d | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 80s on 3…
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:14:51 +01:00
mesh-tools 84e9f8a192 Merge pull request 'Design 47 G: the lead's answers A-101 to A-111' (#565) from design/47-G-lead-answers-7 into main 2026-11-11 23:12:59 +00:00
mesh-tools d65ed10931 Merge pull request 'Design 39 bus-and-switch: the lead's answers B38 and B39 met in develop' (#564) from design/39-licence-answers-4 into main 2026-11-11 23:09:28 +00:00
jschoubben e65c0ea27f Design 39 bus-and-switch: the lead's answers B38 and B39 met in develop
mesh/delivery merged: source.main moved to novox/hq@d65ed10931c1
mesh/check checking e65c0ea27f24 on d65ed10931c1
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:08:17 +01:00
mesh-tools 044cd2dfdd Merge pull request 'Design 39 bus-and-switch: the lead's answers B26-B37 met in develop' (#563) from design/39-licence-answers-3 into main 2026-11-11 23:07:17 +00:00
jschoubben c75f1072ea Design 39 bus-and-switch: the lead's answers B26-B37 met in develop
mesh/delivery merged: source.main moved to novox/hq@044cd2dfddbe
mesh/check checking c75f1072eaf4 on 044cd2dfddbe
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-12 00:05:53 +01:00
jschoubben 369c875358 Answer A-111: the runner's write verbs as built
mesh/delivery merged: source.main moved to novox/hq@84e9f8a1929d
mesh/check checking 369c875358ce on 84e9f8a1929d
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:57:54 +01:00
jschoubben 3ffb0f8d8e Answer A-110: a process's serves in the form of invokes
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 3ffb0f8d8e27 on d65ed10931c1
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:57:37 +01:00
jschoubben 9ff6368e82 Answer A-109: a process's serves, their grants, chunks on the core bus
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:56:42 +01:00
mesh-tools 87f2901f55 Merge pull request 'Playbook 07: the prepare reads the code; the lead answers design questions against the principles' (#562) from meta/prepare-reads-code into main 2026-11-11 22:54:31 +00:00
jschoubben a2da2a3e74 Let the lead answer every design question against the principles
mesh/delivery merged: source.main moved to novox/hq@87f2901f556b
mesh/check checking a2da2a3e7451 on 87f2901f556b
The operator replaced the instruction on implementing a design on
2026-11-11 about 21:48 CET: the lead answers each question met while
building in one line against the principles, and only one no principle
settles goes on, to the operator when it changes what they see or how
they work, removes data or needs their terminal, otherwise to the design
writer as a design line. Playbooks 02, 07 and 08, the glossary's design
question, AGENTS.md, the code reviewer and the drawn overview still said
only the operator or the design writer answers; they now say the rule.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:53:03 +01:00
jschoubben ccfac82fd1 Answer A-108: the node agent runs the catch-up after the restore
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:51:15 +01:00
jschoubben b6ea3a740c Have the prepare read the code and list where design and code disagree
In milestone 47/G's develop the lead answered over 100 questions, most of
them a design line meeting the code: a form the code cannot take, or a part
assumed built that was not. The prepare had read the design alone. The
operator, 2026-11-11 about 23:45 CET: the prepare reads the code each design
line lands in as well, and lists every such place before any developer
starts; the lead answers each in one line against the principles.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:50:17 +01:00
jschoubben f29a8f0ebe Answer A-107: a process answers its seat's verbs, the operator's yes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:48:03 +01:00
jschoubben 362c8074d1 Answer A-106: the runner's steps as built, its write verbs waiting on the operator
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:44:17 +01:00
jschoubben ab39ee3290 Answer A-105: the runner's toolchains state
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:42:40 +01:00
jschoubben 9efa510ad5 Answer A-104: the runner's write verbs, kept results, toolchain and unanswered asks
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:40:16 +01:00
jschoubben f426b4e5fa Answer A-103: the registration's first branch wins; the follower's helpers built once
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:40:16 +01:00
jschoubben 1c9488c2ae Answer A-102: accepted kinds named in invokes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:40:16 +01:00
jschoubben f871c9f968 Answer A-101: > in TopicMatches, one operator-channel asker in mesh-sdk
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:40:16 +01:00
mesh-tools e1609f5dbb Merge pull request 'Design 47 G: the lead's answers A-71 to A-92' (#560) from design/47-G-lead-answers-6 into main 2026-11-11 22:39:59 +00:00
jschoubben e8fce4ca46 Answer A-100: build.ended as built
mesh/delivery merged: source.main moved to novox/hq@e1609f5dbb9d
mesh/check being carried to e8fce4ca46b3 on e1609f5dbb9d | from 95d2c2247131
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:33:25 +01:00
jschoubben ed9a41147a Answer A-99: build is every pipeline's first step, for its own entry
mesh/delivery closed: a newer head of the same pull request
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:33:11 +01:00
mesh-tools 95d2c22471 Merge pull request 'Design 39 bus-and-switch: the lead's answers B23 and B24 met in develop' (#561) from design/39-licence-answers-2 into main 2026-11-11 22:32:54 +00:00
jschoubben fa8c046682 Answer A-98: a stop act that removes nothing, for a revert
mesh/delivery closed: a newer head of the same pull request
mesh/check checking fa8c046682f8 on 95d2c2247131
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:32:21 +01:00
jschoubben fe3b86d65f Design 39 bus-and-switch: the lead's answer B25, the bindings verb answers the licence's key
mesh/delivery merged: source.main moved to novox/hq@95d2c2247131
mesh/check checking fe3b86d65f86 on 95d2c2247131
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:31:45 +01:00
jschoubben 720cff7271 Answer A-97: a candidate's build is a pipeline step the runner asks of mesh-builds
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 720cff7271cb on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:31:38 +01:00
jschoubben 128236e7a0 Design 39 bus-and-switch: the lead's answers B23 and B24 met in develop
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 128236e7a047 on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 23:30:54 +01:00
jschoubben 2eb897699f Answer A-96: the builder says build.ended on its own stream
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 2eb897699fed on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:28:59 +01:00
jschoubben 4ffcadf4e9 Name the node-engine by its word in A-95
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 4ffcadf4e97c on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:28:21 +01:00
jschoubben 04f4d715bd Answer A-95: the node-engine's kept entries as built, and what is still owed
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 04f4d715bd1f on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:28:03 +01:00
jschoubben fb10a9359b Answer A-94: the assigner hears build.ended and maps a want to its release by source
mesh/delivery closed: a newer head of the same pull request
mesh/check checking fb10a9359bfb on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:27:35 +01:00
jschoubben d0beb962d8 Answer A-93: a branch's take-in computes its revision directly; the trunk stays main
mesh/delivery closed: a newer head of the same pull request
mesh/check checking d0beb962d890 on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:27:11 +01:00
jschoubben d0c7373d38 Answer A-92: the node-engine's bound keys
mesh/delivery closed: a newer head of the same pull request
mesh/check checking d0c7373d386e on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:26:25 +01:00
jschoubben 03813cce78 Answer A-91: the warrant in the router's record, unnamed steps, the runner's asking half
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:26:00 +01:00
jschoubben c96d5d6c6d Answer A-90: every watched branch is taken in by the trunk's take-in
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:25:22 +01:00
jschoubben f103680251 Answer A-89: every asker reads its warrant from the router's record
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:24:00 +01:00
jschoubben c096c4af1c Answer A-88: no callers named in mesh-networking's seats
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:21:30 +01:00
jschoubben 63153faa94 Answer A-87: the builder's fault words and builder_build's newest
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:20:52 +01:00
jschoubben 82e0d85700 Answer A-86: who keeps an input, and a kind-3 edge's language
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:20:01 +01:00
jschoubben 834370bf67 Answer A-85: a catalog revision per watched branch
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:18:38 +01:00
jschoubben 5b4cbc035c Answer A-84: the builder learns a candidate from the run's build step
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:17:35 +01:00
jschoubben 1963baefb3 Answer A-83: a branch build's edges and its newest build
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:15:40 +01:00
jschoubben 2f8e07d923 Answer A-82: build modules found through their seats
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:13:31 +01:00
jschoubben acdef3a98d Answer A-81: the builder's publish again, as built
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:13:02 +01:00
jschoubben 9d406b6f02 Answer A-80: what the indexer keeps as heard has a state of its own
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:08:26 +01:00
jschoubben 6cf7372ad4 Answer A-79: the grants part's catalog half, as built, and its last indexer reads go
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:06:17 +01:00
jschoubben e67326b6c7 Answer A-78: what outdated keeps and reads
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:05:20 +01:00
jschoubben 6ec5ac27a7 Answer A-77: a wait's bound is a key with its own time to live
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:05:00 +01:00
jschoubben e9c927d448 Answer A-76: the reservation's fingerprint, the registry seat, the fault's class
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:04:18 +01:00
jschoubben 8e87bdc148 Answer A-75: mesh-issues' pipeline rows in its delivery line
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:03:39 +01:00
jschoubben 0fe2118105 Answer A-73 and A-74: publish again by digest, the router's asks through its seat
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:02:28 +01:00
mesh-tools 01b6441a48 Merge pull request 'Design 47 G: the lead's answers A-44 to A-70' (#559) from design/47-G-lead-answers-5 into main 2026-11-11 22:01:50 +00:00
mesh-tools 173d6d2cd4 Merge pull request 'Design 39 bus-and-switch: the lead's answers to the prepare's 22 questions' (#558) from design/39-licence-answers into main 2026-11-11 22:00:34 +00:00
jschoubben ba91206c6b Answer A-69 and A-70: pending artifacts kept, the builder's bundle serves reserved
mesh/delivery merged: source.main moved to novox/hq@01b6441a48d2
mesh/check checking ba91206c6b13 on 01b6441a48d2
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 23:00:34 +01:00
jschoubben 23356b2c88 Answer A-68: what outdated compares against is kept as heard
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:59:37 +01:00
jschoubben 441296f37a Answer A-67: the pass keeps its assignment graph revision first
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:57:38 +01:00
jschoubben e1764b4892 Answer A-66: the seed's steps call, its fetch and its five minutes
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:57:23 +01:00
jschoubben eb061c7603 Answer A-65: mesh-knowledge's findings go through the writer
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:57:04 +01:00
jschoubben c1d331da40 Answer A-64: the bus module's wire forms, as built, and its finding's scope
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:56:41 +01:00
jschoubben a062015501 Answer A-63: the outside wait's readings, as built
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:55:15 +01:00
jschoubben 2bf174b57c Design 39 bus-and-switch: the lead's answers to the prepare's 22 questions, each by the principles
mesh/delivery merged: source.main moved to novox/hq@173d6d2cd45d
mesh/check being carried to 2bf174b57c57 on 173d6d2cd45d | from 18639c73921a
Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 22:50:43 +01:00
jschoubben 712f2cc60f Answer A-62: the assigner's placement verb and the placed refusal
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:50:05 +01:00
jschoubben fe5275b470 Answer A-61: wanted said per want, kept last per node and module
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:49:37 +01:00
jschoubben fdaff7b780 Answer A-60: chunk subjects go to the principal that answers the verb
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:47:35 +01:00
jschoubben 93985958bc Answer A-59: the bus module's bundle reads the snapshot files
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:47:12 +01:00
jschoubben 5b54cfdad1 Answer A-58: an owner change is its own change value
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:45:57 +01:00
jschoubben ac217628a1 Answer A-57: the builder's package-published, whole, with its branch and source
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:45:25 +01:00
jschoubben 927ff51842 Answer A-54 to A-56: distribution's old stream name, the builder's setting, mesh TS packages in the locks check
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:45:11 +01:00
jschoubben 7ee9271a3b Answer A-53: a snapshot's record outlives no file and ages out with it
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:44:24 +01:00
jschoubben 85b1a9f317 Answer A-52: the grants part's view-waits is its reader's
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:44:10 +01:00
jschoubben 5216099fe4 Answer A-50 and A-51: the node-engine's references by digest, the grants part's four finding kinds
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:41:04 +01:00
jschoubben 7e9ed3abf3 Answer A-49: the session bound, its end at the channel's close, and mesh/capabilities
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:39:33 +01:00
jschoubben a8c596c270 Answer A-48: the assigner's outside waits, ask, release and rule
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:39:19 +01:00
jschoubben da5c034928 Answer A-47: the node-engine's entry reads and kept entries
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:39:04 +01:00
jschoubben d50a593d68 Answer A-46: the manifest versions come from builder_versions now
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:37:27 +01:00
jschoubben 6818f61ce0 Answer A-45: the bus module's bundle answers mesh-broker's verbs, the keeper does the work
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:37:07 +01:00
mesh-tools 18639c7392 Merge pull request 'Design 39 bus-and-switch: the licence manager keeps its data on the bus and switches on its own usage' (#557) from design/39-licence-switch into main 2026-11-11 21:36:26 +00:00
jschoubben 315cd7ec24 Answer A-44: snapshot-remove's answer, and chunks in the seat reader
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:35:54 +01:00
jschoubben 8d772ad40c Answer design 39 bus-and-switch's questions by the principles, so it can ship on main as it is
mesh/delivery merged: source.main moved to novox/hq@18639c73921a
mesh/check being carried to 8d772ad40c95 on 18639c73921a | from e1819e0a0d08
The lead answered the four open questions against hq's principles on
2026-11-11: the vault serves licence secrets to the tool runner's account
(principle 6), the operator's word replaces ADR 0183 §21 and ADR 0206 §16, §18
with no record, the store database stays one milestone as the carry-over's
read-only source (principle 7, the delivery gap named), and the milestone no
longer waits for design 47's G: its conditions are said on the operator
channel as any module does today.

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 22:30:44 +01:00
jschoubben 84bb85d037 Design 39 bus-and-switch: the licence manager keeps its data on the bus and switches on its own usage
The operator, 2026-11-11: the licence manager itself moves every consumer off a
licence at 99 % of its session or weekly window to the licence with the most
room, configurable, with its Postgres database replaced by bus state and tools
for all it keeps and does. That night a session meant to switch missed the
readings, so no session does it any more. The new milestone part writes every
line it builds (kept data, the one carry-over of today's rows, the vault move
of ADR 0550 §3, bindings and holdings as events, the switch, its warning and
condition, the tools, adding a licence), and names the three questions no
record settles; the main file is put in the shape a builder reads whole.

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
2026-11-11 22:30:44 +01:00
mesh-tools e1819e0a0d Merge pull request 'Design 47 G: the lead's answers A-36 to A-43, and corrections of A-27 and A-30' (#556) from design/47-G-lead-answers-4 into main 2026-11-11 21:27:07 +00:00
jschoubben d9a0e19b20 Answer A-43: plain views families are followed, the runner's writer uses its JetStream
mesh/delivery merged: source.main moved to novox/hq@e1819e0a0d08
mesh/check checking d9a0e19b204e on e1819e0a0d08
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:25:39 +01:00
jschoubben 2d5f6ac949 Correct A-30 to §529's finding stream forms; answer A-42, the gate finding's scope
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:24:07 +01:00
jschoubben fd04333fa5 Answer A-41: no part reads the build modules' toolchains buckets where the builder answers
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:16:58 +01:00
jschoubben feebb8cb20 Answer A-40: the held writer seeds at Start
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:16:18 +01:00
jschoubben 867229003b Answer A-39: which record change makes which owed act
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-11 22:14:05 +01:00
jschoubben c5bdcbf0c5 Correct A-27: the tool runner keeps its followers and writes its findings through a held writer 2026-11-11 22:11:57 +01:00
jschoubben efec941fa7 Answer A-38: the held writer answers what it holds 2026-11-11 22:11:30 +01:00
jschoubben 528a7ee41c Answer A-37: the held writer's stdio form, and it holds what a reader writes
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 22:07:36 +01:00
jschoubben 9d57887357 Answer A-36: mesh-sdk's calls words and the TS follower, as built
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 22:06:49 +01:00
mesh-tools 2b731e1141 Merge pull request 'Design 47 G: the lead's answers A-27 to A-35, each tested against the principles and the design's lines' (#555) from design/47-G-lead-answers-3 into main 2026-11-11 21:06:02 +00:00
jschoubben d0a7565088 Answer A-33 to A-35: the gate's records from take-in, source, owed acts
mesh/delivery merged: source.main moved to novox/hq@2b731e11414c
mesh/check checking d0a7565088f9 on 2b731e11414c
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 22:05:00 +01:00
jschoubben d60f14c56c Answer A-30 to A-32: declared finding events, old keys, held writers
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 22:03:55 +01:00
jschoubben 2103f4479b Answer A-29: one mesh-sdk writer that observes from its own state
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 22:01:05 +01:00
jschoubben a1c77688ab A-23 names the trace operation, as built
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 22:00:25 +01:00
jschoubben dca4bc973e Answer A-28: own followers through a reader, a single ask through the writer
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 22:00:05 +01:00
jschoubben 201e998418 Answer A-27: the tool runner's first views are adopted by a process reader
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:59:52 +01:00
mesh-tools 597d097982 Merge pull request 'Design 47 G: the lead's answers A-16 to A-25, each tested against the principles' (#554) from design/47-G-lead-answers-2 into main 2026-11-11 20:58:51 +00:00
jschoubben a1efa45250 Correct A-17: mesh-sdk names no seat; the module passes the contract's findings-asked subject
mesh/delivery merged: source.main moved to novox/hq@597d0979828f
mesh/check checking a1efa452500f on 597d0979828f
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:57:51 +01:00
jschoubben c0409d137a Answer A-26: followers' findings are the reader's, through Adopt
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:57:32 +01:00
jschoubben 0bcfff8e11 Correct A-18: checks goes with the merge states, as §431 says
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 0bcfff8e1189 on 269d226a8d14
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:55:34 +01:00
jschoubben 9e3b63a6db Answer A-25: a session put keeps its description
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 9e3b63a6dbfc on 269d226a8d14
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:55:12 +01:00
jschoubben cab8c547f1 Correct A-13: findings-asked is heard on the seat's subject, not the part's stream
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:55:01 +01:00
jschoubben d0597bd2e5 Answer A-24: the gate's reading verbs answer the delivery records
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben 21fa15d96d Answer A-23: the gate's recovery mark is one field of the record
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben 1ac0f4e311 Extend A-22: resolved words are dropped in every module
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben 4d729a009a Answer A-22: the self-check and healers moves, as built
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben c3de071fc8 Answer A-21: the object sessions' store, chunk and threshold
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben b057329e65 Answer A-20: the gate's kept data moves with the fewest parts that lose none
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben 99d9721ff3 Answer A-19: HearSeatEvents' read at the start
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben c58a00b3a7 Answer A-18: what G retires in the gate leaves no trace
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben 7c476bfb4a Answer A-17: the writer names findings-asked with its seat
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
jschoubben 7dc0707008 Answer A-16: one mesh-sdk constructor makes a stdio module's finding writer
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:54:44 +01:00
mesh-tools 269d226a8d Merge pull request 'Design 47 G: answer develop's questions against the principles; the outside-cause wait whole (G-19); lead answers (G-20)' (#553) from design/47-G-develop-answers-7 into main 2026-11-11 20:54:19 +00:00
jschoubben b6556a4bcf Answer A-15: the branch follower's not-found, as built
mesh/delivery merged: source.main moved to novox/hq@269d226a8d14
mesh/check being carried to b6556a4bcfac on 269d226a8d14 | from 60e99f38759b
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:41:47 +01:00
jschoubben 0e1a742b10 Answer A-10 to A-14 against the principles: seat events, the granting wait's release, the writer's subjects, ref in the id
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 0e1a742b1066 on 60e99f38759b
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:40:29 +01:00
jschoubben cfb6666711 Answer A-4 to A-9 against the principles: grants, the branch follower, the gate's kept data, take-in bodies
mesh/delivery closed: a newer head of the same pull request
mesh/check pass cfb66667112f on 60e99f38759b | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 51s on 4…
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:36:23 +01:00
jschoubben 51a9919c56 Answer A-3: the finding writer is generic over the contract's plain structs, replacing A-1's accessors
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 51a9919c5622 on 60e99f38759b
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:35:22 +01:00
jschoubben 3e7befcb0b Answer A-2: busarchive moves with bussnap into mesh-sdk
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 3e7befcb0bb1 on 60e99f38759b | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 58s on 4…
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:31:14 +01:00
jschoubben e95b7f8ea0 Answer develop's questions against a short list of principles, so a detail needs no design batch
The operator, 2026-11-11 about 21:30 CET: a living list of principles; the lead answers each question against it
and does not register every detail. Adds 00-META/principles.md, the lead-answers file G-20 with A-1, and lets the
developer take a lead answer from it.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:30:52 +01:00
jschoubben 7f094b8278 Write the outside-cause wait whole in G-19, so develop builds one mechanism
Round 1 of the review found §705 patched per finding: the commonest cause
(a machine down by its own network or silence) was missed, the keeper's
silence, the race between a check and its condition, the wait's ends, its
event fields, its finding's telling and its tools were unwritten. §706 now
says them once, following ADR 0677; §705 points to it. §707 gives the
writer's own finding-refused to each bundle's key part, with the statement's
bundle, in place of the G-15 edit, since G-15 is at its size cap. The
glossary's waiting call moves below the kept call's Not: line.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:22:52 +01:00
jschoubben 26f4b1c2a5 Hear O-9's outside cause from the condition keeper, and follow ADR 0676 in the gate
§705 said the assigner hears rewritten and unreachable from the node-engine's
finding events, but the node-engine only states the network; the watchman
raises those findings and the keeper holds them as conditions, and an
unreachable is raised on the machine that cannot be reached. The assigner now
reads the machine's conditions through mesh-conditions' verb at the judging
and judges again on condition-cleared, never reading the watchman's state.

§677 cited ADR 0259 §3, never in force; it now cites ADR 0286 §6 and ADR 0259
§6 with ADR 0676's clauses, says waiting call for the held call, and checks
what ADR 0676 decides. The glossary gains the waiting call and the holder that
asks for a marked verb served from a module's process. §580 gives the writer's
own finding-refused to every bundle within its own key part.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:13:37 +01:00
mesh-tools 60e99f3875 Merge pull request 'Design 47 milestone G: develop answers batches 6 and 7 (G-16 to G-18, §587-§705), the operator's answers O-1 to O-9 of 2026-11-11' (#552) from design/47-G-develop-answers-6 into main 2026-11-11 20:09:06 +00:00
jschoubben e6c8bb15d1 Hold a delivery on a machine whose network names an outside cause (O-9), and name mesh-host's wireguard places in §428
mesh/delivery merged: source.main moved to novox/hq@60e99f38759b
mesh/check being carried to e6c8bb15d1a7 on 60e99f38759b | from 108a56fbc78a
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 21:04:00 +01:00
jschoubben 414490fa5a Close batches 6 and 7 by review round 3, ADR 0675 and ADR 0676, and develop's D-33 to D-49
Every must of the last review round is written as its reviewer proposed or
as the narrowest line: the gated call keyed by its own call id with one ask
per act across instances, the holder granted from its want, the keeper's
protected restore on a standing grant with a cancel and its partial stream
removed, the node-engine's kept entries in its composition state, the
indexer ordered before the parts that ask its entries, and G's own deletions
moving the gate's copy mark. Develop's D-33 to D-49 are §698 to §704.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 20:56:26 +01:00
jschoubben ccc8a4c492 Give every acting verb of mesh-recovery one answer form, cancel included (D-32)
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 20:44:49 +01:00
jschoubben 350d05b2c2 Make a module's call of a marked verb one gated call (O-8), and answer develop's D-26 to D-31
The operator's O-8: the holder keeps a module's call, asks, and runs that
same call on the Approve; the caller hears the end on its own subject of
the seat's gated-ended and never calls again, while a person through the
tool runner keeps ADR 0286 §37's path for now (§677, resting on a record
to come). Develop's D-26 to D-31 and the reader release's order are §691
to §697, with §668, §682 and §554 corrected to them.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 20:44:03 +01:00
jschoubben 51c6dcbe46 Fix design 47 G's batches 6 and 7 by review round 2, O-7 and develop's D-19 to D-25
The holder of a marked verb a process serves now asks, checks and claims
the warrant itself, under its own name and in its own state, and a holder
of before G is never reached (§677). The keeper's snapshot is sent by the
keeper's own verb and a protected stream's never leaves it, restored in
place (§681, the operator's O-7). The recovery module's hand verbs get
their fields, answers and asker (§682); the node-engine keeps its entries
on its node (§683); the grants part composes a want's account before its
first views, so no cycle stands (§684). The republish queue is kept on the
bus, §588 rests on ADR 0674, and D-20 to D-25 are §685 to §690.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 20:35:34 +01:00
jschoubben 9381cde1b4 Fix design 47 G's batches 6 and 7 by the review round, and drop every named caller list
The four reviewers' musts on §587 to §676 are written into the lines they
name: the snapshot sent on its call's own session with its digest
recorded, warrants matched to the current ask and its digest, the gate's
copy at every start, the recovery module's hand tools as seat verbs, the
catalog half's pending entries and the node-engine following no branch,
the keeper following a moved finding stream, the builder's republish queue
and its toolchain wait, and more. The operator's answer of about 16:10 CET
ends every named caller list: every verb takes its class's default, and a
module's process that calls a marked verb carries the operator's warrant
for that one call (§677). Develop's D-16 to D-18 are §678 to §680, in a new
part G-18 so that G-17 stays under 60 KB.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 20:16:47 +01:00
jschoubben d004b90e49 Answer milestone G's seventh develop batch and the operator's answers of 15:14 to 15:42 CET
Batch 7 of design 47 G is written as §610 to §676 in G-17 (§614 was
committed ahead): the operator's answers on callers by class, on the five
parts that move only their findings, on the forge and image registry's
other entries, and on the gate's kept data; the refreshed build maps'
questions of mesh-tools, mesh-host and mesh-networking, mesh-sdk,
mesh-delivery, mesh-catalog and mesh-contracts; and develop's D-1 to D-15.
§588 now carries the operator's yes to publishing again on an event, its
record to come. Lines the answers correct are corrected in place, in G and
in design 34 every-event and design 46 7-approval-mark, which ship in G's
release. The main file now lists ADR 0668 and ADR 0672.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 15:54:59 +01:00
jschoubben 1012514fdc Say once how every part reads the catalog half per watched branch, so develop builds one form
Design 47 G §614 answers develop's D-5 and D-12 ahead of the rest of
batch 7: the reader through meshcatalogclient on the part's one views
reader, the branches from the registrations or the gate's source, the
manifest entries each part declares, an older build's entry asked once by
fingerprint, a none-yet branch read as empty, the first pass after every
first view, and a dropped branch's findings cleared.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 15:53:52 +01:00
jschoubben 0cdd66f945 Answer milestone G's sixth develop batch in design 47 so develop builds without choosing
Eight questions met in the join and develop after batch 5 are written as
lines §587 to §594 of a new part G-16, each of internal form within the
records and lines it cites: the ledger's upkeep calls' shapes, publishing
again every unpublished reserved version, a whole read of a module's build
keys, one event of a node's capabilities (manifest-reads-changed, ADR 0618
§4, ADR 0673 §2), the object stores' caps, the gate's merge path from G's
delivery, the gate's dropped Approve-per-merge reads, and the manifest
readers on the catalog graph entry. §568 now cites ADR 0673 in place of
"number to come". The automatic publish on a registry holder's running is
left to the operator: no record decides it.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 15:23:04 +01:00
mesh-tools 108a56fbc7 Merge pull request 'Design 47 milestone G: develop answers batch 5 (G-15 §560-§586), the operator's answers of 2026-11-11 afternoon' (#551) from design/47-G-develop-answers-5 into main 2026-11-11 13:37:27 +00:00
jschoubben 785cd229b3 Fix batch 5 by its review, so no cycle, lost act or lost pin is left to develop
mesh/delivery merged: source.main moved to novox/hq@108a56fbc78a
mesh/check checking 785cd229b310 on 108a56fbc78a
Writes round 1's musts into the lines they name. A build edge is met by a running
holder. An act cut off by a move ends through its failure path. Pins survive a
race and a restart, and the gate restates them. Each point's digest is kept on the
bus. The words of a failed build move to the assigner's hold. Also writes the
operator's answers of 14:55 and 15:15 CET: three automatic rebuilds and then an
ask; the image registry copies its template keys at its first start; refused asks
kept are a setting.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 14:35:30 +01:00
jschoubben e5d199263b Make the forge module's readings of step-ended design lines, and home the reserved client in its seat's contract
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 14:31:46 +01:00
jschoubben 8999f8e9d0 Answer design 47 G's fifth develop batch, so develop can go on without choosing
Writes §560 to §584 (G-15) and corrects the lines of G-1, G-12 and G-14 those
answers name. They cover the recovery module's events, findings, cap, hand tools
and digest check; the dump giving way to a restore; the builder's reads and its
retry on an event; mixed build and delivery cycles; refused asks; mesh packages
in lock files; and the forms the developers asked about. They carry the
operator's answers of 2026-11-11 (14:05, 14:15 and 14:30 CET): a put-off dump is
that night's backup; the recovery module moves freely; it is one process holding
its seat; refused asks are listed; a failed build says so; and the forge and the
image registry modules join the foundation.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 14:31:46 +01:00
mesh-tools 6280fc3a3e Merge pull request 'Agents: hq-record-writer, the definition of the agent that proposes a record from a draft' (#550) from agents/hq-record-writer into main 2026-11-11 13:18:34 +00:00
jschoubben b4abb98aaa Give the record writer a definition, as a role started more than once
mesh/delivery merged: source.main moved to novox/hq@6280fc3a3e2e
mesh/check checking b4abb98aaabc on 6280fc3a3e2e
The lead started an agent that proposes a record from a draft by hand in two sessions; agents-have-a-home asks a definition.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 14:17:07 +01:00
mesh-tools 1c6fe9611c Merge pull request 'Design 25 bindings-first-view §9: compose a bucket's binding from the bus module's list answer, breaking the registry circle (issue 1966)' (#549) from design/25-bindings-from-the-list into main 2026-11-11 12:50:07 +00:00
jschoubben f65acb98c7 Compose a bucket's binding from the bus module's existing list answer, breaking the registry circle
mesh/delivery merged: source.main moved to novox/hq@1c6fe9611ccc
mesh/check checking f65acb98c742 on 1c6fe9611ccc
The bus module's new build cannot be built: its image needs the registry's
mirror, which the registry holder serves only with a state binding the grants
part never heard and waited for that very build to say. The operator chose
(2026-11-11 13:30 CET) to compose key-value and object-store bindings from the
fields the running bus module already answers, delivered with the grants part
alone. Records that §7's 'needs no edge' missed the bus module's own build
through the registry.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 13:48:10 +01:00
mesh-tools 51b43dfb10 Merge pull request 'Design 47 milestone G: develop answers batch 4 (G-12, G-14), the recovery module and the operator's answers of 2026-11-11' (#548) from design/47-G-develop-answers-4 into main 2026-11-11 12:14:42 +00:00
jschoubben 040ad22e69 Keep setting aside and discarding an aside on the operator's hand tools, refused by restore-kept
mesh/delivery merged: source.main moved to novox/hq@51b43dfb10d1
mesh/check checking 040ad22e691d on 51b43dfb10d1
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 13:12:42 +01:00
jschoubben e164a66362 Write the operator's 13:10 answer: a restore asked is done, identity being the one check
mesh/delivery closed: a newer head of the same pull request
mesh/check checking e164a6636261 on cbafc7556a40
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 13:08:53 +01:00
jschoubben caa7a9265c Make identity the one check of the recovery module's callers, and give a bundle's finding writer its two runtime calls
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 13:08:11 +01:00
jschoubben 0b90251098 Write the operator's answers of 13:06 on setting aside and on discard-aside's check
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 13:06:37 +01:00
jschoubben a3d9975e4b Answer the last review round of batch 4: the recovery module's acts, order, callers and grants
Its verbs answer on acceptance and its events carry the act's form and fingerprint,
it never restores beside the nightly dump either way, a node-engine may call it only
for a want its node holds, and its grants are listed whole as ADR 0672 has them; the
long form of an invokes entry is read by the second reader release first.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 13:06:14 +01:00
mesh-tools cbafc7556a Merge pull request 'Design 47 milestone G: develop answers batch 3 (G-10, G-11, G-13), with the operator's answers of 2026-11-11' (#547) from design/47-G-develop-answers-3 into main 2026-11-11 11:57:47 +00:00
jschoubben 6d0506402d Put restoring kept data on a recovery module, and answer round 1 of the review of batch 4
The operator chose a module of its own for snapshotting and restoring a module's
kept data on the live bus, so the bus module and the snapshot account keep their
narrow grants; the reviewers' musts are written into the lines they name, and the
new lines go to G-14 so G-12 stays within a part's size.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 12:57:01 +01:00
jschoubben e504dac534 Write the operator's answers of 12:41 on restore points, the checker's drop and an empty step status
Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 12:42:09 +01:00
jschoubben e41dbeef4a Write the operator's answer on an Approve before G, and answer round 3's leftovers of the third batch
The operator made an old Approve that pull request's approval step, decided by the
registration's policy, so §510's carried act and §608's merge wait go; round 3 of
the third batch has no fourth round, so its findings are answered here (§543 to §550).

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 12:40:58 +01:00
jschoubben 05b16d63bb Answer develop's fourth batch of questions on milestone G, and write the operator's words for the teller
Develop met questions the design did not answer on restore points, a bundle's
channel to the tool runner, the want's new fields, the finding stream's form
under ADR 0595 and ADR 0666, and per-branch views; each is now a design line
(G-12, §516 to §541) with its reason and check, so develop builds them and
decides nothing.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 12:38:56 +01:00
jschoubben 529cba366b Answer the last review round of milestone G's third batch, in G-13 from §600
mesh/delivery merged: source.main moved to novox/hq@cbafc7556a40
mesh/check being carried to 529cba366b07 on cbafc7556a40 | from ec6be19b1138
The four reviewers' round-2 findings are answered as design lines: the build seats' accept subjects, workers and
grants and the rollout's order as one seam, an ask's start and end outside a pipeline, the builder's heard
toolchains, the tool runner's order from the package a build resolved, the step log's single write, the objects
stores' caps and a carried merge's wait.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 12:29:39 +01:00
jschoubben 298e68c71f Write the operator's noon answers, the review of §456 to §479, and develop's next answers into milestone G
The operator answered who may drop a build seat's steps, whose name a step key carries, and that a review in G
refuses no one for authorship. The four reviewers' musts on G-10 are answered as design lines, and develop's next
questions of internal form (the watchman's, builder's, indexer's, forge bridge's, assigner's, view's, bus module's,
keeper's and node-engine's) in the new part file G-11 (§480 to §515); the teller's words wait for the operator.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 12:08:39 +01:00
jschoubben e9ae4afc12 Answer develop's third batch of questions on milestone G, and let the design writer answer internal forms
The operator answered D-12, D-13 and D-19 at the terminal, and ruled on 2026-11-11 that a question touching nothing
the operator sees, no permission and no data kept is answered as a design line by the design writer and read by the
reviewers by architectural domain. The new part file G-10 carries those answers and the internal-form ones (§456 to
§479), the earlier lines point to them, and playbooks 02, 07 and 08 and the glossary say who answers a design
question now. The questions that touch who may call or what the operator sees are left for the operator.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 11:52:51 +01:00
mesh-tools ec6be19b11 Merge pull request 'A developer reads its build map and the design lines it cites, not the whole design' (#546) from process/developer-reads-its-lines into main 2026-11-11 10:32:29 +00:00
jschoubben ae1b5ee957 A developer reads its build map and the design lines it cites, not the whole design
mesh/delivery merged: source.main moved to novox/hq@ec6be19b1138
mesh/check checking ae1b5ee957ad on ec6be19b1138
Four developers of design 47's milestone G ran out of context on 2026-11-11 after one or two subtasks: the
milestone part and the main file they were told to read whole come to about 580 KB.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 11:31:14 +01:00
mesh-tools 9ca4757357 Merge pull request 'Design 47 milestone G: develop answers D-7 to D-10, and design 25's types wording' (#545) from design/47-G-develop-answers-2 into main 2026-11-11 09:56:58 +00:00
jschoubben 073160ca46 Answer develop's next questions on G's contracts, and correct design 25 on the bus module's types
mesh/delivery merged: source.main moved to novox/hq@9ca4757357d2
mesh/check checking 073160ca46f1 on 9ca4757357d2
The generator emits body types and sending stays mesh-sdk's, the reader release reads needs-approval and the
bodies map, an accepted kind's name is separate from its stream, and the bus module's created and recreated do
carry types.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 10:55:20 +01:00
mesh-tools a1cbcfb23f Merge pull request 'Design 47 milestone G: answers to develop's first questions (callers of the bus module's acts, the node-engine's verbs, small body forms)' (#544) from design/47-G-develop-answers into main 2026-11-11 09:46:41 +00:00
jschoubben 4976caa124 Answer develop's six questions on milestone G's contracts, so the seat definitions are written from the design
mesh/delivery merged: source.main moved to novox/hq@a1cbcfb23f69
mesh/check checking 4976caa12443 on a1cbcfb23f69
Develop met verbs and events in mesh-contracts whose callers, classes, states or bodies no line named: the bus
module's acting verbs, the node-engine's thirteen verbs, the keeper's findings-views, the grants part's fill
events, two answers and three field forms.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 10:45:18 +01:00
mesh-tools 6c1e759e26 Merge pull request 'Design 25: the grants part takes a first view of the bus module's bindings at every start, so a missed created event is recovered (issue 1966)' (#543) from design/25-bindings-first-view into main 2026-11-11 09:27:00 +00:00
jschoubben d0760629e5 Shape the seat's Binding as the bus module sends it
mesh/delivery merged: source.main moved to novox/hq@6c1e759e267b
mesh/check being carried to d0760629e5fe on 6c1e759e267b | from f1fe609b3e85
The seat definition's Binding named a bus name and subjects while
created and recreated carry mesh-sdk's membership binding, which the
grants part needs; one form for the events and list's answer means the
generated contract must decode what is on the wire, unchanged.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 10:14:07 +01:00
jschoubben 27ec6f8500 Make the grants part one of G's observers, so its waits are findings
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 27ec6f8500da on f1fe609b3e85 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 91s on 3…
G-4 §200 already had the grants part write view-waits as a finding, while
G-8 §387 left it out of the observers that say findings as events. Adding
it, with its state findings and finding stream in its old sections, lets
the bindings first view's two conditions move to findings at G's delivery.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 10:08:04 +01:00
jschoubben c098518cc8 Design 25: the grants part asks the bus module for every binding at its start
A binding was written only on the bus module's live created event, so one
event the grants part never heard lost the binding for good (issue 1966):
a registry holder lacked three entries the bus held, and its publishes
were never said published. The operator chose to ask the bus module: at
every start and on a re-ask the grants part takes a first view from the
seat mesh-broker's list, whose entries now carry their binding, and
composes the bindings from it; a binding the bus no longer lists is kept
and said, never removed by a first view.

The same change puts design 25 in playbook 02's shape: section 12.2 to
12.6 move, unchanged and numbered, into the part of milestone bus-state,
so the main file falls from 104 KB to about 71 KB.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 10:08:04 +01:00
mesh-tools f1fe609b3e Merge pull request 'The approval mark and every-event: the prepare's answers, so both parts are complete for develop with milestone G' (#542) from design/34-wait-on-any-event into main 2026-11-11 08:56:23 +00:00
jschoubben 8e9116469a Answer step 0b's review of the approval mark and marked waits
mesh/delivery merged: source.main moved to novox/hq@f1fe609b3e85
mesh/check being carried to 8e9116469a0a on f1fe609b3e85 | from fbd7981b7be2
The served version comes from what the bundle runs and a reading from the
runner that made it, so no placement or clock lifts a deny; the runner's
state takes the old form the bus module can create; kept calls stay open on
their key; and the closed directory's mode is read by node-files.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 03:26:27 +01:00
jschoubben 28c22c65cb Answer the prepare's questions on the approval mark and marked waits
Each of the 25 recommendations becomes a design line with its question id,
so the builders find every field, test and source named; the live readings
of the credentials go into the gate's stated bound.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 03:24:13 +01:00
mesh-tools fbd7981b7b Merge pull request 'Design 47 milestone G: the prepare's answers, so the design is complete for develop' (#541) from design/47-G-prepare-answers into main 2026-11-11 02:17:01 +00:00
jschoubben b4a07c1de6 Answer the step-0b review of G-9: the catalog entry carries its manifest, grants follow the placed build
mesh/delivery merged: source.main moved to novox/hq@fbd7981b7be2
mesh/check checking b4a07c1de6f6 on fbd7981b7be2
The review found that reading the catalog graph instead of manifests needed the entry to carry the manifest and
stay while a build names it, the grants to follow the wanted build, the pre-G node-engines their manifests by
digest, and the restore, caller, stream, finding and test-budget lines a name for every field they left open.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 03:15:45 +01:00
jschoubben 30b29a58a4 Have only the indexer read manifests, and settle the four prepare items that contradicted each other
The operator's rule is that the indexer alone uses manifests to build the catalog graph, so every other part's
read of a manifest becomes a read of the catalog half. Lead-47's rulings settle the finding layout as a seat
contract with generated writers, process-builds as a node-engine seat verb, a grants-for read on mesh-grants for
widens, and aside on node-backup.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 03:15:45 +01:00
jschoubben b08147e531 Answer the questions of milestone G's final prepare in its part, so develop starts from the design alone
The prepare found about a hundred places where G's lines left the builder a choice. Each answer is now a line of
G-9 with its question and its check: one default of callers per verb class, so no verb is closed for lack of a
list; the seat-definition forms; kept data and restore points; versions; steps and test counting; the readers and
the gate's remaining parts. Earlier lines the answers change are amended in place.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 03:15:45 +01:00
mesh-tools 7dfb6f0b7b Merge pull request 'Designs 34 and 46: sessions wait on any event, and the approval mark gates calls and marked waits, delivered with design 47's milestone G' (#540) from design/34-wait-on-any-event into main 2026-11-11 02:08:01 +00:00
jschoubben 95ae1dd6ed Read the secrets list and every mark from the module catalog alone
mesh/delivery merged: source.main moved to novox/hq@7dfb6f0b7b15
mesh/check being carried to 95ae1dd6ed90 on 7dfb6f0b7b15 | from c74546a16f78
The operator answered that only the indexer uses the manifests, to build
the module catalog; so the secrets review and the tool runner read the
catalog revision, and the bus read and its nats tool extension go.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 03:03:00 +01:00
jschoubben de62fe5852 Answer round 3 of the domain review of the approval mark
mesh/delivery closed: a newer head of the same pull request
mesh/check pass de62fe58523a on c74546a16f78 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 69s on 4…
The client's node prefix stays on its own keys, so the router's record is
still read where the router writes it; the remaining notes say where the
runner's version is read and what each limit costs.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:53:20 +01:00
jschoubben bddf6ce95a Answer round 2 of the domain review of the approval mark
A stuck state recovers as a kept call, a node with no or stale readings is
denied, the caller-named subjects are withheld too, and the per-machine key
form and the client's prefix are named, so no path serves a marked tool
unasked; the forge's loosening is marked by the operator's answer.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:51:24 +01:00
jschoubben 636b2be7ac Write the operator's answers on the approval mark into milestone 7
Call again stands by the operator's choice; the credential file's reach is
a known limit, as G's shared account is; and the forge's two loosening acts
move to a marked tool of their own, so tightening stays unasked.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:48:10 +01:00
jschoubben a6c9b96be3 Answer round 1 of the domain review of the approval mark and marked waits
The bus withholds a marked subject from a runner that does not honour the
mark again, as ADR 0286 has it; marks follow G's catalog half; a recording,
a stuck gate, an absent router and a start before the marks are read each
have a stated answer, so nothing runs unasked and nothing fails silently.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:46:01 +01:00
jschoubben ca1fbf7867 Build the approval mark in milestone G and let a marked wait use it
The operator answered "Build the gate in G" and wants no new records: the
marked-wait lines now hold by the operator's answers, each departure said in
its Why, and design 46's milestone 7 becomes a part a builder reads whole,
written to the mesh as built (marks read from the bus, the capability hold,
the client's claim in bus state, the hand-act seat), delivered with G and
every-event.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:41:11 +01:00
jschoubben aec610a6f4 Design 34 every-event: hear every module stream, and gate a marked wait on the operator's warrant
The operator chose every module stream, mail too (ADR 0671), so the stream
lines hold and the restricted-stream deny goes. The operator also asked that
a wait respect the approval mark; that extends ADR 0286 to events and has the
tool runner's gate hold it, so those lines are written pending the record.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:41:11 +01:00
jschoubben d758964ca9 Design 34 every-event: the review reads a stream's subjects with nats_streams
No tool answered which subjects a stream holds, so the secret review could not find an
event the list missed; a module's tools read everything it holds, so the nats module's
tool gains that read, built first as a small fix.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:41:11 +01:00
jschoubben c232b1fb81 Design 34 every-event: write in the four domain reviews' findings
The reviews found waits that time out for ever on a misspelt name, refusals that would
loop a watcher, an acceptance no answer could prove, readers still describing the list,
and no way back said. Each is now a design line; the stream-events lines are written for
the recommended option and marked pending the operator's answer.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:41:11 +01:00
jschoubben 024627f658 Design 34: a session waits on any event, the tool runner hearing every one (ADR 0670)
Watchers reread on a timer because mesh_wait refuses every event outside a list of five,
and the delivery status view's events, which playbooks have watchers wait on, are not on
it. ADR 0670 grants the tool runner every event subject and drops the list; this writes
that as milestone every-event, one delivery group of mesh-sdk, mesh-delivery and
mesh-tools, and puts design 34 in the main-file-and-parts shape.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:41:11 +01:00
mesh-tools c74546a16f Merge pull request 'Design 47: milestone G's part complete, every operator answer and three review rounds written in' (#539) from feat/47-G into main 2026-11-11 01:34:16 +00:00
jschoubben 87827ebb82 Answer round 3 of the review of milestone G: a home and trust for the finding events, restarts that keep standing faults
mesh/delivery merged: source.main moved to novox/hq@c74546a16f78
mesh/check checking 87827ebb82a2 on c74546a16f78
The finding events lacked a stream, a consumer per source, a rule against a restart clearing what still stands, a
take-over rule and presence events; the 2026-11-11 answers move to a part file of their own so each stays under its
size. The test budget is counted per module, the upgrade window between the builder and the registry holders gets
its edge, and the readers of the new manifest words come first.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:30:59 +01:00
jschoubben bfd5fd2c0b Hear an event every declaring module says, keep the connectivity verbs open, and clear findings of a node a module left
The keeper finds its sources in the catalog and names none in its manifest, so one generic consumes form with its
validator rule and per-module grant carries the finding events; the operator's answer that a verb open today stays
open covers the connectivity verbs that name no callers; and findings of a module no longer placed on a node would
otherwise stand forever.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:21:43 +01:00
jschoubben 9f9cb58f54 Write the operator's answers into milestone G's part: findings heard as events, invokers chosen, the shared account a known limit
The condition keeper read other modules' findings states, which only their owner may reach; a node-engine
restarted with the full seat list never cleared its own finding, because only its process remembered writing it.
Observers now say each finding as an event and state all they own at each start and on the keeper's ask, so the
keeper's view is rebuilt from events alone. The `invokers` form is the operator's choice, and the tool runner's
shared account stays as it is in G, named as a known limit.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-11 02:20:24 +01:00
jschoubben 9bcd0ad197 Drop the lint scan for fixed names and keep "no part fixes a name" as a guideline the code review reads
The operator, 2026-11-11: the scan was a flaky check, and the rule is a guideline, not a decision. The names
G's own work removes are removed by its subtasks; the exemption line and the scan's checks go with it.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben 27c9c6f11f Answer round 2 of the review of milestone G's part: one rebuild trigger, digests from events, every new store sized
The four reviewers found two triggers for one rebuild, digests read from the builder's own keys, a mesh-wide wait
with no owner, bound or tool, new states with no retention or cap, a first fill the builder could race, and a
registry check whose source goes; each is now a design line with its reason, and test 60 follows section 271.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben 893d83d23b Rebuild only on the edges a part is made from, wait for a delivery mesh-wide, and name the version in each want
The operator answered on 2026-11-10 that only an imported package and the build toolchain make a dependent stale,
every other edge only ordering, that a dependent waits for its dependency's delivery to every present machine,
that a want names the released version and each part's digest, and that one tool answers a part's edges both
ways. G's answers move into a file of their own so each file of the part stays under 60 KB.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben 5df2eca59e Compare a release's edges by the dependency part's digest and per node, and fill them once at the start
A module's version can change without changing the part an edge points to, a delivery happens per node, a build
of another branch has no version, and acting and serving are facts, not versions: the lead's review of §374 to
§376 found each would mark edges behind wrongly or redo work. A first fill keeps the first query from finding
the whole mesh behind.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben b157283202 Record on each released version what it was built against, and find what is behind with one query
The operator chose on 2026-11-10 that the catalog of versions keeps a lockfile per released version (issue
1961): each edge's dependency version, so one query answers what is outdated after a restart or a missed event,
while each edge kind is still started by its own event, a release or a delivery.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben f215cb9207 End tagged versions at milestone G, since no custom versions exist for now
The operator's answer of 2026-11-10 allows no custom version, and a tagged version is one: a group's run on
request delivers nothing, so it builds its members against each package member's build by digest and publishes
no version, and `tag-wanted` and `tag-asks` go with G's delivery.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben c46669a17e Have design 51 reserve a module's version at its build's start, as design 47's milestone G does
Design 51 still numbered a package at its publication and said `version.numbered`, which the operator's choice
of 2026-11-10 replaces: the catalog's released versions are the one list, and a packed file carries its version.
The catalog's first versions are filled from what is published, so numbering continues above today's.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben e27fd02269 Number a module's version before its build, as the operator chose, so packed files carry it
A Go module zip and a TypeScript package hold their version inside the file, so a number given at publication
could not be valid for a digest built before it (gaps items 22, 23, 43). The operator chose on 2026-11-10: the
manifest says MAJOR.MINOR, the build system reserves the next patch at the build's start, the catalog holds and
says released versions only, the first watched branch alone releases, and Go packages are tagged, departing from
ADR 0573 §2 until a later research brings the record in line.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben 63eeb96ebd Answer round 1 of the review of milestone G's part, and split the part so each file is read whole
The four reviewers by architectural domain found retentions judged from another part's records, unbounded aging,
waits and stores no condition names, a kept artifact checked against the builder's key, and caller limits left
open; each is now a design line with its reason. The part is six files under 60 KB, one milestone still, with
G.md as its index; the main file stays over its bound on the lead's word.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
jschoubben da83f29f47 Give milestone G its own part of design 47, and answer its builders' open questions there
G's builders read a 443 KB design by searching it and filled what they did not find; playbook 02's shape gives
each milestone a part read whole. G's lines move whole into 47-milestones/G.md, numbered as design lines, and
the questions G's prepare and develop met within the records are written as the design's own choices
(the operator, 2026-11-10: small design choices are the design's, only architectural ones are asked).
The size bounds are bent for this split on the lead's word; research 087 takes them up.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-11 02:06:46 +01:00
mesh-tools 000a35d5cf Merge pull request 'Design 47: the watchman's take-in seed ends waits the indexer no longer holds (issue 1966)' (#538) from design/47-seed-ends-stale-waits into main 2026-11-10 22:45:34 +00:00
jschoubben b58d789e83 Design 47: the watchman's take-in seed ends waits the indexer no longer holds
mesh/delivery merged: source.main moved to novox/hq@000a35d5cfce
mesh/check checking b58d789e83d0 on 000a35d5cfce
The operator's answer to the design question of issue 1966 note 195: a take-in
wait whose taken event was never heard stood for ever; the indexer's pending
state is what waits. Also says the indexer's taken event covers every key its
pending tool named.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-10 23:44:03 +01:00
mesh-tools e65d0f8f2b Merge pull request 'Build features from the design alone and ask the operator what it leaves open' (#537) from process/implement-the-design into main 2026-11-10 16:51:25 +00:00
jschoubben cdfed7593e Keep the check step's no-second-review sentence whole
mesh/delivery merged: source.main moved to novox/hq@e65d0f8f2bbd
mesh/check being carried to cdfed7593e73 on e65d0f8f2bbd | from b229f5c4df1e
Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 17:24:12 +01:00
jschoubben 6e90fa443e Compare seat contracts with the design in the prepare, and call a verdictless merge unchecked
On 2026-11-10 a seat contract and an event's subject that differed from design 47
surfaced only in develop, and the checker twice dropped a check still running when
its pull request merged. The prepare now compares every seat contract the milestone
touches with its design lines, each difference a design question before develop;
and a merged change with no verdict is reported as unchecked, never as passed.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 17:24:02 +01:00
jschoubben 8f9a6d4f7b Build features from the design alone and ask the operator what it leaves open
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 8f9a6d4f7bc9 on b229f5c4df1e
Milestone 47/G was prepared and developed from a 430 KB design by agents told to
start from the knowledge base and judge what was not mesh-shaped. The prepare wrote
its own findings and about 40 rulings by two leads, seven of which went against the
records or the design, and 29 questions the design never answered surfaced only in
develop.

The playbooks now make the milestone's part of its design the only input to the
prepare, develop and the code review: the prepare outputs a build map per
repository and the design questions; the operator answers each and the answer is
written into the design before develop starts; every commit cites the design lines
it builds and the review judges it against them. A design is a main file and one
part per milestone, within a size an agent reads whole. Also: one lead per
milestone, a contract's users start from its commit, one writer per file, one
sibling placement, and agents stop between subtasks, never inside one.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 17:21:06 +01:00
mesh-tools b229f5c4df Merge pull request 'Take back three design 47 choices that broke accepted decisions (issue 1966)' (#536) from design/47-G-undo into main 2026-11-10 15:52:22 +00:00
jschoubben ebf115303a Undo three of design 47's milestone G choices that went against accepted decisions
mesh/delivery merged: source.main moved to novox/hq@b229f5c4df1e
mesh/check checking ebf115303a87 on b229f5c4df1e
The joint amendment wrote three choices that the records already decide
otherwise. The foundation parts are again the manifest validator's list, the
runner added (ADR 0666 §4), with no registration value and no finding of their
own. The runner again starts a group's pipeline on the indexer's take-in and
consumes package-published (ADR 0666 §13, §15); a second start for the same
revision and module writes nothing by the pipeline key's compare-and-set. A
replaced object stays, like a key-gone one, until the drop tool on a person's
word (ADR 0595 §4). Design 51 and the glossary follow.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 16:50:38 +01:00
mesh-tools 3d97fe016c Merge pull request 'Amend design 47 with milestone 47/G's joint findings as its own choices (issue 1966)' (#535) from design/47-G-joint into main 2026-11-10 15:33:22 +00:00
jschoubben b33ac325c0 Amend design 47 with milestone 47/G's joint findings as its own choices
mesh/delivery merged: source.main moved to novox/hq@3d97fe016c2d
mesh/check being carried to b33ac325c0ec on 3d97fe016c2d | from d0d71b554876
The prepare found where ADR 0666, ADR 0667 and design 47 disagreed. The
ADR 0666 insight was dropped, so its five points are written here as the
design's choices with their reasons: the foundation from the registrations
(departing from ADR 0619 §3 for ADR 0666 §5), the want's compare-and-set per
module and watched branch, every pipeline started on catalog-revision, the
cost of a refused new indexer, and each check at its row's level. ADR 0667's
clause numbers follow the record as accepted, so the merge token goes only at
§12's moment and on the operator's word. The approval is the operator-channel's
accepted kind, a replace removes its object, the sent store's age is
transport, and what the milestone creates has its retention. The glossary
gains "held back" and "sent store"; design 51 starts a publication's
pipelines on catalog-revision.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 16:13:45 +01:00
mesh-tools d0d71b5548 Merge pull request 'Close a research effort with the knowledge base's close verb (issue 1966)' (#534) from fix/1966-close-verb into main 2026-11-10 09:58:42 +00:00
jschoubben c814bf2e0f Close a research effort with the close verb, so only the closing is judged
mesh/delivery merged: source.main moved to novox/hq@d0d71b554876
mesh/check checking c814bf2e0f18 on d0d71b554876
Proposing a graduated effort again whole had the knowledge base re-judge its approved reasoning
against the decisions that replace it (issue 1966).

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 10:57:23 +01:00
mesh-tools 2ba88971cf Merge pull request 'Glossary: transport fault and kept call; developers run the touched packages' integration tests once per head and use the live seat list (issue 1966, ADR 0668)' (#533) from agents/1966-lessons into main 2026-11-10 07:33:26 +00:00
mesh-tools 38915bbe01 Merge pull request 'Amend designs 47, 46 and 51 for pipelines per module and milestone G as one milestone (ADR 0666, ADR 0667)' (#532) from design/086-pipelines into main 2026-11-10 07:20:44 +00:00
jschoubben 78ef41e7bd Make playbooks 07 and 08 agree on the developer's integration run, and bound it
mesh/delivery merged: source.main moved to novox/hq@2ba88971cf52
mesh/check checking 78ef41e7bdb7 on 2ba88971cf52
The first amendment left the step table, the review's reading of integration
tests, step 3 and playbook 08 saying the opposite. The run is now once per head
reported, the touched packages and their importers in the module, by name where
the importers are many, and never a whole module or level, within the operator's
rule of 2026-10-18. The live seat list is read again when a head is reported.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-10 06:53:26 +01:00
jschoubben 7bbd4a86b4 Name transport faults and kept calls, and make developers check what the review found
Issue 1966's fix group showed two gaps in how a developer checks: an integration
test the change broke went unseen because only the untagged suite ran, and a stale
seat list made the local manifest validator refuse what the running one accepts.
The developer definition and playbook 07's develop step now run the touched
packages' integration tests against an embedded bus, the whole of mesh-sdk, and
the validator against the live seat list and a fresh catalog trunk, and stop at
two thirds of the context with a handover in the prepare file. The glossary gains
transport fault (ADR 0664 §1, ADR 0668 §1) and kept call (ADR 0668).

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-10 06:51:43 +01:00
jschoubben 43eac215d2 Follow ADR 0666's 26 clauses in the designs and the glossary
mesh/delivery merged: source.main moved to novox/hq@38915bbe0174
mesh/check checking 43eac215d264 on 2ba88971cf52
The record was proposed again with clauses 2 and 17 split for the
knowledge base's review, so every citation of it moves to the new numbers.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 03:11:29 +01:00
jschoubben 40661df80f Take in round 3's last two fixes and record the three review rounds
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 40661df80ff4 on 6611b94e3d7a | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 114s on …
Seams and tools and security and upgrade approved with two fixes: only an
operator's or a healer's rerun clears a failed step for merge-pull, and a
fix before the first registrations is merged by the operator under the
trunk-only want. The review section now records all three rounds.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 03:02:39 +01:00
jschoubben 45cc27cf24 Take in round 2 of the review by architectural domain of the 086 amendment
Seams and tools and security and upgrade asked seven items, ruled by
fix-1965 with ADR 0666 and ADR 0667 staged to match: the assigner and
merge-pull switch on the first registrations, a branch that lost its
protection starts nothing, a passing rerun clears a failed step, sent
objects are read by a grant derived from the manifests, and the object
store is a small mesh-sdk package so nothing else rebuilds. The staged
record renumbered its clauses, so every citation of ADR 0666 follows the
new numbers.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 03:00:45 +01:00
jschoubben a8103cf0b1 Take in round 1 of the review by architectural domain of the 086 amendment
The four reviewers asked changes in every domain and fix-1965 ruled on each,
with ADR 0666 and ADR 0667's wording staged to match: foundation parts keep
old sections, merge-pull judges from its own state so a head merges while
the runner is down, a group's wants wait for group-ended, a failed head is
said to its author then the operator, the checker goes only after a first
end-to-end pipeline and a tested backup, and the stores are sized again for
publish rebuilds.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 02:49:51 +01:00
jschoubben bce14ad12e Hold no requirer of bus state for a bus build that waits for a yes
The assigner's provider rule read the phase of the bus module's newer want,
so a yes not yet asked held every module asking bus state (issue 1966,
note 150); milestone G reads what runs. Also name the tracker of the
review-author stopgap.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 02:39:49 +01:00
jschoubben 55ac9f2d8e Name in design 47 the records its pipeline text now follows
Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 02:38:40 +01:00
jschoubben 3de6895e32 Give the glossary research 086's words for pipelines
Research 086's Domains section names the words its graduation adds and
changes; the designs now use them, so the glossary defines them in the
same set: pipeline, pipeline step, policy, registration, watched branch,
run on request, artifact reference, the seats mesh-pipeline-runner and
mesh-builds, the composition step and the build-input fingerprint, with
Homonyms rows for policy and step. The words that leave (merge check, try,
trunk commit, merge turn, composition check) keep their entries until
milestone G's delivery, since they govern it, and move to Not: lines with
the as-is update after it.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 02:38:13 +01:00
jschoubben 2fa15a1c8d Write ADR 0666's asks into design 46 and its versions into design 51
ADR 0666 §6 has the operator-channel offer a fourth ask instead of refusing
it, never holding the newest back; design 46 now says which ask waits and
how it is offered again. ADR 0666 §9 numbers a package when it is
published and has that publication rebuild its dependents; design 51 now
says how the builder numbers it, says it on the seat mesh-builds, and who
asks a tagged version once the checker goes.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 02:36:26 +01:00
jschoubben 471fd8791d Write research 086's pipelines into design 47, one milestone with G
ADR 0666 and ADR 0667 decide that a change goes from a commit on a watched
branch to the machines through its module's pipeline, with no Approve per
merge and the checker gone, built and delivered as one milestone with
design 47's milestone G. The design now says how: registrations, the
catalog graph per watched branch, pipeline steps and their policy, a
session's own merge through merge-pull reading the head's latest run,
edges between artifacts, values of any size, the tools, the runner's kept
data and the build capacity, and the acceptance tests by level and
repository. Every section the pipelines replace keeps governing G's own
delivery and says so; the recovery merge points to its decision.

Claude-Session: https://claude.ai/code/session_01JG73KK8HD1gufnqyFxMFdW
2026-11-10 02:35:25 +01:00
mesh-tools 6611b94e3d Merge pull request 'Amend design 47 for milestone G: the gate's record, the seat git, the merge on the Approve alone' (#530) from design/47-G-amendment into main 2026-11-09 19:38:46 +00:00
mesh-tools fc6a30070f Merge pull request 'Developers, design writers and preparers can say a commit others wait on as it lands' (#531) from agents/developers-report into main 2026-11-09 19:37:47 +00:00
jschoubben c9abb1c5f9 Test 58 names a part not heard, not a node away: a check still passes a sleeping node with a warning
mesh/delivery merged: source.main moved to novox/hq@6611b94e3d7a
mesh/check checking c9abb1c5f92e on 6611b94e3d7a
The test read as failing every check while a node sleeps, against ADR 0593 §9 and the design's own try section.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-09 20:12:00 +01:00
jschoubben b3484cdcfc Name the indexer's mesh-wide hold among what milestone G retires and a revert restores
mesh/delivery closed: a newer head of the same pull request
mesh/check checking b3484cdcfc50 on 2de011e070d4
The per-node wait is the assigner's, so the indexer's hold states and tools go with its modules state.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-09 20:07:35 +01:00
jschoubben 76b317ed3a Take in round 3's second pass on design 47's G amendment: put-stands timed by the watchman, the put ends by done
A new step would read as a running put to a reverted assigner, the running-before-assigner order is no
edge of the module graph, put-stands needs a writer that survives a stopped assigner, and the tool
runner's revision rests on ADR 0663, now required before the push beside ADR 0661.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-09 20:06:40 +01:00
jschoubben 205992f15c Take in round 3's review of design 47's G amendment: the put's end by step, the readers' order and waits
The reviewers found the put's end written as built when the recovery reads the have, no end for a put of
the running build, nothing said for a put that stands, two of the eight readers outside the served-verb
order, the readers missing the no-view rule and the candidate case, and a revert's restored modules
unsaid. Each is now written as the lead ruled, with rows in tests 61 and 62.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-09 20:02:31 +01:00
jschoubben e73a1a7bcf Take in round 3 of design 47's G review: no module reads the indexer's modules, a put ends itself, ADR 0662 withdrawn
G stops declaring the indexer's modules, so the eight other modules that read it would break unless they
move to the catalog half in the same milestone. A put without hold never ended and pinned its want; ADR
0546 §5 already says it ends once the node runs it, now heard on the node-engine seat's running event.
ADR 0662 decided ADR 0661's switch another way and is withdrawn, so G builds ADR 0661 alone.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-09 19:56:42 +01:00
jschoubben 64cdada15a Developers, design writers and preparers can say a commit others wait on as it lands
mesh/delivery merged: source.main moved to novox/hq@fc6a30070f68
mesh/check being carried to 64cdada15a34 on fc6a30070f68 | from 2de011e070d4
Without SendMessage, milestone G's developers could report only at their end, so a seat definition the
others wait on could not be handed over mid-way.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
2026-11-09 19:55:31 +01:00
jschoubben 4f388e8452 Take in round 2 of design 47's G review: running kept per subject, the merge waits on the Approve alone, ADR 0661 named
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 4f388e845294 on 2de011e070d4 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 85s on 4…
Round 2 found the pass's running events could age out of a new consumer's reach, the merge bullet still
holding for a carry, the checker's own owed acts unanswered, findings that never cleared, and
kept-artifact open to any module and caller. Each is now written as the lead ruled, with its test row.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
2026-11-09 19:21:49 +01:00
jschoubben 2fd96e1e4e Take in round 1 of design 47's G review: hear what nodes run, say what the forge is owed, reach the forge by its seat
The reviewers found a seat's pass waiting on a call to a node that may be away, acts owed to the forge
that could be lost or done twice, a seat git that missed the gate's and half the parts' forge calls and
events, a merge that still judged a check the operator removed, and waits with no bound or condition.
The design now keeps what each node runs from its running events, makes every owed act safe to repeat
with its outcome said and recorded, gives the seat git every call and event with its act verbs granted
by the holder's callers, orders forge, bridge and gate with a way back, adds module-held and
check-could-not-run, numbers tests 56 to 60, and says how the store seat's holder takes its own build.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
2026-11-09 19:21:49 +01:00
jschoubben 7f2f37d9e6 Amend design 47 for milestone G's prepare, so G is built from what the records hold
The prepare found the design behind its records and the code: Placement retired by ADR 0641 while the
design still put two seats in a contracts module never made, ADR 0659 and ADR 0660 not followed, the
checker and the forge bridge reading the gate's state, the delivery parts naming the forge module's
tools, and four hand-wired seat-list readers. Design 47 now writes each as the design's choice under the
record it follows; the hold and the merge turns are removed by the operator's word with their record
still to come, and a session's recovery merge waits for one. The glossary moves Placement's words into
Core and Delivery and adds the seat mesh-grants; the words test refuses Placement as a domain heading
(ADR 0641 §5); design 26 takes the new contracts homes and the seat git's verbs.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
2026-11-09 19:21:49 +01:00
mesh-tools 2de011e070 Merge pull request 'Research writes its requirements from the operator's answers before any option' (#529) from docs/research-requirements into main 2026-11-09 16:21:08 +00:00
jschoubben d61bed820f Research writes its requirements from the operator's answers before any option
mesh/delivery merged: source.main moved to novox/hq@2de011e070d4
mesh/check checking d61bed820f8e on 2de011e070d4
The operator, 2026-11-09: a list of requirements shapes the research, so every
option is weighed against requirements the operator has corrected first.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-09 17:19:15 +01:00
mesh-tools 3cf28e840d Merge pull request 'Watcher definition: a report along the way does not end the watch' (#528) from agents/watcher-keeps-watching into main 2026-11-09 08:49:46 +00:00
jschoubben c57930a759 A watcher reports along the way with SendMessage and keeps watching; only the end, a failure, the bound or a twice-failed call ends it
mesh/delivery merged: source.main moved to novox/hq@3cf28e840de3
mesh/check checking c57930a75988 on 3cf28e840de3
On 2026-11-09 four watchers in one session ended at their first five-minute report,
leaving waits unwatched while a check, a take-in and an answer were still to come.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-09 09:31:22 +01:00
mesh-tools e7f090e333 Merge pull request 'Glossary names no seat's holder: who holds a seat is read from the mesh' (#527) from docs/glossary-no-holders into main 2026-11-08 23:21:13 +00:00
jschoubben dea6b197c5 Glossary names no seat's holder: who holds a seat is read from the mesh
mesh/delivery merged: source.main moved to novox/hq@e7f090e33302
Which module holds a seat is a fact of the mesh's assignments, and a copy
in the glossary drifts (the operator, 2026-11-09: "it shouldn't be in the
glossary"). Each entry keeps its definition and drops the holder; the head
says the holder is read with mesh-module-assigner.assignments and seats.
The words test now refuses a seat held or claimed by a named module; it
failed on twenty statements before they were reworded.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
2026-11-09 00:20:26 +01:00
mesh-tools 1fb7943efd Merge pull request 'Agent definitions: no symbolic links; a review proves a fix against the live mesh (issue 1966)' (#526) from agents/developer-no-symlinks into main 2026-11-08 15:03:27 +00:00
jschoubben e01bf97a17 Reviewer definition: prove a fix against the live mesh, never a re-creation
mesh/delivery merged: source.main moved to novox/hq@1fb7943efdb0
mesh/check failed: ace: not composed on ace: the composer at the node-engine's trunk 875b4ee5ce38d4bb96089ba3b05962b450… | e01bf97a17f9 on 1fb7943efdb0
Twice on 2026-11-08 a review re-created the indexer's manifest validator without
its seat list and passed a fix the live one still refused (issue 1966); the
operator asked for this line in the same change.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
2026-11-08 01:58:58 +01:00
jschoubben b6b657917e Developer definition: place siblings without symbolic links
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: mesh-build-go: the change's manifests cannot be judged: the seat list is not read (the seat list is … | b6b657917ed6 on aaad9daa69bb
A developer placed a sibling worktree under /var/tmp with a symbolic link
(2026-11-08, issue 1966); the mesh's hard rules allow none anywhere.

Claude-Session: https://claude.ai/code/session_01NcbgfFfQ4Z9UE5jK32PmE5
2026-11-08 00:34:23 +01:00
mesh-tools aaad9daa69 Merge pull request 'Design 47's B0 says its one new seat verb is delivered first, alone (issue 1921)' (#525) from feat/b0-checking-program into main 2026-11-07 00:32:44 +00:00
jschoubben ec71485755 Design 47's B0 says its one new seat verb is delivered first, alone (issue 1921)
mesh/delivery merged: source.main moved to novox/hq@aaad9daa69bb
mesh/delivery-group group feat/b0-checking-program merged: every member is merged
mesh/check being carried to ec714857556d on aaad9daa69bb | from 885099eebbbd, mesh-catalog 980ade933853, mesh-delivery 2d201f781ced, mesh-sdk c1b50e99…
B0's first check failed because the running indexer did not know the checker
seat's verb input and the composition took its verdict over the check's own
validator. The operator chose to deliver that verb alone first (2026-11-07).

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-07 00:53:27 +01:00
mesh-tools 885099eebb Merge pull request 'Design 47's B0 says what was built: the checking program on every check, its input by the checker's verb (issue 1921)' (#524) from docs/47-b0-row into main 2026-11-06 23:01:03 +00:00
jschoubben d676466578 Design 47's B0 says the checker is delivered first and a failed seat list ends the check there (issue 1921, B0 review round 2)
mesh/delivery merged: source.main moved to novox/hq@885099eebbbd
mesh/check being carried to d676466578c4 on 885099eebbbd | from dd55153cf355
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 23:56:30 +01:00
jschoubben da828c147a Design 47's B0 takes in round 1's rulings: the input asked of the checker's verb, the cycle rule the program's
A build module reading the checker's state broke ADR 0596 §2, and no record names the
cycle rule as an exception to ADR 0656 §5; the code now follows both, and so does the
design, with the answer's bound that the build module now enforces.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 23:51:08 +01:00
jschoubben 35b92fde78 Design 47's B0 says what was built, so the review reads the design the code follows
The first code review round found the B0 section described the input and answer in the
build seat's objects, no running rule after B0, and a carry's program built at merged heads.
It now says the input is kept in the checker's own state and the answer rides the step key,
names the cycle rule and a failed program's wording as the two exceptions until milestone G
with why, and says where a carry's program is built and when a trunk move asks a new build.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 23:23:53 +01:00
jschoubben 60369fddee Design 47's B0 takes the lead's rulings on its open points
The begin bound of a run ask, the referenced objects' size, the kept
programs' tools and their 30-day sweep, and the versioned input were left
open by the first amendment; each is now one of B0's choices with its
reason, as ruled, so the developers and the review read one text.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 22:10:14 +01:00
jschoubben 12489d580c Design 47's B0 names the trunk's homes and runs the checking program on every check
Row B0 named mesh-delivery-contracts for the ask `run`, but the registry group
creates that module and joins milestone G, after B0; on every trunk before G
the seat's types are mesh-sdk's go/delivery and go/composer, which G's merge
moves (ADR 0634 §4, ADR 0635 §4). The bootstrap section now says every check
runs the checking program (a lone pull request a group of one, a carried
verdict included), that the program makes the heads' seat list, and carries
the seams review's three findings on B0's prepare as the design's own choices
with their reasons: a dropped `run` ask fails by name within a bound, kept
programs have tools and a sweep, and the input goes by reference with a size
bound.

The glossary gains "seat list", used as a term by ADR 0635, ADR 0636 and
design 47 without an entry, retiring "seat set" in hq; four designs take the
word, and issue 299's narrative keeps its day's word by an allowance row.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 22:08:31 +01:00
mesh-tools dd55153cf3 Merge pull request 'Design 47 follows ADR 0656 and ADR 0644: the delivery's own parts checked and delivered by their own change (issue 1921)' (#523) from docs/47-two-graphs into main 2026-11-06 20:42:02 +00:00
jschoubben 27d31d5490 Merge remote-tracking branch 'origin/main' into docs/47-two-graphs
mesh/delivery merged: source.main moved to novox/hq@dd55153cf355
mesh/check being carried to 27d31d54908f on dd55153cf355 | from 5ff1298e2173
2026-11-06 21:37:41 +01:00
jschoubben 8a85a2fd54 Design 47 records its review by architectural domain of 2026-11-06
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 21:37:41 +01:00
jschoubben 49ecfb6cdf Design 47 keeps the catch-up tool in the restore point
At a revert the manifest being applied is the one from before the change,
which names no catch-up tool, so the node agent could not find it there. The
newer build's manifest declares it beside the kept data whose form it changes,
the backup records it in the restore point, and the node agent takes it from
there.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 21:37:09 +01:00
jschoubben 25d9f8df61 Design 47 names the forms milestone G changes and how a revert catches up, read from the code
Round 3 found the revert section left open which kept data G changes, the order
a revert needs, and whether the parts of before can catch up. Read on main, the
indexer catches up from its forge consumer, whose stream keeps only 7 days, so
the restore also runs its existing take-in of every repository; the assigner
keeps nothing to restore and computes the wants again; the gate's start reads
the forge, the pending answers and the source again. The wants keep their form
and are never restored, since that would send nodes to older builds.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 21:35:44 +01:00
jschoubben af6695161c Design 47 writes only the new forms, and reverts by restoring the parts' kept data
The operator looks only forward, yet wants reverts supported. So the indexer,
the assigner and the gate no longer write the old keys, wants source or merges
and turns beside the new forms, and milestone H goes. A revert is delivered like
any change: ADR 0404's backup covers only the items on a module's node, so the
delivery system gains a restore point of a module's kept data on the bus,
taken before a change of its form and restored beside the live data before a
reverted build starts, after which the part catches up forward; a missing
restore point holds the revert and asks the operator.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 21:30:35 +01:00
jschoubben ce3fe6aa3b Design 47 takes in round 2: waits timed from their start, composing held per node by the assigner, stores sized
Round 2 found a wait whose holder stopped could go untimed, a node away holding
others' wants, a new field in the want that an old node-engine cannot read,
stores sized for whole revisions, a full store that never cleared, a ledger
sized without its operations, and a kept build a program could rewrite. The
design now says wait-held at a wait's start with its bound, starts each clock
when the new version's build is done, holds a node's wants in the assigner
until that node's new node-engine runs, stores an unchanged entry once, sizes
the ledger from its operations, clears full on room, keeps only builds from a
run step that runs nothing else, names every change of the checking program's
fingerprint, and makes the switch safe to run twice.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 21:23:25 +01:00
jschoubben de7358a2c5 Design 47 takes in round 1 of the review by architectural domain
The reviewers found the amendment left the stores to grow without a bound,
the waits without an owner's tools or events, a rollback that would send nodes
back to old builds, a newer indexer that could never be wanted, events any
account could forge, and the node-engine reading every node's placements. The
design now chooses, each with its reason: a default record retention and caps
with fill conditions, a revision removed when no record pins it and it is not
current, waits held and released by each step's own part with their events,
the build module's wait ending on the agreed toolchain, the old keys kept
written until a named milestone H for a rollback, the running indexer making
the newer indexer's entry, rule changes named by the running checker from
outside the sandbox, and subjects and grants that let each part say only its
own. The review verb leaves milestone G for issue 1782, and the watcher's
definition goes back to main, since it contradicted an approved instruction.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 21:18:14 +01:00
jschoubben 8d98d54bd2 Design 47 tests each change ADR 0656 makes, and leaves the review verb as ADR 0493 §5 has it
ADR 0656 v6 no longer changes ADR 0493 §5: who may review is another question,
waiting on issue 1782, so the design stops saying the reviewer comes from the
caller's bus account or that the author is refused. Each change ADR 0656 makes
in an earlier record now has its own acceptance test, so the milestone proves
every one of them.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 20:57:46 +01:00
jschoubben dbc047bf56 Design 47 follows ADR 0656 alone and writes its own choices with their reasons
The operator ruled that research 085 graduates into one record per question:
ADR 0656 holds the whole answer and supersedes ADR 0645 to 0648, and the short
records 0649 to 0655, 0657 and 0658 are withdrawn. The design now cites ADR 0656
or ADR 0644 for what they decide, and what they leave is no longer an open
point but this design's choice, listed with its reason, so the review can
judge each one.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 20:56:35 +01:00
mesh-tools 5ff1298e21 Merge pull request 'Playbooks 01 and 02: one record per question an effort answers; design choices live in the design' (#522) from docs/one-record-per-question into main 2026-11-06 19:55:23 +00:00
jschoubben c1cf0149b6 Design writer and hq-graduate follow the new step 2; the playbook names no record by number
mesh/delivery merged: source.main moved to novox/hq@5ff1298e2173
mesh/check being carried to c1cf0149b6d9 on 5ff1298e2173 | from 1ca5a3a1331f
Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 20:52:38 +01:00
jschoubben 5e3b836404 Playbooks 01 and 02: one record per question an effort answers; design choices live in the design
Research 085 graduated into eleven records where it answered two questions:
the rule 'one record per choice that could be reversed on its own' made
every design choice a record of its own.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 20:51:00 +01:00
jschoubben cad2693361 Design 47 carries what research 085's records no longer say, and cites the short records
The operator ruled that a decision record is a question, its options and the
choice made, so research 085's records were cut to their choices: ADR 0656,
0657 and 0658 supersede ADR 0645, 0647 and 0648, and ADR 0652 to 0655 split
ADR 0646. What the long forms carried beyond the choice now lives here as
design text and acceptance tests: the graphs' keeping in the old objects
sections and their retention by the gate's pin and free events, the
candidate's graphs, the step events' fields, the first tier and the sandbox,
the trunk's validator beside the change's own, the waits' holders and tools,
the delivery record's consumer, switch, rollback, node away, small fix and the
Approve held at its heads, the milestone ask and events, and the trace's link
kinds. Each fact cites the record that decides it, or says it is this
design's, so the review can tell the two apart.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 20:33:24 +01:00
jschoubben e99094ab94 Design 47 and the glossary follow ADR 0644 to ADR 0651: two graphs, one revision view (issue 1921)
Research 085 found that each acting part handled a change to itself by a case
of its own, and that parts disagreed on the state they read: 7 of 24 failures
since 2026-11-01. Design 47 now describes the catalog graph and the computed
assignment graph kept per revision, a change delivered as its graph
difference, acting parts' rules on their seats as bounded waits, the check on
the change's own acting parts, the revision view, the gate's delivery record
and the trace's links, with the bootstrap delivery before that milestone.

It also settles what the records leave to this amendment, as proposals for
the review: the acting rule's fields and mesh-sdk cost, the fingerprint
handover, the build seat's ask that runs a program in the sandbox, the first
tier's bound, the step events' subjects, and the tools the seams review
listed. The glossary gains the seven words of research 085's Domains. The
watcher's definition learns that a quiet five minutes is no reason to end,
since its final report is its only channel.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 19:41:16 +01:00
mesh-tools 1ca5a3a133 Merge pull request 'Design 47 follows ADR 0633, 0637, 0638 and 0639: the take-in by the indexer it names, and the group try (issues 1966, 1921)' (#521) from docs/47-try-and-take-in into main 2026-11-05 20:06:31 +00:00
jschoubben b926db338d Design 47 cites ADR 0639 §9 for the hand drop, so both hand acts are said to clear take-in-waits
mesh/delivery merged: source.main moved to novox/hq@1ca5a3a1331f
mesh/check being carried to b926db338dcf on 1ca5a3a1331f | from 674f88f9abb8
ADR 0639 v9 gives the drop its own clause and moves the checks to clause 10.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 20:26:55 +01:00
jschoubben d61e18de41 Design 47 cites ADR 0639 for the review's rulings, and fixes the indexer's build at its start
The rulings of round 1 now rest on a record, clause by clause, instead of on the
review; the indexer's own build is set once from the build it was started with, so a
process being replaced cannot take in with its old manifest validator (ADR 0639 §7).

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 18:46:11 +01:00
jschoubben 8b923e6bff Design 47 takes in round 1 of the review by architectural domain
The try and the take-in described seams, failure paths, live cases and the health
chain less exactly than the release builds them: the reads that stay until issue
1966, the bus form of pending and the tryevent contract, the try's tools against the
four lines, a checker_tries that does not answer, try events never lost, what a
narrowed try keeps, the finding and the condition keeper, and both live cases. Rulings
no record decides are marked as the review's.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 18:38:05 +01:00
jschoubben 26b6dcf4f6 Design 47 cites ADR 0638 and its recovery order, so a try's candidate survives an indexer restart
ADR 0638 makes the candidate from the checker's events and decides that the indexer
creates its consumer, asks checker_tries and fixes the candidates before it handles
an event; the design now says so and how it is checked.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 18:32:49 +01:00
jschoubben 5688a2efa1 Design 47 names the try's events, so the indexer's path to a try is a stream it consumes
The checker says a whole try's start and end as try.started and try.ended on its stream
checks, and the indexer recovers open tries through checker_tries; the record changing
ADR 0637 §4 carries this and is cited until its number is known.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 18:31:21 +01:00
jschoubben 7e33e9a15f Design 47 follows ADR 0637 and ADR 0633: a group try, and a revision taken in by its own indexer
A try now checks a delivery group at exact commits with the check's own code, and a
merge's take-in waits for the indexer its revision names, so design 47 says both where
its checks and its source revision are described. The indexer hears a try on the
checker's events rather than reading its state (ADR 0596); the record changing
ADR 0637 §4 to say so is to follow. The glossary gains try and pending revision.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-05 18:23:09 +01:00
mesh-tools 674f88f9ab Merge pull request 'Designs and glossary follow ADR 0634, 0635 and 0636: shared code lives with its owner, the seat list from the bus (issue 1966)' (#520) from docs/domain-sdk into main 2026-11-05 13:01:50 +00:00
jschoubben b81c05c36e Designs follow ADR 0636 as accepted: a waiting reader, the bootstrapped three
mesh/delivery merged: source.main moved to novox/hq@674f88f9abb8
mesh/check being carried to b81c05c36e95 on 674f88f9abb8 | from 160f384e4fcd
The accepted record renumbers its clauses and settles what the review asked: a
reader without the seat list starts nothing new but keeps running, its wait
reaches the operator through the condition keeper and the router, which are
bootstrapped with mesh-catalog, a missing definition blocks new claims only,
a move of a definition is a handover, and the private network's value is the
seat's contract. The renames also refuse old names in consumes and say what an
old installer meets; design 47's done milestones keep the name they shipped.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-05 13:40:32 +01:00
mesh-tools 160f384e4f Merge pull request 'Watcher definition: what a machine runs is read from the machine (issue 1585)' (#519) from agents/watcher-reads-machines into main 2026-11-05 12:28:43 +00:00
jschoubben e1713bf59a Designs follow ADR 0636 v4: the seat-definitions verb, the indexer bootstrapped
A full restart must bring back the part that serves the seat list, so the
bootstrapped definitions now name mesh-catalog beside the foundation's seats,
with a test of it. A seat defined outside the mesh's own contracts is taken
from its own contracts module, not refused. The bus server's resources and the
placement check get their owners, and two stale seat names in design 26's old
table are corrected.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-05 13:22:52 +01:00
jschoubben cb1b055b80 Designs follow ADR 0636 and the first review's rulings
The review by architectural domain found that readers of the seat list would
name the indexer, never hear a removal, could deadlock a full restart, and
that any folder could define a seat again; the-private-network's holder sat
in mesh-sdk. ADR 0636 and the lead's rulings answer these, and the designs
must say so before develop.

- design 26: the seat list through a verb and two events of mesh-catalog; a
  reader without it acts on nothing new; a seat defined only in its domain's
  contracts module; states are any holder's, caller-written only for events
  and logs, each with its size cap, declared in the holder's requires; the
  renames move no state; the group's check reads every member's head;
  Connectivity in the mesh-networking repository, held by mesh-wireguard;
  acceptance tests by level and repository; two stale table rows corrected
- design 47: the composer in mesh-delivery-sdk, the bus composer's seat
  parts in the grants part, placement's contract and check by their seat
- design 20: a module's own package is fingerprinted by its own files
- glossary: mesh-placement retired for tool descriptions too; the
  composition renamed to mesh-delivery-sdk's; six seats given their domains

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-05 13:20:45 +01:00
jschoubben c70b275187 Watcher definition: the status view, not the delivery view; the assigner's want verb named (review round 1)
mesh/delivery merged: source.main moved to novox/hq@160f384e4fcd
mesh/check being carried to c70b27518712 on 160f384e4fcd | from d1e2059dab3f
Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
2026-11-05 12:55:21 +01:00
jschoubben b165eb9438 Watcher definition: what a machine runs is read from the machine, not the delivery view's take-in line (issue 1585)
A watcher of 2026-11-05 reported that four modules taken in by hand reached no machine, from the view's
'not yet taken in' line; node-engine.composition showed all four running their new builds.

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
2026-11-05 12:54:10 +01:00
jschoubben 7d77102db0 Designs and glossary follow ADR 0634 and ADR 0635: shared code lives with its owner
The registry milestone now moves the Delivery domain out of mesh-sdk, creates a
contracts module per domain it moves and the first domain SDK, defines 25 seats
whole with their states, reads the seat list from the bus and renames two seats.
Design 26's *Where a seat's code goes* is what the review and the developer read
to place every new name, so it must say this before develop starts.

- design 26: definitions carry states; the 25 seats by contracts module; the
  seat list from the bus; the two renames; the domain SDK and a module's own
  package; the transfer in the Delivery domain's SDK; checks per ADR 0634 §10
  and ADR 0635 §6
- design 20, playbook 06: the homes of shared code, the first that fits
- design 47: the seat mesh-module-assigner
- glossary: domain SDK added; seat-set package retired; mesh-module-assigner,
  mesh-wants, mesh-node-enroller and the-private-network in their domains
- preparer and code reviewer definitions: the homes, without the retired word

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-05 12:46:06 +01:00
mesh-tools d1e2059dab Merge pull request 'Developer definition: the manifest validator gets one predefined key list for both cases, always (issue 1585)' (#518) from agents/validator-key-list into main 2026-11-05 01:28:03 +00:00
jschoubben 304d17b561 Developer definition: the manifest validator always gets a predefined key list, as the checker passes one
mesh/delivery merged: source.main moved to novox/hq@d1e2059dab3f
mesh/check being carried to 304d17b5612c on d1e2059dab3f | from 331e5707922b
The review of hq #517 noted that the checker always passes the predefined key list (the change's when it alters it,
else the trunk's), while the text said only "when the change alters it" (issue 1585).

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
2026-11-05 02:15:45 +01:00
mesh-tools 331e570792 Merge pull request 'Developer and reviewer definitions: validate every manifest a change touches before handing back (issue 1585)' (#517) from agents/dev-validates-manifests into main 2026-11-05 00:44:47 +00:00
jschoubben 749249466f Developer definition: the validator run as the checker does it (review round 1)
mesh/delivery merged: source.main moved to novox/hq@331e5707922b
mesh/check being carried to 749249466f8c on 331e5707922b | from 6b8b6b08e39a
Built at mesh-delivery's trunk unless the change alters the validator; raw
manifests in the two cases change and trunk; every manifest whose verdict
differs is reported, an unchanged one included (ADR 0579 §2).

Issue 1585.

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
2026-11-05 01:41:12 +01:00
jschoubben c4b98b2a03 Developer and reviewer definitions: validate every changed manifest before handing back
The take-in group's first check failed after a full build on a manifest the
validator refused (an indexer's own stream outside the bus's forms); no local
run had validated the changed manifests. The developer now runs the manifest
validator as the checker does and reports each verdict; the reviewer asks for it.

Issue 1585.

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
2026-11-05 01:38:40 +01:00
mesh-tools 6b8b6b08e3 Merge pull request 'Say manifest validator, not judge: the glossary gains the word and retires the noun' (#516) from docs/manifest-validator into main 2026-11-04 22:39:51 +00:00
jschoubben f91a5a7c75 Name the composing program built from a change, a probe for one health check, and ADR 0579 §2's whole case (review 1)
mesh/delivery merged: source.main moved to novox/hq@6b8b6b08e39a
mesh/check being carried to f91a5a7c75e5 on 6b8b6b08e39a | from f0c05d1edb84
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 23:37:21 +01:00
jschoubben aac8494ca3 Say manifest validator, not judge: the operator asked why a new word was needed for the indexer's rules
The glossary gets the entry in the Delivery domain with the records that decide it, and retires the noun in
hq's prose on a Not line; the verb stays. Where "judge" named something else, the design now uses that
thing's word: the node-engine's look at health, an account or the network; the composition check built from
a change; the claude-code module's judgement tool. Five issue narratives keep the word of their day.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 23:32:13 +01:00
mesh-tools f0c05d1edb Merge pull request 'Where new code goes is a design question: playbook 08, the developer and the code reviewer check it against designs 20 and 26' (#515) from docs/placement-is-a-design-question into main 2026-11-04 22:23:45 +00:00
jschoubben b525f1059b Name the design section, not its number; the preparer and playbook 07 place new code the same way (review 1)
mesh/delivery merged: source.main moved to novox/hq@f0c05d1edb84
mesh/check being carried to b525f1059b69 on f0c05d1edb84 | from d4ae72782336
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 23:20:38 +01:00
jschoubben 69f95f551d Where new code goes is a design question: the playbook, the developer and the reviewer check it against designs 20 and 26
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 69f95f551d19 on d4ae72782336 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed in 17s on 4…
A small fix's lead placed a seat's type in mesh-sdk by its own handover (issue 1748), against design 26;
the developer built the handover and the reviewer checked against it, so it passed review and its check.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 23:17:57 +01:00
mesh-tools d4ae727823 Merge pull request 'Designs 20, 25, 26, 32, 51, 55 and playbook 07: the registry milestone as built (issue 1966)' (#514) from docs/registry-milestone-designs into main 2026-11-04 18:19:24 +00:00
jschoubben 304ce84cac Designs 32, 25: a state may be read-only to the tools; nats_requests reads spent so
mesh/delivery merged: source.main moved to novox/hq@d4ae72782336
mesh/check being carried to 304ce84cac18 on d4ae72782336 | from 273d608d9faa
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:10:48 +01:00
jschoubben e0a70a468b Designs 25, 51: name the spent state; a shared-digest tag is recorded as kept
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:08:58 +01:00
jschoubben 571c0a3e68 Designs 25, 51: follow review round 2 (decided via uses, spend record apart, fix checks, genesis gap, kept answer)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 18:08:36 +01:00
jschoubben b0a9fa14da Playbook 07: a group's pin is for GOWORK=off; the check builds against the group's heads (ADR 0573), so an older pin is not refused
The line said the checker refuses an older commit of the same branch; that refusal was removed as a fault of the check
(issue 1585). Found pinning the registry milestone, where mesh-sdk and mesh-contracts import each other.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 17:49:32 +01:00
jschoubben b3a9970ac7 Design 51: name ADR 0627 among its decisions
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 17:33:15 +01:00
jschoubben 5499c3546b Design 51: cite ADR 0627 for the holders' shared receiving side, ADR 0611 §3 for the drop
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 17:32:54 +01:00
jschoubben c3d8367b33 Designs 25, 51: write what review round 1 settled (acted key, ask lifetime, loopback bootstrap, shared idle drop, shared-digest tag, forge account removal)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 17:32:21 +01:00
jschoubben 8f75606167 Designs 25, 51: follow review round 1 of the registry milestone
Design 25 counts seven first-view tools, the indexer's graph granted to the
assigner alone (ADR 0631); the bus module's process wakes on its own decided
and acts on a warrant once (ADR 0259 §6, ADR 0620 §2) instead of a timer, and
the tools watch their request key; §12.7 carries the rows, and its first-view
row, split by an earlier edit, is one row again. Design 51's incoming store
asks the objects template's most, 8 GiB, with no lifetime; the holder drops an
idle transfer itself (ADR 0611 §3).

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 16:23:11 +01:00
jschoubben 901c92a924 mesh-developer: run the repository's own checks whole before reporting a head, and start no agents of your own
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 15:51:25 +01:00
jschoubben bdc5ae4fe6 Design 26: the seat definitions' generator is one program in the Go build module, run by both build modules
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 15:09:57 +01:00
jschoubben aced03a287 Design 32: a package is named by its name in its own language, one name across the indexer, builder, registry seat and users
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 14:46:30 +01:00
jschoubben da1c7f30a9 Designs 25, 26, 20, playbook 06, glossary: follow ADR 0625 to 0627 for the registry milestone
Design 25 § 12.3 now says how the bus module's process really receives a person's
request (its own state requests, the warrant verified by the process itself), the
process account with keeps it holds beside the snapshot account, and that
mesh-broker's events go on the shared stream like every seat's, with the 30-day
history its own stream. Design 26 gains where a seat's code goes: a package per seat,
the seat-set package in mesh-contracts, the seven client packages and the
seat-neutral parts transfer; design 20 and playbook 06 tell a module's author the
same, and the glossary gains client package and seat-set package. mesh-developer
stops at two thirds of its context, as the milestone's agents learned.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 13:19:57 +01:00
mesh-tools 273d608d9f Merge pull request 'Designs 51, 32: the windows wake on the node-engine seat's manifest-reads-changed (ADR 0618), and every build stays readable by every counted node-engine (ADR 0624)' (#513) from docs/cite-adr-0618 into main 2026-11-04 09:08:19 +00:00
jschoubben cac55dc254 Design 51: follow ADR 0624, every build keeps each counted node-engine's form
mesh/delivery merged: source.main moved to novox/hq@273d608d9faa
mesh/check being carried to cac55dc2543a on 273d608d9faa | from 6cd965b3cdf6
ADR 0624 supersedes ADR 0623, which could find the node-engine's own
builds only by the module's name. Every build now keeps each form a
counted node-engine states, the oldest in the single reference, and a
node is held only when it reads none of them.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 09:59:44 +01:00
jschoubben 01d4f2781e Design 51: keep the node-engine's own build readable by away nodes (ADR 0623)
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 01d4f2781ef0 on 6cd965b3cdf6 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed in 33s on 4…
Under ADR 0618 alone a node away with an old node-engine would be held
from the node-engine's own build in the new form and could never update
itself. ADR 0623 keeps every counted node-engine's form in those builds
and moves them to a node that reads any one of them.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 09:48:56 +01:00
jschoubben 48fa0a5a48 Designs 51, 32: name the manifest-reads grant and who holds the old-form window
The upgrade and security review asked that each window's caller be
granted the event through its invokes, that an away node be held by the
assigner rather than the old-form window, and that a node-engine not yet
serving manifest-reads keep a window open by name.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 09:46:46 +01:00
jschoubben f47744a8b6 Designs 51, 32: cite ADR 0618 for the windows' wake and the reference form
ADR 0618 is accepted: the node-engine seat's manifest-reads verb and its
manifest-reads-changed event wake the old seat name's, the old-form and
the rename windows, and the builder writes a new form only once every
present node-engine states it. The next delivered stays only for the
rename window's case the record does not cover.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 09:44:31 +01:00
mesh-tools 6cd965b3cd Merge pull request 'Designs 25, 51: the registry milestone's prepare points (a failed pull retried on the store holder's settled, the owners' read tools, verdicts as a first view, ADR 0622)' (#512) from docs/registry-prepare-points into main 2026-11-04 08:40:09 +00:00
jschoubben 4f6b9f0ca5 Designs 25, 51: bound the race retry to a settled heard after the pull started
mesh/delivery merged: source.main moved to novox/hq@6cd965b3cdf6
mesh/check being carried to 4f6b9f0ca5ff on 6cd965b3cdf6 | from e7ac664afff7
A retry on any settled ever heard would apply again on every failure; the
pull's start bounds it to one retry per settled, kept in memory. The seat and
holder beside a routed image are outside a container's digest, so naming
them replaces nothing, and the verdicts gap case is named.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 02:16:03 +01:00
jschoubben a838b6b3d3 Design 51: a routed image names its seat and holder, so the retry matches settled by it
The declaration carried only the store's address, so the node-engine could
not tell whose settled ends a failed pull without naming a module.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 02:13:10 +01:00
jschoubben 6c6b2c5cc1 Designs 25, 51: verdicts a first view of its own; every node hears the holder settle
The store's holder runs on one node, so a node-engine elsewhere must hear its
settled too, and a pull failed just after it settled must not wait. The
grants part's verdicts read is a first view under ADR 0622, not an ordinary
read tool, and the node-engine's reads granted in code are said, so the
live count of zero declared reads is not taken for more than it is.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 02:12:38 +01:00
jschoubben fb7aa0915f Design 25: the grants part reads the indexer's verdicts through a read tool
The grants part is the one reader of the indexer's verdicts, and no first
view covered that bucket, so its move had no path. It now takes its first
view through indexer_read_verdicts and follows the manifest events.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 02:05:51 +01:00
jschoubben 74b69857a1 Designs 25, 51: retry a swapped pull on settled; owners' read tools get names of their own
Neither condition-cleared nor delivered comes during the store holder's own
delivery, so the node-engine retries on the status view's settled for that
holder, heard through the grants part's list, and at its own start. The
status view keeps its tools and answers them through the owners' new read
tools, so no name moves between modules.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 02:04:51 +01:00
jschoubben 0e8a698025 Designs 25, 51: the owners take the view's six tool names; distribution's delivered retries a failed pull
The status view can no longer answer its six tools named for the owners'
buckets once it stops reading them, so the owners' tools take the names. A
finding held during distribution's own delivery raises no condition and so
no clear, so the node-engine also tries a failed pull again on the status
view's delivered for distribution, and nothing waits for a hand compose.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 01:59:59 +01:00
jschoubben 01d2ea0855 Designs 25, 51: settle the registry milestone's prepare points
A pull that falls in distribution's swap is tried again on the condition
keeper's clear of the store's holder's condition, so the delivery needs no
hand compose and no timer. The owners' tools for the other readers' buckets
are named, the reads count has its tool, and the test paths match main.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 01:58:54 +01:00
mesh-tools e7ac664aff Merge pull request 'Designs 25, 26, 32, 51, 55: the registry milestone's designs after the review by architectural domain (ADR 0615-0621)' (#511) from docs/registry-milestone-designs into main 2026-11-04 00:46:48 +00:00
jschoubben 457ac2eabd Designs 25, 26, 51: cite ADR 0620 and ADR 0621; the owners serve their first views as tools in a runtime of their own
mesh/delivery merged: source.main moved to novox/hq@e7ac664afff7
mesh/check being carried to 457ac2eabdcd on e7ac664afff7 | from 276cbef0c476
ADR 0621 keeps the manifest form as it is, so the process-subject form, its mesh-sdk change, the
open point on who grants it and the push's wait on a judge release all go; the per-verb grants and
the record's four checks take their place.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 01:43:36 +01:00
jschoubben 216e0e5102 Design 26: keep the seats' own events apart from their holders', since no event moves subject in this milestone
The four seats defined whole list only what their seat subject carries as the seat's; the
holders' events stay under their own subjects, checked by a fixture from the running holder.
The vault seat drops the two verbs no holder serves.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:53:30 +01:00
jschoubben f91f4879fc Design 25: cite ADR 0619, accepted, for the foundation that keeps the old sections
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:49:42 +01:00
jschoubben c8e6eb5edf Designs 26, 51, 25: define whole the four seats the milestone adds verbs or events to
The operator chose to move them now (2026-11-04), so a seat's new verb never lands at the seat
list's grain. Each is listed with what its holder's manifest on the trunk serves today; the
builder and the indexer hold no seat, so their events stay module events.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:47:45 +01:00
jschoubben a81334b1af Design 32: the rename window closes on the next delivered while ADR 0618 is only reserved, and no waiter on the vault's rotation wakes on own-retired
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:46:40 +01:00
jschoubben 546a18a20e Design 25: answer the third review (views families on the old streams sections under a record extending ADR 0595 §10, the tool runner's first-view tools on the mesh MCP server, the running judge takes the process-subject form in)
The assigner must start on a fresh mesh before the bus module can answer, so its new stream
stays on its old section; a new indexer can never judge its own manifest, so the running judge
must accept the form first. The glossary stops counting the mesh MCP server's tools.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:46:25 +01:00
jschoubben a9745b63cb Design 51: answer the third review (ADR 0618 only reserved, a module's images pulled before its containers are replaced, public package reads kept, levels on every new case)
The windows close on the next delivered until ADR 0618 is accepted; distribution's first
delivery needs the node-engine to pull every image first, since its registry serves every pull;
the operator kept public reads, so ADR 0346 stands and the route refusal goes.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:42:35 +01:00
jschoubben 021fca5e6d Design 32: the rename window wakes on the node-engine seat's event of ADR 0618, as design 51's old seat name does
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:32:24 +01:00
jschoubben fa2d199b16 Design 51: answer the second review (event wakes for every floor source, versions within the state cap, the direct-write and old-form windows shaped as the old name's, release on the caller list, distribution's own family)
Also writes the operator's answer of 2026-11-04 on anonymous reads (mesh network only, the public route refusing
the forge's package paths), the expected count of three builds per module, and the revert's order by the graph.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:31:30 +01:00
jschoubben cdfddff72e Design 25 § 12: owners' events carry the value whole, a deleted module's entries become orphans, the first views get named tools and a live bound
Answers the second review's V1-V9 by architectural domain: the assigner's views in the new form, newer by revision
alone, what a reader does when a recreate takes revisions back, a hand re-say of manifest-accepted, and the judge
question under the open point on issue 1966.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:29:30 +01:00
jschoubben 939d000327 Design 32: name the vault's event for retire and its family
Answers the second review's R1 by architectural domain: own-retired on the module events stream under the vault's subjects.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:28:07 +01:00
jschoubben e51e6961e3 Design 55 § 11: say what from: machine gives the page's port, that local processes reach it until issue 1968, and that the route proxy runs on its node
Answers the second review's K1 by architectural domain.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:28:07 +01:00
jschoubben 0168a545c5 Design 51: cite ADR 0617 for the unnumbered-first image and hold; narrow hold to what the sweep keeps, never an image
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:12:24 +01:00
jschoubben 0ae7fc2e57 hq-design-writer: it calls no verb that writes on the mesh; what a record must carry goes to the caller
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:11:23 +01:00
jschoubben da823becbe Design 55 § 11: answer the first review (the router's stream kept and capped, the page's port reached only by the route proxy, a re-read tool per source)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:11:15 +01:00
jschoubben 3b1278b3bc Design 32: answer the first review (the checker invokes renames and waits for it, retire's caller list and warrant, retired events, tests, the away node's window)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:10:57 +01:00
jschoubben 4710fd18c6 Design 26: answer the first review (the seat list's grain as ADR 0613 has it; out of step cleared only by the same contracts module)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:10:40 +01:00
jschoubben 34384b6c9c Design 25 § 12: answer the first review of the milestone amendment (the bus module's first view against replays and an unanswered tool, the foundation readers' bounds, families and tools, the tests they join, the open grant)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:10:19 +01:00
jschoubben 2d48b85c0e Design 51: answer the first review of the milestone amendment (builds counted from the records, the write side's window and outage, the revert's open points, holds and the old name's events, live proof)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:09:56 +01:00
jschoubben 01a0a3a4d9 Amend designs 51, 26, 32, 25 and 55 for the registry milestone
The registry move, seat definitions, declares, bus state and the knowledge base
ship as one milestone (ADR 0612, 0613), ordered by the module graph's edges
(ADR 0614), with the foundation parts' first view (ADR 0615) and the overview
page (ADR 0616). The designs said four deliveries, a seat-definition source
nobody serves, a checker naming mesh-config, and left the bus module's first
view, caller and deletion paths open; they now say what the milestone builds.
Adds the glossary words it uses, playbook 02's cites link for an abandoned
effort, and the hq-design-writer agent definition.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 00:02:43 +01:00
mesh-tools 276cbef0c4 Merge pull request 'Graduate research 081: registries are seats published into over the bus, the builder numbers every build, seat definitions on the bus, contracts modules' (#510) from docs/graduate-081 into main 2026-11-03 20:12:24 +00:00
jschoubben 5b2cead5e3 Designs 51 and 32: answer the third review (publish-part listed with its callers, the node-engines' reports through the status view, C24 tested)
mesh/delivery merged: source.main moved to novox/hq@276cbef0c476
mesh/check pass 5b2cead5e3bc on 276cbef0c476 | carried from f6bd4246b0d7 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-03 18:10:19 +01:00
jschoubben 9845b78e5b Designs 51, 26, 32: answer the second review by architectural domain (ADR 0611, caller identity, node-engine reports, families and retention, Go references); add each design's review record
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-03 18:08:00 +01:00
jschoubben 20157fcafe Designs 51, 26, 32: answer the first review by architectural domain (ADR 0596 paths, holder-side caller lists, the file in the publish verb, findings for the condition keeper, windows on node-engine reports)
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-03 18:02:09 +01:00
jschoubben 27063a492b Graduate research 081 into designs 51, 26 and 32: registries are seats published into over the bus, the builder numbers every build, seat definitions on the bus, contracts modules (ADR 0605-0609) 2026-11-03 17:10:36 +01:00
mesh-tools f6bd4246b0 Merge pull request 'mesh-watcher waits on the event of the part that owns the fact it watches' (#509) from docs/watcher-owner-event into main 2026-11-03 13:27:21 +00:00
jschoubben 33d25c5074 mesh-watcher: the glossary's words, the delivery gate and the status view
mesh/delivery merged: source.main moved to novox/hq@f6bd4246b0d7
mesh/check pass 33d25c50742e on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-03 13:29:36 +01:00
jschoubben bf5ef24ea3 mesh-watcher waits on the event of the part that owns the fact it watches (the operator, 2026-11-03) 2026-11-03 13:28:44 +01:00
mesh-tools 979bf3f19c Merge pull request 'hq ignores .work/, where every subagent's worktrees and context files live' (#508) from docs/groomer-and-gitignore into main 2026-11-03 11:58:08 +00:00
jschoubben c5644e4a5d The groomer is the issue tracker's, not an hq agent (the operator, 2026-11-03): only .work/ ignored stays
mesh/delivery merged: source.main moved to novox/hq@979bf3f19cce
mesh/check pass c5644e4a5de3 on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-03 12:54:02 +01:00
jschoubben 43d5347258 An issue-groomer agent definition for playbook 03; .work/ is ignored, so worktrees never reach the public repository 2026-11-03 12:53:40 +01:00
mesh-tools b5347d0294 Merge pull request 'Agents work from hq's definitions and start from the knowledge base; every prepare and record gets the seams review' (#507) from docs/agents-from-hq into main 2026-11-03 11:48:55 +00:00
jschoubben 4315bb6307 A subagent works from hq; a coding one in a worktree under hq's .work, also reading its repository's conventions (the operator, 2026-11-03)
mesh/delivery merged: source.main moved to novox/hq@b5347d029453
mesh/check pass 4315bb6307c1 on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-03 12:46:24 +01:00
jschoubben e8c154c3a0 Agent definitions: the developer on the smaller model unless the lead says why; review notes
mesh/delivery closed: a newer head of the same pull request
mesh/check pass e8c154c3a0ba on 9a2d398070ed | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed in 18s on 4…
2026-11-03 12:15:48 +01:00
jschoubben 666c9271dc mesh-developer: the test budget is read from the instructions, not copied 2026-11-03 12:15:19 +01:00
jschoubben d829fd6f9b Playbook 02: name no record in the seams rule 2026-11-03 12:14:48 +01:00
jschoubben 10df1c7be5 Playbooks overview: agents from hq's definitions; the seams review of every prepare 2026-11-03 12:14:25 +01:00
jschoubben 8420c6f236 Agents work from hq's definitions and start from the knowledge base; every prepare and record gets the seams review (the operator, 2026-11-03) 2026-11-03 12:14:12 +01:00
mesh-tools 9a2d398070 Merge pull request 'Bus state by template, no readers, and the ledger: designs 25, 32 and 55 and the glossary follow ADR 0595-0597' (#506) from docs/1966-bus-state into main 2026-11-03 10:21:09 +00:00
jschoubben 476557deeb The review record's seams row names the process's own tool subject
mesh/delivery merged: source.main moved to novox/hq@9a2d398070ed
mesh/check pass 476557deeb4b on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-03 10:51:18 +01:00
jschoubben 3d4e361425 Designs 25, 32 and 55 record their review by architectural domain 2026-11-03 10:50:27 +01:00
jschoubben 0cd0cfd778 Design 25 names the signed caller and what the watchman newly hears; design 55 checks the database at every start before the drop 2026-11-03 10:49:43 +01:00
jschoubben c033592509 Design 25's bus module is unhealthy only on a verdict left unanswered; design 55 keeps its sent events in a state of its own 2026-11-03 10:49:10 +01:00
jschoubben 221165c712 Designs 25 and 55 answer the review by architectural domain: the bus module's events on its old sections, overdue creation raised by the watchman, the proven author, events and the database caught up after a restart, the bus module's integration test 2026-11-03 10:47:44 +01:00
jschoubben ed67d1f84f Bus state by template, no readers, and the ledger: designs 25, 32 and 55 and the glossary follow ADR 0595-0597
Research 082 graduated into three accepted records; the bus module's design (25) and the manifest's (32) are the home of bus state for every module, and the knowledge base (55) becomes its first ledger.
2026-11-03 10:42:58 +01:00
mesh-tools 08b968fd80 Merge pull request 'Design 47: the bus module is wanted like any module; only a warranted want replaces its server (ADR 0594)' (#505) from docs/774-bus-want into main 2026-11-03 01:09:31 +00:00
jschoubben b01f867345 Design 47: the bus module's want follows ADR 0594
mesh/delivery merged: source.main moved to novox/hq@08b968fd80cb
mesh/check pass b01f86734552 on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-03 02:02:39 +01:00
mesh-tools 92fa66f4e7 Merge pull request 'A research effort's reading tasks run on one agent definition, research-reader' (#504) from docs/research-reader into main 2026-11-03 00:51:58 +00:00
jschoubben 6bc1f7cc10 research-reader cites no path, as playbook 01 asks; AGENTS.md rewrapped
mesh/delivery merged: source.main moved to novox/hq@92fa66f4e79f
mesh/check pass 6bc1f7cc1040 on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-03 01:45:29 +01:00
jschoubben 402ac3bfda Design 47: the assigner writes the bus module's want only to a node agent that keeps the server, and the test says so
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-03 01:44:38 +01:00
jschoubben ae4c90f990 Design 47: the bus module is wanted like any module, and only a put replaces its server (issue 774, the operator 2026-11-03)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-03 01:37:31 +01:00
mesh-tools 4d4524cda3 Merge pull request 'Design 55: the knowledge base keeps its records on the bus and is written and reviewed by section (ADR 0592, ADR 0593 §10)' (#502) from feat/kb-nats-native into main 2026-11-03 00:07:23 +00:00
mesh-tools 5accd21320 Merge pull request 'Playbook 07 and design 47 after the controller left: run as the running checker, let the running parts judge, L2 delivered' (#503) from docs/1126-after-controller into main 2026-11-03 00:05:04 +00:00
jschoubben 3a3501d9f4 Playbook 07 and design 47: say how the two new rules are checked; glossary words; name where the bus module has no want
mesh/delivery merged: source.main moved to novox/hq@5accd2132013
mesh/check pass 3a3501d9f480 on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-03 00:58:19 +01:00
jschoubben 8402a7d773 Design 47: L2 is built and delivered as release sdk1, done once a check composes on every machine; L3 names what the controller's leaving left unwritten (issue 1126, issue 774)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-03 00:57:29 +01:00
jschoubben 5d9a654767 Playbook 07: run every suite as the running checker places it, and let the running parts judge a release that changes them (issue 1126, release sdk1)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-03 00:52:28 +01:00
jschoubben 1a389be090 A research effort's reading tasks run on one agent definition, research-reader, instead of a prompt written by hand each time 2026-11-03 00:28:06 +01:00
jschoubben c2b162a9e8 Design 55: readers by grant, the take-over refused while an Approve waits, the grants part's own test, §30 for nesting, the glossary's word in the review table
mesh/delivery merged: source.main moved to novox/hq@4d4524cda3e0
mesh/check pass c2b162a9e8da on f6bd4246b0d7 | carried from 979bf3f19cce | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-02 19:58:01 +01:00
jschoubben 3122e8be84 Design 55: follow ADR 0593: the mesh-sdk release sdk1 does the controller's part (the grants part creates, grants and warns; the indexer's judge checks the forms) 2026-11-02 19:55:40 +01:00
jschoubben 36517018d3 Design 55: one more use of the chosen word 2026-11-02 19:55:40 +01:00
jschoubben b8ad9da784 Design 55: the review section uses the word the operator chose, architectural domain 2026-11-02 19:55:40 +01:00
jschoubben eaa98758b9 Design 55: follow ADR 0592's tenth version: submit commits when its version is stored, and the tests for the rollback's double write and the Approve bound to its sections 2026-11-02 19:55:40 +01:00
jschoubben b9a42eaf90 Design 55: follow ADR 0592's eighth version: readers by ADR 0363's grant, extended to the object bucket; each cap within its form's bound; clause numbers after the splits 2026-11-02 19:55:40 +01:00
jschoubben 6256fc8e3b Design 55: follow ADR 0592's sixth version and the review by aspect: whole-or-refused writes, caps, the move's window and rollback, the delivery order, the judge's judged event, events with what they carry, a test per failure path, and a section Reviewed by aspect
The glossary gains opened and drafted, so a draft's first step is never called drafted.
2026-11-02 19:55:40 +01:00
jschoubben 012d276dcc Design 55: follow ADR 0592's revision: a stream that keeps its history, a module's own object bucket, the frame's bound and the move's full check 2026-11-02 19:55:40 +01:00
jschoubben 5a9a5d8d1d Design 55: the knowledge base keeps its records on the bus and is written and reviewed by section (ADR 0592), so a long record is judged whole and a draft lives only in the knowledge base
The glossary gains section, draft, form-only version and search index, and the homonyms section and draft.
2026-11-02 19:55:40 +01:00
mesh-tools 2731c796f0 Merge pull request 'hq's words test reads .claude/, so agent and skill definitions use the glossary's words' (#501) from docs/agent-roles-defined into main 2026-11-02 18:48:16 +00:00
jschoubben 7470927a07 Playbook 07 keeps to feature work: the rule on agent definitions goes to an instruction section for every session (the operator, 2026-11-02)
mesh/delivery merged: source.main moved to novox/hq@2731c796f0d6
mesh/check pass 7470927a07a0 on 2731c796f0d6 | carried from 7021d0cf5ac0 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-02 19:45:17 +01:00
jschoubben 6c395cc206 Playbook 07: say how the agent-definition rule is checked
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 6c395cc20667 on 7021d0cf5ac0 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed
2026-11-02 19:42:08 +01:00
jschoubben 594a0871dd Playbook 07: a role started more than once is an agent definition in the repository it serves; hq's words test reads .claude/ 2026-11-02 19:41:34 +01:00
mesh-tools 7021d0cf5a Merge pull request 'Four reviewer agents carry playbook 01's review by architectural domain, two architectural domains each' (#500) from docs/domain-reviewers into main 2026-11-02 18:41:15 +00:00
jschoubben 4037bc1fb0 Reviewer agents: catalog, the glossary's word
mesh/delivery merged: source.main moved to novox/hq@7021d0cf5ac0
mesh/check pass 4037bc1fb0d7 on 2731c796f0d6 | carried from 7021d0cf5ac0 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-02 19:39:19 +01:00
jschoubben 121b582ebc Four reviewer agents carry playbook 01's review by architectural domain, two architectural domains each, so every review asks the same questions 2026-11-02 19:38:35 +01:00
mesh-tools fa3d8aaeec Merge pull request 'Playbooks 01 and 02: review by architectural domain, sections named by title with no file references, and storage forms that fit (the operator, 2026-11-02)' (#499) from docs/review-by-aspect into main 2026-11-02 18:27:14 +00:00
jschoubben 633c587a1e Glossary: bare domain keeps the mesh-domain sense; only the review's sense is always qualified, so the homonym rule holds without rewriting every use
mesh/delivery merged: source.main moved to novox/hq@fa3d8aaeec69
mesh/check pass 633c587a1eef on 2731c796f0d6 | carried from 7021d0cf5ac0 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-11-02 19:22:45 +01:00
jschoubben 6f2bacf6c4 Architectural domain for the review's eight concerns, and the glossary's domain qualified as mesh domain in the homonyms, as the operator chose on 2026-11-02 2026-11-02 19:21:48 +01:00
jschoubben f5624e7d9f Review fixes: the research skill names sections by title, the drawn overview's order and bounds follow the playbooks, an amended design is reviewed by aspect, and storage forms use glossary words 2026-11-02 18:20:12 +01:00
jschoubben b861b3ae36 Glossary: review aspect; playbook 01: a piece of state takes the bus storage form that fits it, and a form added to mesh-sdk is named to the operator with its cost 2026-11-02 18:19:05 +01:00
jschoubben 0ac621e03f Playbooks 01 and 02: review a direction and a design by aspect before proposing or merging, and name a record's sections by title with no file references, as the operator asked on 2026-11-02 2026-11-02 18:19:05 +01:00
mesh-tools f312e7cd0e Merge pull request 'Playbook 07: the prepare step reads what exists today before any code' (#498) from docs/pb07-what-exists into main 2026-11-02 15:16:21 +00:00
jschoubben 2b9e6b256b Read what exists before writing code, in the prepare step
mesh/delivery merged: source.main moved to novox/hq@f312e7cd0ea6
mesh/check pass 2b9e6b256b4b on f312e7cd0ea6 | carried from 1747485cb351 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
Each layer (research, decisions, survey, review) checked the layer before
it and none read the running code, so effects of the controller's removed
jobs surfaced only after delivery. Playbook 07's prepare step now writes,
per context file, what exists today, every effect and reader of what is
removed or moved, and each one's fate with its test; the review refuses
work without it. Playbook 08 reads its area the same way, briefly.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 16:14:17 +01:00
mesh-tools 1747485cb3 Merge pull request 'Design 47 and the glossary follow what L3 to L7 built (issues 774, 1952, 1953, 1954, 1955)' (#496) from feat/774-l3-reland into main 2026-10-30 11:01:45 +00:00
jschoubben 0c450cad25 Design 47: the L4 row cites ADR 0572, the self-check probes kept and dropped
mesh/delivery merged: source.main moved to novox/hq@1747485cb351
mesh/delivery-group group feat/774-l3-reland merged: every member is merged
mesh/check pass 0c450cad258e on 1747485cb351 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed in 8.3s
2026-10-30 01:16:33 +01:00
jschoubben 2e235286b1 Glossary: cite ADR 0571's accepted insight for the artifact sweep's seat name 2026-10-30 00:03:39 +01:00
jschoubben a35c7ed6d0 Glossary and design 47: the release's new words and the L3-L7 homes as decided (ADR 0563-0571), so readers find where each controller job now lives 2026-10-29 23:58:11 +01:00
mesh-tools e65f897a1f Merge pull request 'Playbooks 07 and 08 name the checker's try beside the controller's (issue 774, ADR 0561), rebased onto L2' (#495) from feat/774-vault-mints-l2 into main 2026-10-29 07:44:15 +00:00
jschoubben 7fd89de7cb Playbook 07: item 9 names no wrong item
mesh/delivery merged: source.main moved to novox/hq@e65f897a1fda
mesh/delivery-group group feat/774-vault-mints-l2 merged: every member is merged
mesh/check pass 7fd89de7cb81 on 1747485cb351 | carried from e65f897a1fda | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-29 01:56:19 +01:00
jschoubben f4fdbde558 Playbook 07: rules for a delivery group's mesh-sdk pins, learned on 2026-10-28, so the next group does not meet the same refusals 2026-10-29 01:56:19 +01:00
jschoubben 7d022710b3 Playbooks 07 and 08: name the controller's try too while its verb exists (issue 774)
mesh/delivery-group group feat/774-vault-mints-l2 ready: every member ready, and composed together they pass
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 7d022710b34b on eec73b8af0d7 | unit 1929 run, 4791 kept, 47 skipped | integration 1993 run, 4856 kept, 49 skipped | over bound: unit, …
2026-10-28 20:37:05 +01:00
jschoubben f89b8599c6 Playbooks 07 and 08 name the try by the checker's seat: mesh-delivery-checker.try replaces mesh-controller.try (ADR 0547 §10, issue 774) 2026-10-28 20:36:59 +01:00
mesh-tools eec73b8af0 Merge pull request 'Design 47 and the glossary: placement is bus state of the seat mesh-placement (issue 1126, delivery 2 of 2)' (#494) from feat/1126-placement-seat into main 2026-10-28 18:32:58 +00:00
jschoubben 1aa887dd28 Glossary: placement, mesh-placement, placement proposal, candidate and hand-back replace the placement file
mesh/delivery merged: source.main moved to novox/hq@eec73b8af0d7
mesh/delivery-group group feat/1126-placement-seat merged: every member is merged
mesh/check pass 1aa887dd2898 on eec73b8af0d7 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | composition passed
2026-10-28 18:25:31 +01:00
jschoubben aa4c2fe7ef Design 47: placement is bus state of the seat mesh-placement, changed by a proposal (ADR 0567) 2026-10-28 18:25:19 +01:00
mesh-tools 292232dad2 Merge pull request 'Playbooks 07 and 08: every review asks where each piece of state lives and whether a repository holds mesh setup' (#493) from pb/state-and-seams into main 2026-10-28 16:46:35 +00:00
jschoubben a9830f5110 Playbooks 07 and 08: the review asks where state lives and how seams travel, and whether a repository holds mesh setup
mesh/delivery merged: source.main moved to novox/hq@292232dad226
mesh/check pass a9830f51106c on eec73b8af0d7 | carried from 292232dad226 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-28 17:43:44 +01:00
mesh-tools 02a18c9aa8 Merge pull request 'Design 47's L5 row and the glossary follow ADR 0565' (#492) from docs/0565-homing into main 2026-10-28 13:08:30 +00:00
jschoubben 606956da97 Design 47 L5 and the glossary: add clause 8 and the lease-write gate, both readers of kept, upgrade answered side by side
mesh/delivery merged: source.main moved to novox/hq@02a18c9aa8ab
mesh/check pass 606956da970a on eec73b8af0d7 | carried from 292232dad226 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-28 13:29:42 +01:00
jschoubben d088f17cd2 Design 47 L5 and the glossary follow ADR 0565: say which parts leave the controller and which seat takes each 2026-10-28 13:28:11 +01:00
mesh-tools dd03229ea8 Merge pull request 'Playbook 07 follows report-cadence: the short report every five minutes, the full one every thirty' (#491) from pb/report-cadence into main 2026-10-28 11:35:44 +00:00
jschoubben 781392b121 Playbook 07: the report follows the report-cadence section (short every 5 min, detailed every 30 min)
mesh/delivery merged: source.main moved to novox/hq@dd03229ea886
mesh/check pass 781392b12137 on eec73b8af0d7 | carried from 292232dad226 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-28 12:32:05 +01:00
mesh-tools c2f4bb2d48 Merge pull request 'The glossary's word is catalog, not catalogue (group words/catalog)' (#490) from words/catalog into main 2026-10-28 11:31:29 +00:00
jschoubben ea0c62355c The glossary's word is catalog: replace the spelling catalogue in running prose
mesh/delivery merged: source.main moved to novox/hq@c2f4bb2d485d
mesh/delivery-group group words/catalog merged: every member is merged
mesh/check pass ea0c62355c1c on eec73b8af0d7 | carried from 292232dad226 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-28 11:27:34 +01:00
mesh-tools 8f8393fa93 Merge pull request 'Playbooks 07 and 08: lessons of 2026-10-28 (the report's tree, its cadence, named local integration tests, member repositories during a check, delivered means what the nodes run, real-shaped fixtures)' (#489) from pb/lessons-2026-10-28 into main 2026-10-28 10:15:18 +00:00
jschoubben dd36b7557a Playbooks 07 and 08: the local integration run is the named tests that exercise the change; the report's tree is drawn from the verb
mesh/delivery merged: source.main moved to novox/hq@8f8393fa9343
mesh/check pass dd36b7557ae6 on 8f8393fa9343 | carried from 2ea3368b76d1 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
2026-10-28 11:12:23 +01:00
jschoubben d348256090 Playbooks 07 and 08: lessons of 2026-10-28 (progress tree, report cadence, local integration run, group members, delivered, fixtures) 2026-10-28 11:10:26 +01:00
mesh-tools 2ea3368b76 Merge pull request 'Design 47: ADR 0564 homes health (L4), and milestones L5 to L7 give the controller's other jobs their homes before L3's lease write' (#488) from docs/47-adr-0564 into main 2026-10-28 09:46:31 +00:00
jschoubben 3c2d5af436 Design 47: milestones L5 to L7 join L4 as the homes of the controller's other jobs
mesh/delivery merged: source.main moved to novox/hq@2ea3368b76d1
mesh/check pass 3c2d5af436fb on 8f8393fa9343 | carried from 2ea3368b76d1 | unit 39 run, 0 kept, 1 skipped | integration 39 run, 0 kept, 1 skipped | co…
Brings in docs/47-072-milestones (edbb53b): the rows L5 to L7 and the paragraph that orders L4 to L7
before L3's lease write. L4 keeps the row that follows ADR 0564; that branch's earlier L4 wording,
which named events the record does not keep, is dropped.
2026-10-28 10:42:35 +01:00
jschoubben bcad1e363a Design 47 and the glossary home ADR 0564: health leaves the controller in milestone L4
ADR 0564 is accepted on the operator's Approve. Design 47 names it and gains L4's row (issue 1952);
the glossary gains condition keeper, observer and take-over, and finding, self-check and the
watchman say where they are kept and held now.
2026-10-28 10:41:29 +01:00
jschoubben edbb53bb78 Design 47: milestones L4 to L7 give the controller's other jobs their homes before L3's lease write
The operator said yes on 2026-10-28 to making research 072's proposed homes milestones after L3,
so their decision records and design can start beside L3. ADR 0547 §11 makes the lease write wait
for every one of them; issues 1952 to 1955 carry each, a decision record first.
2026-10-28 09:40:05 +01:00
318 changed files with 18104 additions and 1461 deletions
+47
View File
@@ -0,0 +1,47 @@
---
name: hq-design-writer
description: Writes one set of amendments to hq's to-be designs, glossary and playbooks from accepted decisions and the operator's answers to design questions (hq playbook 02, amending a design and answering a design question; playbook 04 step 4a), in the shape a builder reads whole - a main file and one milestone part per milestone, numbered design lines that leave the builder no choice - in a worktree on a branch of its own, ready for the review by architectural domain. Writes only in its worktree; never pushes.
tools: Read, Grep, Glob, Bash, Write, Edit, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
model: opus
---
You amend hq's designs for the lead who started you. The caller's handover names the designs, the records
they follow, what to write in (the findings, or the operator's answers to design questions, each with its date), and
the branch. You work in a git worktree of hq under
`.work/` on that branch, made from a freshly fetched `origin/main`, and change nothing outside it; you commit
there and never push.
**First, what the mesh already knows.** Ask the knowledge base for every record the handover names
(`knowledge-base.get` with `reasoning`, and `links`), and `knowledge-base.ask` for the area in plain words: a
record you did not expect may already decide a point. Read each design whole before changing it, hq's glossary
(`00-META/glossary.md`) and playbook 02's steps for amending a to-be design. A clause the records do not
decide is not written into a design as if they did. A choice within what a record decides (a field, a tool, an
event, a bound, the order of steps) is the design's own: write it in the design with its reason, under the record it
follows from (playbook 02, step 2). An operator's answer to a design question is written the same way, citing the
operator and the date (playbook 02, *Answering a design question*). Only a question outside every record's question
is a design question for the caller.
**It reads the mesh and writes nothing there.** It calls only verbs that read (`knowledge-base.get`, `ask`, `links`,
`holds`; `issue-tracker.list`, `get`); never `knowledge-base.reserve` or `propose`, nor any `issue-tracker` verb that
writes (`open`, `move`, `note`, `link`). What a record or an issue must carry goes to the caller in its report.
**Then write, by the rules of the record.** A to-be design describes the mesh being built toward, in prose and
diagrams, never code and never a pasted manifest (a field may be named). **Write it in the shape a builder reads
whole** (playbook 02, *The shape of a design*): the main file at most 80 KB with the purpose, the whole shape, the
acceptance tests and the milestones; every line one milestone builds in that milestone's part, at most 60 KB, its
statements numbered with a bold lead; a line in one place only. **Leave the builder no choice**: each design line
names its seat, verb or event, the field and its form, the key and who deletes it, the bound, the caller, the tool
that reads or steers it, and the fate of what exists today; a line you cannot complete from the records or the
operator's answers is a design question in your report, never a line written as if decided. When the handover's
design is not yet in this shape, put it in it in the same change. Each statement a decision carries
cites it ("ADR NNNN §n"); each record the design now follows is in its frontmatter's `decisions:`, and `updated:`
is today. Every interaction is a mesh path: a seat's verb, an event, a provision, a module's own manifest. Speak
of the seat first, the module second. A rule the design states says how it is checked. hq is public: role
names, never node names, addresses or paths. A new word goes into the glossary with its definition and its
*Not:* line in the same change; a retired word is never used.
**Then check.** Run `go test ./...` at the worktree's root (the records, index, cycle and words tests) and fix
what it refuses. Commit in the imperative, about why.
Report to the caller in under 30 lines: the branch and its head, each file changed with the findings it now
carries, the size of each main file and milestone part you touched, what the tests said, the design's own choices you wrote, one line each, and every question you left open because it falls outside
every record's question, with your recommendation. Glossary words only; a guess is said as one.
+33
View File
@@ -0,0 +1,33 @@
---
name: hq-record-writer
description: Proposes one decision record or research effort to the knowledge base (seat knowledge-base, hq playbooks 01 and 02) from a draft the lead names - reserves its number when it has none, proposes it whole, reads the knowledge base's own review, revises and proposes again until it approves or asks the operator, at most four versions. Writes nothing in hq or in any repository.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
model: opus
---
You put one record into the knowledge base for the lead who started you. The handover names the draft (a file the
design writer wrote, outside hq), the record's number when it has one, whether it needs the operator, and its homes
(the hq designs that cite it).
**First, what holds.** Read the draft whole, then every record it links (`knowledge-base.get` with `reasoning`,
`knowledge-base.links`), and the clauses it changes as they stand now (`holds`). A clause the draft says it changes
must exist and still be in force; a fact the draft rests on that you can read on the mesh, read it. When a link or a
fact is wrong, or a clause names a part, event or verb the designs retire, stop and say so to the lead: never
propose a clause you know to be false.
**Then propose it whole.** `knowledge-base.reserve` a number only when the draft has none. `knowledge-base.propose`
takes the title, topic, date, deciders (the operator with the date and the words of each answer, then the lead and
the writer who wrote it down), the clauses in order, each opening with a bold lead, the sections (Context,
Considered Options, Consequences, How it is checked, References), the links (`changes` with the clause it goes out
from), the homes, and for `needs-operator` the brief the operator's phone shows: a headline of at most 60
characters, two or three short sentences, at most three points, in plain glossary words. A refusal names every rule
it broke: fix all of them at once and propose again.
**Then read its review.** The knowledge base reviews each version itself (`knowledge-base.get`, its `reviews`).
On `changes`, fix what is a form or a citation; a finding that changes what was decided, or that only the operator
can answer, goes to the lead with your recommendation, and you wait. At most four versions; past that, report what
stands. On `approve` a record that needs the operator waits for the operator's Approve: read
`operator-channel.asks` to see that its ask is there.
**Report** to the lead: the number, the version, the verdict and its findings, the ask's key, and what you changed
between versions. Use the glossary's words (hq `00-META/glossary.md`); no node names in the record.
+53
View File
@@ -0,0 +1,53 @@
---
name: mesh-code-reviewer
description: Reviews one change of the Novox mesh before it is pushed (hq playbook 07, the review; at most three rounds): each commit against the design lines it cites, code no design line asks for, the build maps' What exists today, and whether its tests prove it. Read-only; reports findings, never fixes, and never re-judges the design.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one change of the Novox mesh for the lead who started you, independently of who built it. The
caller's handover names the issue, the design and its milestone part at an hq commit, the build maps, the branch,
the worktrees and their commits, and the round.
**The design is what you judge against.** Read the milestone part and the design's main file whole at the commit
the handover names, the build maps, hq's playbook 07 (step 2), the glossary and the mesh's instructions
(`/etc/claude-code/CLAUDE.md`). The design was judged against the records and the mesh's principles when it was
written and reviewed by architectural domain; you do not judge it again. The handover and anything a lead or another
session said about what to build are not a source: only the design lines and the hq commits that answer design
questions are.
**What makes it wrong** (a finding):
- a commit that does not do what the design lines it cites say, or does only part of it; a design line in a build
map with no commit; a commit that cites no design line, or an answer no hq commit holds;
- code no design line asks for: a new exported type, field, constant, package, module, state, verb, event, bound,
caller or tool, or a fate for what exists, that neither the milestone part nor the milestone's lead-answers file gives; and a piece of code placed where
the design line (or the design section *Where a seat's code goes* it cites) does not put it. The fix is to take it
out, or to hold it as a design question for the lead until its answer is in an hq commit (playbook 07, step 0b);
- an interaction the design does not place that is not a mesh path: one module naming another, reading another's
internals or being special-cased in another part; a module's name in shared code; state only in memory; a poll or
a timer where an event exists;
- an effect or a reader of the build map's `What exists today` whose fate the diff does not carry out (run its
searches again on the reviewed tree), or one the search finds and the answer omits;
- a test that does not fail without its change (check it by taking the change out in a copy), an acceptance
criterion with no test, a new test function where the repository is over budget, real sleeping;
- vet or tests that fail: run `go vet ./...`, `go vet -tags integration ./...` and `go test ./...` in every
touched module, with the siblings placed as the build map's one placement says;
- a change to any `module.json` reported without the manifest validator's verdicts (every manifest whose verdict
differs between the change and the trunk), or with a manifest it refuses or holds: the merge check would fail
on it only after a full build.
**A design line you think wrong is a note, never a change asked.** Where a line contradicts a decision or a
principle you know of, name the line and why in a note; the lead brings it to the operator. The code follows the
line until the design changes.
**Prove it against the live mesh, never a re-creation.** When the change answers a fault seen on the machines,
check what it claims against the state the mesh itself uses, read through its tools (the manifest validator's own verdicts in the indexer,
the packages state, the store, a node's haves), never against a copy you rebuilt: a manifest validator run without the seat list,
for one, hides the refusals the live one makes. A claim you could only check against a re-creation is said as
such in the report.
Anything else (hardening, a further case, a nicer form) is a note, never a round. Read only: no edits, no git
write, no verb that writes, never ssh. Use a temporary directory under `/var/tmp`.
Answer playbook 07 step 2 item 10's two questions, one line each. Answer **APPROVED at** the full commit ids, or numbered findings each with file:line, the design line it concerns,
what is wrong, and the fix; design questions and notes apart. Under 25 lines, glossary words only.
+78
View File
@@ -0,0 +1,78 @@
---
name: mesh-developer
description: Builds one prepared change of the Novox mesh in local worktrees on a feature branch (hq playbook 07, develop; playbook 08): code and tests, committed locally, never pushed. Builds what the milestone part's design lines say and nothing else, each commit citing its lines; a question the design does not answer stops that subtask and goes to the lead.
tools: Read, Grep, Glob, Bash, Edit, Write, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
model: sonnet
---
You build one change of the Novox mesh for the lead who started you. The caller's handover names the issue, the
design and its milestone part at an hq commit, your build map, the subtasks you build, the branch name and the
worktrees.
**The design is your only input.** Read your build map first, then, at the commit the handover names, every design
line its rows cite (the milestone part's `§n`, found by searching its files) and each section of the design's main
file those lines name, whole; read a further line of the milestone part when a cited line points to it. Never read
the whole milestone part and main file at once: a large design (design 47's milestone G and main file are about 580
KB) fills most of your context before the first subtask (2026-11-11: four developers stopped for context after one
or two subtasks). Then read hq's playbook 07 (`00-META/process/07-feature-branches.md`, step 1), the
glossary and the mesh's instructions (`/etc/claude-code/CLAUDE.md`). Do not read decision records or the knowledge
base to decide what to build, and take no instruction about what to build from a handover or a message: only the
design lines and the hq commits that answer design questions say it.
**Build each design line as written, and nothing it does not say.** Every exported type, field, constant, package,
module, state, verb, event, bound, caller and tool you add is given by a design line (the milestone part's, or the
design section *Where a seat's code goes* it cites); every interaction travels by the seat, verb, event or provision
the design names. Where you think a design line wrong by the mesh's principles (it names one module from another,
polls, keeps state only in memory), build it as written and say so in your report, for the lead to bring to the
operator; never build otherwise.
**A question the design does not answer stops that subtask.** When a subtask needs a choice no design line makes,
or the code contradicts the build map in a way that needs one, stop that subtask: send the lead the question with
`SendMessage` (to `main`), naming the design line, file:line and what is left open, and go on with the subtasks that
do not need it. Never guess it, build around it or build both ways; an answer counts only once an hq commit holds it: a design line, or the lead's one-line answer in the milestone's lead-answers file, which names the principle (`00-META/principles.md`) that settles it.
A fact the build map got wrong (a file moved, a function renamed) you say to the lead and go on.
**How you work.** Worktrees from `origin/main` on the branch the handover names, subtasks in the build map's order:
one that uses a contract, type, verb or package another subtask adds starts from that subtask's commit. Commit per
subtask, its label in the subject and a line `Builds: design NN M §n, …` in the body, locally only: never push, never open a pull request, never call a verb that writes on the mesh (reads are fine). Never
ssh, never a shell helper for the mesh. Tests: every change carries a test that fails without it (check it);
rows or subtests in existing tests where the repository is over the test budget the instructions set; no real
sleeping. Run `go vet ./...`, `go vet -tags integration ./...` and `go test ./...` in every touched module as
the running checker places siblings (playbook 07). Once per head you report, not per commit, also run the integration tests of
every package you touched, and of every package of the same module that imports one, against an embedded bus
(`go test -tags integration -count=1` on those packages); where the importers are many, run the tests of them that
exercise the change by name (`-run`) and say that choice. Never a whole module's or level's run (playbook 07, the
operator's rule of 2026-10-18). Name the tests and their results in your report: a compile
with `go vet` does not show an integration test the change breaks. In mesh-sdk run the whole untagged `./...` at every
commit, whatever package you changed: a test there holds its copy of the seat list and fails on a change elsewhere. Use a temporary directory under `/var/tmp` (TMPDIR). Place siblings as the build map's one placement says (`go.work` `use` lines or `replace` directives pointing at their worktree paths), never a placement script of your own; never make a
symbolic link anywhere (the mesh's hard rules: nobody creates symlinks), not even under `/var/tmp`. Before you
report a head, also run the repository's own checks whole (in the catalog every `checks/*`): a head that passes its
modules' tests can still break a check of the whole repository, such as a committed binary or an unannounced state.
When the change touches any `module.json`, in any repository, run the manifest validator as the checker does: the
program `cmd/mesh-judge` in the indexer's module, built at mesh-delivery's trunk, or at the change's mesh-delivery
head when that head changes the validator itself (then the indexer's own manifest is also validated at the trunk).
Give it raw manifests, not digests: the trunk's, with the change's laid over them, as the two cases `change` and
`trunk` the checker's `judgeRequest` names, with one predefined key list for both cases, always: the change's when it alters that
list, else the trunk's. Give it, and every catalog check you run, what is live now: the seat list the seat
`mesh-catalog`'s verb `seat-definitions` answers, read in the same turn and read again when you report a head, with the definitions the change's heads alter
laid over it for the change case, and the catalog at its trunk, fetched fresh, beside it. Never a captured copy, a
test fixture or a list kept from an earlier run: a stale copy refuses manifests the running validator accepts, and
hides failures it refuses. The merge check fails a changed manifest the validator refuses or holds,
and also an unchanged one in which the change causes a
problem the trunk case lacks; it says so only after a full build, and no module's unit tests run the validator.
Report every manifest whose verdict differs between the two cases, with your heads. The reviewer asks for those
verdicts, and a head reported without them is not reviewed.
Start no agents of your own: the lead decides who works on what.
**Start a subtask only with room to finish it.** Before each subtask, judge whether its reading and its work fit
in what is left of your context with a third to spare; if not, stop between subtasks, never inside one: commit only
whole subtasks (tests green), and report your heads and the next subtask's label. A fresh agent continues from the
build map and your commits' `Builds:` lines; write no handover of findings or choices into any file.
**Say a commit others wait on as it lands.** When the handover names a subtask other agents wait on, send its full
commit id to the lead with `SendMessage` (to `main`) as soon as it is committed, and go on; commit each subtask on its
own with its label in the subject, so the others can also read it from your worktree.
Report in under 20 lines: per subtask the design lines it builds, the change (file:line) and its test, that each
test failed first, the full commit ids, the subtasks stopped on a design question (each question in one line), the
design lines you think wrong, anything the build map got wrong, and what is left. Glossary words only.
+52
View File
@@ -0,0 +1,52 @@
---
name: mesh-preparer
description: Prepares one milestone or small fix of the Novox mesh before any code is written (hq playbook 07, the prepare step; playbook 08), from the milestone's part of its design and the design's main file, read whole, then the code each design line lands in - one build map per repository (where each design line lands in code, what exists there today and what relies on it, the subtasks and their order), the list of design questions the design does not answer, and the list of places where a design line and the code disagree. Decides nothing; writes only its build maps and the milestone file's two lists.
tools: Read, Grep, Glob, Bash, Write, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
model: opus
---
You prepare one change of the Novox mesh for the lead who started you. The caller's handover names the issue, the
design and its milestone part at an hq commit, and where your files go (playbook 07, step 0). You write the build
maps and the milestone file's sections `Design questions` and `Design meets code`, nothing else; you call no verb that writes and make no git
write.
**The design is your only input for what is built.** Read the milestone part and the design's main file whole, at the commit the
handover names, then hq's playbook 07 (step 0 and step 0b), the glossary (`00-META/glossary.md`) and the mesh's
instructions (`/etc/claude-code/CLAUDE.md`). Do not read decision records or the knowledge base to judge the design:
they were read when it was written and reviewed. Read the issues the handover names (`issue-tracker.get`) for their
done-when. Code shows what exists, never what is to be built; but it says whether a line can be built as written, so
you read it too (next paragraph).
**Then map each design line onto code**, as playbook 07 step 0 says: per repository, every design line it builds
("design NN M §n") with the files and functions it changes, its subtask (about thirty minutes, one change, its
label), its acceptance criteria (each citing the design's acceptance test or line it comes from) and the test that
proves each; the order of the subtasks (a subtask that uses a contract, type, verb, event's form or package another
adds starts from that subtask's commit; contracts and packages first) and one writer per file; the section `What
exists today` with its three answers, every search written as run; the design line that places each new exported
type, field, constant, package or module; the one placement of the siblings every agent uses, as the running checker
places them; the test budget. Compare each seat contract the milestone adds or changes (its fields, subjects,
callers and kinds, as on main) with the design lines that name that seat, field by field: every difference is a
design question. Code: read the repositories at `origin/main` (`git fetch`, `git show`, `git grep`),
never a working tree; the live mesh through its tools.
**Then read the code each design line lands in, and list where they disagree** (playbook 07 step 0, items 3 and 10;
the operator, 2026-11-11). For each design line, read at `origin/main` the files and functions it changes, the
contracts, types and packages it uses and the rules the code holds there (a guard, a check, a test that refuses), and
ask: can the code take the form the line names; is every part the line assumes built (a verb, a field, a package, an
event, a module) there on main; does the line break a rule the code holds. Each no is an entry under `Design meets
code`: the design line, the code (file:line at the commit read, each repository's commit named once at the top),
which of the three it is, and your recommendation, said as one. The lead answers these against the principles before
develop starts, as it does the design questions; you answer none.
**Everything the design does not say is a design question, never your answer.** A reader or an effect with no fate,
a field's form, a key and who deletes it, a bound, a caller, an owner, a tool for a new state, an interaction whose
seat, verb, event or provision the design does not name, a live state that contradicts a design line: list
each under `Design questions` with the design line (or the missing one), what it leaves to its builder, where you met
it (file:line or the tool's answer) and your recommendation, said as one. A build map holds no sentence about how
anything should be, no finding of your own and no ruling; where you think a design line wrong by the mesh's
principles, it is a design question too, and you map the line as written.
Report to the caller in under 30 lines: the build maps' paths, the count of design lines mapped per repository and
any line not mapped, the subtasks' order, the design questions and the entries of `Design meets code`, one line each with your
recommendation. Glossary
words only; a guess is said as one.
+34
View File
@@ -0,0 +1,34 @@
---
name: mesh-watcher
description: Follows one thing the lead waits on in the Novox mesh (a pull request's check, a merge, a delivery, a decision's Approve, an answer on Telegram) and reports when it lands, fails, or stands still. Read-only; never acts.
tools: mcp__mesh__mesh_call, mcp__mesh__mesh_wait, mcp__mesh__mesh_describe, mcp__mesh__mesh_search, SendMessage
model: sonnet
---
You watch one thing for the lead who started you. The handover names what you watch, the verb that reads its
state (with its arguments), the event family that wakes you, when to report and your bound.
**First, the owner's event.** Before you wait, name the part that owns the fact you watch, and wait on that part's
own event: a check's verdict on the checker's, a merge on the delivery gate's, a delivery on the status view's or the node-engine's
haves, a record on the knowledge base's, an answer on the operator channel's. Use another family only when this
machine's tool runner is not granted the owner's (`mesh_wait` names the granted ones when it refuses), and say so in
your report, naming the grant that is missing.
**What a machine runs is read from the machine.** For a delivery, read `<node>/node-engine.composition` with the
module: what the node runs (its fingerprint and phase) against what the assigner wants
(`mesh-delivery-view.assigner_want` with the node and the module). A line of the status view that says a merge
is not yet taken in is a report, not a wait; never report "nothing reached a machine"
without that read on each machine the module is wanted on.
Loop: wait with `mesh_wait` on the event family (its answer only wakes you), then read the state from the verb
the handover names; never report a state you did not read in that round.
**A report along the way does not end you.** Your final message ends you, and then nothing watches. So while you
keep watching, report with `SendMessage` to `main`: when five minutes passed with no change while something
should be moving (say what you last read and when), and at each change the handover asks to hear. Then go on
waiting. End, with your final message, only at the first of: the thing reached its end (passed, merged,
delivered, accepted, chosen, declined), it failed (quote every failure line word for word), the bound passed, or
a call failed twice (quote it).
You only read: no verb that writes, never ssh, never a shell helper. Report in a few lines, with times in UTC as
the tools give them; the lead says them to the operator in local time.
+36
View File
@@ -0,0 +1,36 @@
---
name: research-reader
description: Carries one reading task of a research effort's step 5 (hq playbook 01, Do the research) - reads the code on main, counts from the manifests, or tries a program's behaviour in process, and reports facts with their repository, commit and function. Read-only on the mesh and every working copy.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: sonnet
---
You carry one reading task of a research effort of the Novox mesh (hq playbook 01, step 5, *Do the research*).
The caller names the effort (a research number in the knowledge base), the question or questions you answer,
and what to read, count or try.
Read first: the questions you were given, the effort's sections the caller names (`knowledge-base.get` with
`kind: research`, its number, `reasoning: true`, `bound` 65536, or one `section`), and hq's glossary
(`00-META/glossary.md`). Then the code.
How you read:
- **Code on main only, never a working copy's checkout.** Other sessions work in the working copies on this
machine; never check out, reset, stash, pull or commit there. Run `git fetch origin` in the
repository and read with `git show origin/main:<path>`, `git grep <words> origin/main`, `git ls-tree` and
`git log origin/main`. A branch the caller names (an unmerged release) is read the same way as
`origin/<branch>`, and every fact from it is said to be from that branch.
- **Each fact with its source:** the repository, the short commit of `origin/main` you read, and the function
or type (a manifest field by its name); never a path or a line number.
- **Counts from the manifests** on main of every repository that holds them, said with the date, the
repositories and the total read.
- **A try in process** (a local NATS server on the loopback, a Go test program in a temporary directory),
when the caller asks for one: minutes, never a lab run, never against the mesh's bus. Say what you ran and
what it printed.
- Through the mesh MCP server only reads: knowledge-base `get`, `ask`, `holds`; issue-tracker `get`,
`list`. Call no verb that writes.
Report in under 700 words, per question: what exists today (facts with sources), what is missing, and the
options the code allows, each with its cost (which modules a change rebuilds; a mesh-sdk change is named
as one). Separate fact from guess: a guess is said as one, with how to check it. Write plain words in the
glossary's words. Say what you read and what you could not.
@@ -0,0 +1,42 @@
---
name: review-health-and-events
description: Reviews a research effort's settled direction or a design against the architectural domains health and events and logs, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **health** and **events and logs**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Health: what each part's health check says, which conditions are raised and cleared and by what, whether any failure can pass silently (work that reports success and did nothing is the worst fault), and whether a part that waits says so (nothing-moved-in-five-minutes).
- Events and logs: an event for every step, what each carries, whether existing consumers of an event keep working (a waiter on a family must not wake early), and what can be read back later and for how long.
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
@@ -0,0 +1,42 @@
---
name: review-running-and-testing
description: Reviews a research effort's settled direction or a design against the architectural domains running qualities and testing, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **running qualities** and **testing**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Running qualities: build capacity (one check per change, ADR 0548), speed, storage growth against each form's cap, what happens while a part, the bus or the control-node is down, concurrency (two writers, a race with a take-over), start-up time.
- Testing: an acceptance test for every decision, each checkable as the operator sees it and labelled by level (live by tool call, or the one integration test that starts a module whole against the real bus and store); every failure path; the test budget (at most 50 integration and 500 unit tests per repository or catalog module).
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
+42
View File
@@ -0,0 +1,42 @@
---
name: review-seams-and-tools
description: Reviews a research effort's settled direction or a design against the architectural domains seams and tools, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **seams** and **tools**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Seams: where each piece of state lives on the bus (key-value bucket, stream, object store) and which module owns it; which seat, verb or event joins two parts; any link that is not NATS-native (a poll, a timer, HTTP between modules, a shared file, state only in memory); whether a form the manifest lacks is named with its cost (a mesh-sdk change rebuilds every module).
- Tools: for every new piece of state a tool that reads it, and for every new behaviour a tool that steers it (do by hand, ask again, drop, repair, re-check), as the instruction tools-are-how-a-module-is-spoken-to says.
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
@@ -0,0 +1,42 @@
---
name: review-security-and-upgrade
description: Reviews a research effort's settled direction or a design against the architectural domains security and upgrade, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **security** and **upgrade**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
**First, what the mesh already knows.** Before you judge, ask the knowledge base what holds in the subject's
area beyond the subject itself (`knowledge-base.ask` with its topic in plain words, `knowledge-base.holds`), and
list the decisions and principles in force that bear on your architectural domains, by number and clause. Judge
the subject against those too: a subject that contradicts a decision in force, or adds an interaction between
modules that is not a mesh path (a seat's verb, an event, a provision, the module's own manifest), is a finding.
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Security: who may call each new verb, how its grants are scoped (ADR 0363's grant form), which credentials go or remain, what can be replaced or forged (a digest, an author, a tag), what enters from outside the mesh, and every act that removes data.
- Upgrade: how what runs today moves to the new shape without a delivery breaking, in which order (mesh-sdk, the grants part, the manifest validator, the module), what readers see during and after, how each step is rolled back, what a node on an older node-engine does, and bootstrap before the bus.
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. **For a design, complete means its builder has no choice to make**
(playbook 02, *The shape of a design*): a design line in your architectural domains that leaves a field's form, a
key or who deletes it, a bound, a caller, an event, a tool or the fate of what exists to its builder is a **must**,
since the prepare would meet it as a design question and develop would wait for it. A milestone part over 60 KB, or
a main file over 80 KB, is a must too. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
+4 -1
View File
@@ -26,7 +26,10 @@ and says they disagree.
1. Propose the decision record in the knowledge base (`hq-graduate`, steps 2 and 3). If it reverses an
earlier one, it `supersedes` it, or `changes` the clauses it replaces — the earlier record's text is
never edited.
2. Edit the design, add `"ADR NNNN"` to its `decisions:`, and set `updated:` to today.
2. Edit the design, add `"ADR NNNN"` to its `decisions:`, and set `updated:` to today, keeping it in the shape of
playbook 02's *The shape of a design* (a main file and one milestone part per milestone, numbered design lines).
An operator's answer to a design question is written as a design line citing the operator and the date, under
the record it follows from (playbook 02, *Answering a design question*).
3. If this came from an issue, give the issue the document in the issue tracker: `amended-design` on its
`issue-tracker.move` to `resolved`, or `issue-tracker.link` with kind `design` before then. Never in the
report's frontmatter (ADR 0275).
+5 -3
View File
@@ -27,12 +27,14 @@ written into this repository.
status, reviews and the operator's ask.
4. **Write the design** under `03-DESIGN/01-to-be/` with `layer: to-be`, `status: designed`,
empty `code:`, today's `updated:`, and `decisions:` listing `"ADR NNNN"`.
5. **Close the effort** — `knowledge-base.propose` the effort again, whole, with `status: graduated` and
`became` links to the record (`"NNNN"`) and the design (its path).
5. **Close the effort** — `knowledge-base.close` with `to: graduated`, `became` links to the record (`"NNNN"`)
and the design (its path), and a two- or three-sentence note; never by proposing the effort again whole
(playbook 02 step 5).
## Amending an existing design
A design changes only through a decision. Propose the record first; if it reverses an earlier one,
A design changes only through a decision; a choice within what a decision decides (a field, a tool, an event, a
bound) is the design's own, written in it with its reason. Propose the record first; if it reverses an earlier one,
it `supersedes` it (or `changes` the clauses it replaces) — **never edit the earlier record's text**;
a correction of fact is a progressive insight (`propose` the accepted record with `insight`). Then edit
the design, add `"ADR NNNN"` to its `decisions:`, and set `updated:`.
+6 -3
View File
@@ -19,9 +19,12 @@ Hands a settled design to a code repository. **Authoritative playbook:**
3. **Read the as-is counterpart.** What is being replaced must be written down in
`03-DESIGN/00-as-is/` before it is changed. Building against an undocumented as-is is how
shipped behaviour gets lost.
4. **Flip the status** to `in-progress`, `updated:` today.
5. Build in the code repository. HQ never carries implementation.
6. On completion, run `hq-graduate`'s "when something ships" section.
4. **Give each milestone its part** (playbook 04 step 4a, playbook 02 *The shape of a design*): every line it
builds in `NN-milestones/<milestone>.md`, numbered, at most 60 KB; the main file at most 80 KB.
5. **Flip the status** to `in-progress`, `updated:` today.
6. Build in the code repository, from the milestone part alone (playbook 07): no code before every design question
its prepare finds is answered by the operator and written into the design. HQ never carries implementation.
7. On completion, run `hq-graduate`'s "when something ships" section.
## Non-negotiable
+1 -1
View File
@@ -21,7 +21,7 @@ All through the mesh MCP server (`mesh_describe` gives each verb's arguments).
number is spent; never pick one by hand.
3. **Propose the effort.** `mesh_call knowledge-base.propose` with `kind: research`, the reserved `number`,
`title`, `status: active`, `date` (today), `sections` (the documents in order, each `name` and `text`;
the first `00-overview.md`: **what** is investigated, **why**, and **what it touches**), and `links`
each named by a plain title, never as a file; the first, **Overview**: **what** is investigated, **why**, and **what it touches**), and `links`
of kind `touches` for every record, effort, issue or design it touches (`cites` for one it only names).
4. **Do the research** in further documents of the same effort. Each change is `propose` again with the
effort whole and `why`. A refusal names every rule broken: fix them all and propose again. On review
+1
View File
@@ -1 +1,2 @@
__pycache__/
.work/
+6 -4
View File
@@ -9,11 +9,12 @@ in memory. The Python files and `merge-check.sh` are gone (issue 1296).
records_test.go structure: links, no record written here, citations in the form "ADR NNNN"
index_test.go the reading order's topics are written, and no list of records is stored
cycle_test.go the development cycle: a design's layer, status, decisions and code; an issue report's frontmatter
words_test.go the glossary's retired words are not used, no daemon's line is written, and no word is defined twice
words_test.go the glossary's retired words are not used, no daemon's line is written, no word is defined twice,
and the glossary names no seat's holder
```
A failing test fails `go test`, so it can be a gate rather than a report. With `MESH_CATALOG_DIR` set, the words
test also compares the catalogue's copies of the retired words and the daemons' lines with the glossary.
test also compares the catalog's copies of the retired words and the daemons' lines with the glossary.
**Why this exists.** Until now nothing in this repository was verified by anything but reading,
which is how a superseded decision stayed live in the constitution for days and in
@@ -31,6 +32,7 @@ indistinguishable from one that cannot.
| `decisions-form` | every entry of a design's `decisions:` is "ADR NNNN", four digits | written with issue 426; it failed on an entry `ADR 290` before it passed |
| `mention-form` | every "ADR N" in the text has four digits, and every "research N" three; a code span is an example and is not read | written with issue 426; it failed on `ADR 12` and `research 45` before it passed |
| *(index test)* | the reading order's six topics are written in `02-DECISIONS/README.md`, none twice, and the README stores no list of records (ADR 0248). The knowledge base reads these lines and refuses a record whose topic is not one of them | a stored list made every two open decisions conflict ([issue 297](../../04-ISSUES/297-two-open-decisions-always-conflict/00-report.md)); it failed on the README of `main` before the list was removed |
| `holders` | the glossary names no module as a seat's holder: no seat "held by" or "claimed by" a named module, no named module "holder of" a seat, no module that "holds the seat"; the holder is read from the mesh's assignments (the operator, 2026-11-09) | **twenty statements** in the glossary, from the registry seats' "held today by the forge module" to the knowledge base's holder |
| `status-vs-code` | a to-be document naming specific code is not still `designed` | **ten documents**, several with a *What was built* section, describing lab-proven code |
## Where the rules on records went
@@ -99,7 +101,7 @@ quotations, struck-through text, link targets, comments and frontmatter are take
`03-DESIGN/`, `AGENTS.md`, `README.md`, and issues opened from 2026-10-07. Decision records and research
efforts live in the knowledge base, which holds a proposed record to the same words (the table above). It
also fails when a glossary head word heads two entries or is also retired.
[`words-allowed.md`](words-allowed.md) names a document that could not be reworded at once, with a date. With `MESH_CATALOG_DIR` set it compares the catalogue's copy
[`words-allowed.md`](words-allowed.md) names a document that could not be reworded at once, with a date. With `MESH_CATALOG_DIR` set it compares the catalog's copy
of the tools' retired words (`retired-words`) with the glossary.
It also fails on a **daemon's line** (ADR 0272,
@@ -107,7 +109,7 @@ its insight of 2026-10-09): the operator types one command, `nox`, and a documen
controller's or the node-engine's binary followed by one of its commands. The lines are the check's own list
(`daemonLines` in the words test); a command that is a line only with an argument after it is matched as a line is written. Looked
for in the same documents, from 2026-10-09 for issues, in the document's own words — code blocks and
spans included, quotations and block quotes taken out. With `MESH_CATALOG_DIR` set it compares the catalogue's copy
spans included, quotations and block quotes taken out. With `MESH_CATALOG_DIR` set it compares the catalog's copy
(`retired-lines`) with the list.
It failed on something real before it passed: 581 uses of retired words in 63 documents on its first run, besides the glossary itself and research 034 —
+7 -1
View File
@@ -19,4 +19,10 @@ which keeps a record's words as it was imported.
| `04-ISSUES/353-the-controller-asked-before-the-bus-held-its-grant/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
| `04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
| `04-ISSUES/362-two-merges-seconds-apart-left-their-delivery-waiting-for-a-person/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
| `04-ISSUES/364-the-gate-refuses-a-module-that-uses-a-seat-another-module-declares/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "merge gate" (issue 1303); it is not rewritten |
| `04-ISSUES/364-the-gate-refuses-a-module-that-uses-a-seat-another-module-declares/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the words of its day, "merge gate" (issue 1303) and "judge" (the glossary's manifest validator retired the noun); it is not rewritten |
| `04-ISSUES/286-a-merge-check-passed-on-an-agents-machine-and-failed-on-the-build-seat/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the composition check's program built from a change; the glossary's manifest validator retired the noun); it is not rewritten |
| `04-ISSUES/306-a-check-on-the-control-node-ran-past-its-timeout/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the composition check's program built from a change; the glossary's manifest validator retired the noun); it is not rewritten |
| `04-ISSUES/310-a-pull-request-ran-its-own-copy-of-its-check/00-report.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the composition check's program built from a change; the glossary's manifest validator retired the noun); it is not rewritten |
| `04-ISSUES/343-the-controllers-terminal-has-no-command/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (a part of the controller's pull request, named so then; the glossary's manifest validator retired the noun); it is not rewritten |
| `04-ISSUES/361-the-control-nodes-agent-account-is-never-judged/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "judge" (the self-check's look at what an account can reach; the glossary's manifest validator retired the noun); it is not rewritten |
| `04-ISSUES/299-an-optional-verb-makes-a-holder-look-silent/01-diagnosis.md` | 2027-04-30 | history: the issue's narrative keeps the word of its day, "seat set" (the seats compiled into the controller then; the glossary's seat list retired it); it is not rewritten |
+116 -10
View File
@@ -35,7 +35,11 @@ import (
// struck-through text, link targets, HTML comments and frontmatter are taken out. A word's plural is the word.
// unique no head word heads two entries, and none is also struck through on a *Not:* line.
// lines no daemon's line in a checked document (ADR 0272, its insight of 2026-10-09): the operator types `nox`.
// copy with MESH_CATALOG_DIR set to a checkout of the catalogue, its copies of the retired words and the
// holders the glossary says no seat's holder: no seat "held by" or "claimed by" a named module, no named module
// "holder of" a seat, no module that "holds the seat" (the operator, 2026-11-09). Who holds a seat is read
// from the mesh's assignments, and the glossary's head says where.
// domains no retired domain heads a section of the glossary (ADR 0641 §5: Placement).
// copy with MESH_CATALOG_DIR set to a checkout of the catalog, its copies of the retired words and the
// daemons' lines equal this glossary's and this list (skipped, with its reason, when unset).
//
// Checked documents: 00-META/, 03-DESIGN/ (both layers), AGENTS.md and README.md, always; an issue opened on or after
@@ -52,7 +56,7 @@ const (
linesFrom = "2026-10-09"
)
var wordsAlways = []string{"00-META/", "03-DESIGN/", "AGENTS.md", "README.md"}
var wordsAlways = []string{"00-META/", "03-DESIGN/", ".claude/", "AGENTS.md", "README.md"}
// daemonLines are the daemons' lines (ADR 0272): each daemon's commands. "alone" is a command that is a line by
// itself; "with an argument" one that is a line only when written as one ("the mesh-controller module" is prose;
@@ -396,7 +400,7 @@ func checkUnique(retired []retiredWord, heads []string) []string {
return out
}
// linesList is the daemons' lines as the catalogue's copy lists them: "<daemon> <command>", with " …" when an
// linesList is the daemons' lines as the catalog's copy lists them: "<daemon> <command>", with " …" when an
// argument must follow.
func linesList() []string {
var out []string
@@ -479,26 +483,26 @@ func readList(file string) ([]string, error) {
return out, nil
}
// checkCopy compares the catalogue's copies of the retired words and the daemons' lines with this glossary's. A
// checkCopy compares the catalog's copies of the retired words and the daemons' lines with this glossary's. A
// difference names what the copy should list, so regenerating it is copying that from the failure.
func checkCopy(catalogue string, retired []retiredWord) []string {
var out []string
copyWords, err := readList(path.Join(catalogue, "retired-words"))
if err != nil {
return []string{"the catalogue's copy of the retired words cannot be read: " + err.Error()}
return []string{"the catalog's copy of the retired words cannot be read: " + err.Error()}
}
sort.Slice(copyWords, func(i, j int) bool { return strings.ToLower(copyWords[i]) < strings.ToLower(copyWords[j]) })
want := toolsList(retired)
if strings.Join(copyWords, "\n") != strings.Join(want, "\n") {
out = append(out, "the catalogue's retired-words differs from the glossary: it should list "+strings.Join(want, ", "))
out = append(out, "the catalog's retired-words differs from the glossary: it should list "+strings.Join(want, ", "))
}
copyLines, err := readList(path.Join(catalogue, "retired-lines"))
if err != nil {
return append(out, "the catalogue's copy of the daemons' lines cannot be read: "+err.Error())
return append(out, "the catalog's copy of the daemons' lines cannot be read: "+err.Error())
}
sort.Strings(copyLines)
if strings.Join(copyLines, "\n") != strings.Join(linesList(), "\n") {
out = append(out, "the catalogue's retired-lines differs from this check's list: it should list, one per line:\n"+strings.Join(linesList(), "\n"))
out = append(out, "the catalog's retired-lines differs from this check's list: it should list, one per line:\n"+strings.Join(linesList(), "\n"))
}
return out
}
@@ -532,7 +536,7 @@ func TestWordsNoDaemonLineWritten(t *testing.T) { expectNone(t, checkLines(rootF
func TestWordsCatalogueCopyAgrees(t *testing.T) {
catalogue := os.Getenv("MESH_CATALOG_DIR")
if catalogue == "" {
t.Skip("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalogue's copy of the list was not read")
t.Skip("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalog's copy of the list was not read")
}
retired, _, _ := parseGlossary(readFile(rootFS(), glossaryFile))
expectNone(t, checkCopy(catalogue, retired))
@@ -691,7 +695,7 @@ func TestAllowanceHoldsAWordBackUntilItsDateAndShrinks(t *testing.T) {
expectProblem(t, checkRetired(mapOf(tree("an overlay\n", "")), retired, nil, "2026-10-23"), `AGENTS.md:1: "overlay" is retired`)
}
// H5d: the daemons' lines and the catalogue comparison.
// H5d: the daemons' lines and the catalog comparison.
func TestLinesCatchesADaemonsLineButNotProse(t *testing.T) {
fsys := mapOf(map[string]string{
@@ -754,3 +758,105 @@ func mapOf(files map[string]string) fs.FS {
}
return m
}
// holders: the glossary defines a word and never says which module holds or claims a seat. Who holds a seat is a fact
// of the mesh's assignments, read with one call (the operator, 2026-11-09: "it shouldn't be in the glossary"), and a
// copy here drifts. Refused, across line breaks: a seat "held by" or "claimed by" anything but a generic holder or a
// seat ("held once for the mesh by `x`", "held today by the forge module"); a named module "holder of" a seat ("the
// module `x`, holder of"); a module that "holds the seat" ("it holds the seat", "the `x` module holds it"); and "its
// holder, the x". A word for the role ("the module that holds the seat `x`") names no module and passes.
var (
heldByRx = regexp.MustCompile("(?i)\\b(held|claimed)(\\s+once)?(\\s+for\\s+the\\s+mesh)?(\\s+today)?\\s+by\\s+")
// What may follow "held by": a holder in general, a seat, or the knowledge base (a seat's word) holding a record.
heldByGenericRx = regexp.MustCompile("^(a\\s+module\\b|some\\s+module\\b|its\\s+holder\\b|the\\s+holder\\b|the\\s+identity\\s+seat\\b|the\\s+knowledge\\s+base\\b|one\\s+author\\b|the\\s+`[^`]+`\\s+seat\\b|the\\s+(node\\s+)?seat\\s+`)")
holderNamedRx = []*regexp.Regexp{
regexp.MustCompile("(?i)(\\bmodule\\s+`?[a-z][a-z0-9-]*`?|`[a-z][a-z0-9-]*`)\\s*,\\s+(the\\s+)?holder\\s+of\\b"),
regexp.MustCompile("(?i)\\b(it|which)\\s+holds\\s+(the\\s+)?(node\\s+)?seats?\\b"),
regexp.MustCompile("(?i)\\bmodule\\s+holds\\s+(it|the\\s+(node\\s+)?seat)\\b"),
regexp.MustCompile("(?i)\\bholder\\s*,\\s+the\\s+[a-z`]"),
}
)
func checkHolders(text string) []string {
var out []string
say := func(i int, m string) {
out = append(out, fmt.Sprintf("%s:%d: %q names a seat's holder; the glossary says where it is read instead", glossaryFile, lineOf(text, i), strings.Join(strings.Fields(m), " ")))
}
for _, loc := range heldByRx.FindAllStringIndex(text, -1) {
rest := text[loc[1]:]
if heldByGenericRx.MatchString(rest) {
continue
}
end := loc[1] + min(len(rest), 30)
say(loc[0], text[loc[0]:end])
}
for _, rx := range holderNamedRx {
for _, loc := range rx.FindAllStringIndex(text, -1) {
say(loc[0], text[loc[0]:loc[1]])
}
}
return out
}
func TestWordsGlossaryNamesNoSeatHolder(t *testing.T) {
expectNone(t, checkHolders(readFile(rootFS(), glossaryFile)))
}
func TestHoldersRefusesANamedHolderAndPassesARole(t *testing.T) {
for _, bad := range []string{
"- **x** — the seat `x`, held once for the mesh by the module `mesh-x`.",
"- **x** — the seat `x`, held\n by `mesh-x`.",
"- **x** — `go-package-registry` (held today by the forge module).",
"- **x** — the seat `x`, claimed by mesh-x.",
"- **x** — the module mesh-x, holder of the seat `x`.",
"- **x** — the module `mesh-x`, holder of `x`.",
"- **mesh-x** — the module that does x: it holds the seat `mesh-x` once.",
"- **bus** — The `nats` module holds it.",
"- **x** — written by its holder, the assigner.",
} {
if len(checkHolders(bad)) == 0 {
t.Errorf("not refused: %q", bad)
}
}
for _, good := range []string{
"- **seat** — a named role, held by a module assignment.",
"- **router** — the module that holds the `operator-channel` seat.",
"- **bus** — Held by the `mesh-broker` seat.",
"- **x** — the seat `x`; its holder is read with `mesh-module-assigner.assignments`.",
"- **depends on a seat** — a seat held on its node by some module.",
} {
if got := checkHolders(good); len(got) != 0 {
t.Errorf("refused a role: %v", got)
}
}
}
// domains: a retired domain is no heading of the glossary. ADR 0641 §1 retired Placement into Core (module
// assignment) and Delivery (the wants), and §5 has this test refuse it as a domain heading.
var retiredDomains = []string{"Placement"}
func checkRetiredDomains(text string) []string {
var out []string
for _, d := range retiredDomains {
rx := regexp.MustCompile(`(?m)^##\s+` + regexp.QuoteMeta(d) + `\b`)
for _, loc := range rx.FindAllStringIndex(text, -1) {
out = append(out, fmt.Sprintf("%s:%d: %q is a retired domain (ADR 0641 §1) and heads no section", glossaryFile, lineOf(text, loc[0]), d))
}
}
return out
}
func TestWordsGlossaryHasNoRetiredDomainHeading(t *testing.T) {
expectNone(t, checkRetiredDomains(readFile(rootFS(), glossaryFile)))
}
func TestRetiredDomainsRefusesTheHeadingAndPassesProse(t *testing.T) {
if len(checkRetiredDomains("## Placement — what runs on which node\n")) == 0 {
t.Error("a Placement heading was not refused")
}
if got := checkRetiredDomains("## Core\n\nwhich was the Placement domain until ADR 0641\n"); len(got) != 0 {
t.Errorf("prose refused: %v", got)
}
}
+603 -116
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -196,7 +196,7 @@ Three levels, by how much real system a test uses (ADR 0391):
| Level | Uses | Budget | Runs |
|---|---|---|---|
| **Unit** | one piece of logic alone: no database, network, bus, running service or other process | well under a second per test | locally, the tests of what changed, while working; and in the forge's checks |
| **Integration** | the real thing beside it: a database, the bus, a catalogue tree | the repository's whole suite in seconds, never over sixty | only in the forge's checks, once per push |
| **Integration** | the real thing beside it: a database, the bus, a catalog tree | the repository's whole suite in seconds, never over sixty | only in the forge's checks, once per push |
| **End to end** | the whole mesh, or a lab mesh, from the outside | — | only when the operator asks |
- **Test each behaviour at the lowest level that can prove it.** A defect found higher up gets a test at the
@@ -209,7 +209,7 @@ Three levels, by how much real system a test uses (ADR 0391):
### Tests within a time budget — how it was earned
On 2026-10-16 a feature's fixing agents ran the catalogue's whole merge check (about ten minutes), a
On 2026-10-16 a feature's fixing agents ran the catalog's whole merge check (about ten minutes), a
throwaway database and a locally built gate during local development. Each round cost tens of minutes and
caught neither real failure; the forge's checks found both.
+30
View File
@@ -0,0 +1,30 @@
---
status: canonical
updated: 2026-11-11
---
# The principles
A short, living list. Every question met while building is tested against these principles and answered by the
lead in one line; only a question no principle settles goes to the operator or to a decision record. A detail that
a principle settles is not written down as a design line or a record. The list changes when the operator says so:
add, sharpen or drop a principle in a pull request to this file.
1. **NATS-native.** Every interaction between modules is an event, or a call over the bus where an answer is
needed. A module keeps its data in the bus's own storage, and reads another module's data only by asking it,
through its verbs or its tools.
2. **The mesh owns the truth.** State lives in the mesh and is written onto the nodes from there. Nothing is changed
by hand on a node.
3. **Failure is loud.** Nothing reports success when it did nothing, and nothing fails silently. When in doubt, fail.
4. **A module is steered by its tools.** Everything a module holds can be read, everything it does can be started
by hand, and everything that goes wrong can be recovered, all through its tools.
5. **Everything ships the same way.** The mesh's own parts go through the same build, check and delivery as any app.
6. **Nothing is exposed publicly.** No data leaves the mesh unasked: no secret in a record, a log or a public
repository, no service reachable from outside unless it is meant to be. Inside the mesh, every agent and module
is trusted; who may do what is not a question to ask.
7. **The simplest thing that works.** Prefer what already exists. No new mechanism unless it is needed, and fewer
parts over more.
8. **One word per thing**, from the [glossary](glossary.md).
How it is checked: the one code review of a milestone checks the code against these principles and the design's
shape; an answer to a develop question names the principle that settles it.
+13 -1
View File
@@ -46,6 +46,18 @@ to-be document's frontmatter goes to `implemented`, and both stand — one descr
the other recording what was intended. Deleting the intention loses the reasoning, which is
the expensive half.
## A design is built as it is written
Research, decisions and the review by architectural domain are where the mesh's choices are made; building makes
none (the operator, 2026-11-10). A design is written to be built without a choice of the builder's own, in a main
file and one part per milestone that an agent reads whole (playbook [02](02-graduation.md), *The shape of a
design*). The prepare, develop and the code review of playbook [07](07-feature-branches.md) read only that design:
the prepare says where each design line lands in code and lists what the design does not answer, the operator
answers each such design question and the answer is written into the design, and only then does develop start. Every
commit cites the design lines it builds, and the review judges each change against its line. How it is checked:
playbook 07's *How it is checked* (the build maps, the commits' `Builds:` lines, the design questions' answering hq
commits).
## The playbooks
A drawn overview of them, the cycle, a feature's steps, the review loop and an issue's life, is
@@ -59,7 +71,7 @@ A drawn overview of them, the cycle, a feature's steps, the review loop and an i
| [04](04-build-handoff.md) | Work breakdown and handoff | A design is ready to be built: milestones usable on their own, issues per repository, each milestone one feature step |
| [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces |
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery |
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: a build map from the milestone's part of its design, the design complete before any code, local development, one review against the design lines, one check, one delivery |
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one review, one check, one delivery |
| [09](09-cleanup.md) | Cleanup | Merged branches, stale pull requests, finished worktrees and folders: read first, delete only what the operator approves, report the rest |
| [10](10-visual-design.md) | Visual design | A page the operator will use is about to be designed or rebuilt: mockups with fixed data, the operator picks, the pick is the reference |
+62 -14
View File
@@ -39,20 +39,33 @@ An idea arrives in one of two forms, and the first step differs:
- **Explain each question with a concrete picture** in the operator's words: what they would see, do or
lose under each option. No internal names (a record number, a verb, a word the glossary lacks) without
what it is beside it.
- **Write the requirements from the answers, before the options.** Each one line, numbered, saying what
the result must hold and how it is checked (for example "R5. A change across repositories is
delivered whole or not at all; check: a change over three repositories"). The requirements shape the
effort: every option is weighed against them, and the operator corrects them before any option is
worked out (the operator, 2026-11-09: "coming up with a list of requirements can also be an important
part, it actually shapes the research").
3. **Take a number.** `knowledge-base.reserve` with `kind: research` and `for`: the effort's title. The number
is spent whether or not the effort is ever proposed; one never proposed is withdrawn after a week. Never
pick one by hand.
4. **Propose it.** `knowledge-base.propose` with `kind: research`, the `number`, a one-line `title`,
`status: active`, `date` (the day it began), and `sections`, each a `name` and its markdown `text`:
- `00-overview.md`: the idea in the operator's words, what exists, the operator's answers (dated), and the
`status: active`, `date` (the day it began), and `sections`, each a `name` and its markdown `text`. A
section is a part of the record, never a file: its `name` is a plain title, a reference to another section
names that title ("the Direction's test 8"), and code evidence names the repository, the commit and the
function, never a path or a line (the operator, 2026-11-02: "don't use file references or local files
anymore"; the knowledge base is to be shown as a web page, where a file name links to nothing). Once
proposed, the record is the only copy: it is changed by `knowledge-base.get` and `propose`, never kept
in a local draft. The first sections:
- **Overview**: the idea in the operator's words, what exists, the operator's answers (dated), and the
words the effort uses that the glossary lacks, each defined in one line;
- `01-questions.md`: the questions to answer, the first ones first;
- `02-method.md`: what will be tried and measured, and on what.
- **Requirements**: the numbered requirements of step 2, each with its check;
- **Questions**: the questions to answer, the first ones first;
- **Method**: what will be tried and measured, and on what.
Name what it touches as `links` of kind `touches` (`"0276"`, `"research 045"`, `"issue 365"`, or a
design's path), and what it names for another reason as `cites`.
5. **Do the research.** Try, measure and read, smallest question first. Each finding goes into
`04-findings.md`: what was tried, what it showed (with numbers), what it means. Each change of the effort
the section **Findings**: what was tried, what it showed (with numbers), what it means. Each change of the effort
is `knowledge-base.propose` again, whole, with `why`; every version is kept, and `knowledge-base.history`
lists them and gives the difference between two. Until issue 844 is delivered, the knowledge base
takes a new version of an accepted, active effort only as a progressive insight: give `insight`, saying
@@ -69,20 +82,52 @@ An idea arrives in one of two forms, and the first step differs:
server, a protocol) is tried in process, on the machine's loopback, in minutes. Lab runs (about 12 to
16 minutes fresh) are for the mesh's own code; before that code exists, a lab run tests nothing an
in-process test does not, and its lab scenario is written as the build's acceptance test instead.
6. **Place it in the domains.** Before the direction, write `03-domains.md`: the domain or domains the idea
6. **Place it in the domains.** Before the direction, write the section **Domains**: the domain or domains the idea
belongs to (the glossary's sections, ADR 0244); the modules and seats it lands in, each new, changed or
retired; what it does to each domain's words, each word added (with its domain and a one-line meaning),
changed or retired, and none on a *Not:* line or already meaning something else (the glossary's homonyms);
and whether a domain grows or shrinks, a concept moving between domains included. The direction names its
decisions in those words. Asked when the idea is set up, too: an option that lands a concept in the wrong
domain is offered with that cost named, like one that breaks a rule.
7. **Settle the direction with the operator.** When the questions are answered, write `05-direction.md`:
the proposed decisions in a few lines each, the options rejected and why, and the build's acceptance
tests: what the build must prove before it is done, carried by its milestones (playbook
7. **Settle the direction with the operator.** When the questions are answered, write the section **Direction**:
the question the effort answers, the options weighed and the one proposed (a second only for a second
question of its own; playbook [02](02-graduation.md), step 2), the design choices that follow from it in a
line each, and the build's acceptance tests: what the build must prove before it is done, carried by its milestones (playbook
[04](04-build-handoff.md)). Nothing else is left open: each question closes by a test, by a decision the
effort records with its reason (a small design limit is the lead's), or by the operator. Ask the
operator to confirm or change the direction, one point at a time, then propose it once. Then the effort
graduates through playbook [02](02-graduation.md).
operator to confirm or change the direction, one point at a time. Then review it by architectural domain (below) and
propose it once. Then the effort graduates through playbook [02](02-graduation.md).
## Review by architectural domain
A direction the operator settled can still be wrong where nobody looked. Before it is proposed, and again
before a design is merged (playbook [02](02-graduation.md)), it is reviewed against each **architectural domain**
(the operator, 2026-11-02), by reviewers on the larger model, about two architectural domains each, side by side:
the four reviewer agents in `.claude/agents/` (`review-security-and-upgrade`, `review-seams-and-tools`,
`review-running-and-testing`, `review-health-and-events`), each given the subject by number or path:
- **security**: who may call each new verb, how its grants are scoped, which credentials go or remain, what
can be replaced or forged, and what enters from outside the mesh;
- **seams**: where each piece of state lives on the bus, which seat, verb or event joins two parts, and any
link that is not NATS-native. Each piece of state is kept in the bus storage form that fits it (a key-value
bucket, a stream, an object store); a form the manifest lacks is added to mesh-sdk, and the review names that change
and its cost (a mesh-sdk change rebuilds every module) to the operator, never folds it in silently (the
operator, 2026-11-02);
- **running qualities**: build capacity, speed, storage growth, and what happens while a part, the bus or the
control-node is down;
- **health**: what each part's health check says, which conditions are raised and cleared, and whether any
failure can pass silently;
- **events and logs**: what each step says on the bus, and what can be read back later;
- **testing**: an acceptance test for every decision, each one checkable and within the test budget, and the
failure paths;
- **upgrade**: how what runs today moves to the new shape without a delivery breaking, in which order, and
how it is rolled back;
- **tools**: which tool reads each new piece of state and which one steers each new behaviour.
A reviewer judges only whether the settled direction is complete and sound in its architectural domains; it never reopens
a decision. Each finding is a sentence for the direction or a new acceptance test; a finding that changes a
decision goes to the operator. The direction ends with a section *Reviewed by architectural domain*: per architectural domain, what was
found and how it was answered, or that nothing was found.
## Bounds and models
@@ -90,9 +135,10 @@ An idea arrives in one of two forms, and the first step differs:
|---|---|---|
| reading and the setup questions (1, 2) | 30 minutes | the session's own, larger |
| proposing (3, 4) | 15 minutes | larger |
| each experiment or reading task (5) | 30 minutes | smaller, one agent at a time unless the operator allows more |
| each experiment or reading task (5) | 30 minutes | smaller, one agent at a time unless the operator allows more: the agent `research-reader` |
| placing it in the domains (6) | 15 minutes | larger |
| the direction (7) | 30 minutes | larger |
| the review by architectural domain | 20 minutes | larger, about two architectural domains per reviewer |
A step past its bound is said to the operator at once, with what is being done about it. The lead reports
as playbook 07 does: every ten minutes and at once when something is found, stops or needs the operator,
@@ -125,7 +171,8 @@ for the operator's Approve on a channel that proves who answered.
## Closing
An effort never just stops. It closes through playbook [02](02-graduation.md) as `graduated` or
`abandoned`, always with a `became` link to what it turned into. Nothing is deleted — what was rejected,
`abandoned`: a graduated effort with a `became` link to what it turned into, an abandoned one with a `cites` link to
what replaced it (playbook 02 step 5). Nothing is deleted — what was rejected,
and why, is the more expensive half to rediscover.
## How it is checked
@@ -134,7 +181,8 @@ The rules on an effort are the knowledge base's: `propose` refuses one that brea
record (the module's table test holds one case per rule), and the rules that judge the whole record set run
after every acceptance and raise a condition when one fails. The habits of steps 2, 5, 6 and 7 (options checked
against the rules, one version per settled set, the cheapest level, nothing left open) are the lead's, read by
the reviewer; of the pre-check, `propose` refuses a link to nothing and a retired word, and the review returns
the reviewer; the review by architectural domain is read in the direction's *Reviewed by architectural domain* section (nothing refuses a direction
without it yet: the lead and the reviewer hold it); of the pre-check, `propose` refuses a link to nothing and a retired word, and the review returns
a record named but not linked, an untracked deferral and a figure not as measured. That this repository holds
no effort is the records test's `no-records` check, on every pull request. An active effort with no new version
for seven days is the smell of research left behind: its lead closes or continues it.
+115 -11
View File
@@ -13,7 +13,7 @@ clauses in force for a topic or a record).
## Graduating research
The effort's `05-direction.md`, confirmed by the operator (playbook [01](01-research.md), step 7), is what
The effort's Direction, confirmed by the operator (playbook [01](01-research.md), step 7), is what
graduates. The lead writes the records and the design on the larger model; it is one sitting, bounded at
about an hour, and said to the operator if it runs past.
@@ -21,8 +21,17 @@ about an hour, and said to the operator if it runs past.
[`mission.md`](../mission.md), [`context.md`](../context.md) and
[`effect.md`](../effect.md). If it conflicts, either the effort is wrong or GENESIS is —
say which, in writing, before proceeding.
2. **Record the decisions** (below, *Writing a decision record*): one record per choice that could be
reversed on its own, not one record for the whole effort.
2. **Record the decisions** (below, *Writing a decision record*): one record per question the effort
answers, holding that question, the options weighed and the one chosen. Most efforts answer one question
and graduate into one record; a second record is written only for a second question of its own, such as
how the change can be checked or delivered at all (the operator, 2026-11-06: an effort graduated into
eleven records where it answered two questions). The choices made while working out the design that
follows (a field, a tool, an event, a bound, the order of steps) are the design's: written in the design
with their reasons, under the record they follow from, never as records of their own. A clause of an
earlier record the decision changes is a `changes` link from the record, with one line saying in what.
How it is checked: the lead names, in the graduation's report to the operator, the question each record
answers; an effort graduating into more than two records is said to the operator with why, before the
records are proposed.
- **Write them together, not one after another.** Reserve every number first, then propose each record
as soon as it is written, without waiting for the review of the one before; the reviews run side by
side, and the operator's asks for the records that need them arrive together. A record that
@@ -49,21 +58,39 @@ about an hour, and said to the operator if it runs past.
---
```
The design lists the direction's acceptance tests: what the build proves before it is done.
The design lists the direction's acceptance tests: what the build proves before it is done. Before it is
merged, the design is reviewed by architectural domain as playbook [01](01-research.md#review-by-architectural-domain) says, and
ends with its own *Reviewed by architectural domain* section; a design amended without an effort is reviewed the same way.
The words of the effort's `03-domains.md` go into the glossary in the same change, each in its domain: a
The words of the effort's Domains go into the glossary in the same change, each in its domain: a
word added gets its entry, a word whose meaning changed gets its entry rewritten, and a retired word moves to
its replacement's *Not:* line. A word that already means something else in another domain is named as a
homonym with the qualified form each domain uses.
4. **Hand it to the work breakdown.** The design goes on through playbook [04](04-build-handoff.md):
milestones, issues, and each milestone built as one feature step (playbook [07](07-feature-branches.md)).
5. **Close the effort.** `knowledge-base.propose` the effort again, whole, with `status: graduated` and a
`became` link to the decision record (`"NNNN"`) and to the design document (its path). An effort that
stops without a decision is proposed with `status: abandoned` and a `became` link to what replaced it.
5. **Close the effort.** `knowledge-base.close` the effort with `to: graduated`, a `became` link to each
decision record (`"NNNN"`) and the design document (its path), and a note of two or three sentences (what it
became, what carries it). The verb keeps the served text unchanged and adds a section *Graduated* with the note;
the knowledge base's review judges only the closing. Never close by proposing the effort again whole:
that is judged as a fresh proposal, so the approved reasoning is re-judged against the decisions that replace it
(on 2026-11-10 an effort closed that way met 5, then 11 findings on unchanged text; `close` was approved at once).
An effort that stops without a decision is closed `to: abandoned` with a `cites` link to what replaced it: the
knowledge base refuses `became` from a research effort to another research effort (its link-fits rule, design
55 § 2), so a replacing effort is cited, and `became` stays for the decision and the design an effort turned
into.
## Writing a decision record
**Before a number: what holds, and the mesh path, reviewed.** Ask the knowledge base what is already decided in
the record's area (`knowledge-base.ask`, `holds`), and name in the draft, for every interaction between modules the
record decides, the seat and verb, event, provision, state on the bus or manifest field that carries it. Then
`review-seams-and-tools` reviews the draft before it is proposed: a clause that has one module name another, read its
internals or be special-cased in another part, or puts a module's name into shared code, is a finding (the operator,
2026-11-03).
How it is checked: the record's References name the seams review's verdict; a record proposed without it is a breach
the lead names.
1. **Take a number.** `knowledge-base.reserve` with `kind: decision` and `for`: the record's title. The
number comes from the issue tracker's counter and is spent whether or not the record is accepted; one
never proposed is withdrawn after a week. Never pick a number by hand.
@@ -123,13 +150,89 @@ by a progressive insight.
record `changes` the older one's clause, so a reader who arrives at the older record by a citation sees
what changed it since.
## The shape of a design
A design is what its builders build from, and nothing else is (playbook [07](07-feature-branches.md)): decision
records and the mesh's principles are read when a design is written, amended or reviewed, and never again by the
prepare, the developers or the code review. So a design is written to be built without a choice of the builder's
own, and in pieces an agent can read whole. On 2026-11-10 one design of more than 400 KB was built in one
milestone: its agents read it by searching for pieces, filled what they did not find themselves, and an audit of
that day's 40 or so choices by the leads found seven against the records or the design and 29 questions the design
never answered, all found only in develop.
- **A main file and one milestone part per milestone.** The design's main file
(`03-DESIGN/01-to-be/NN-<slug>.md`) holds its frontmatter, its purpose, the whole shape (its parts, their seats,
events, provisions and state, drawn and in short prose), its acceptance tests and its *Milestones* section. Each
milestone not yet delivered has its **milestone part**, `03-DESIGN/01-to-be/NN-milestones/<milestone>.md`, which
holds every line that milestone builds and opens with frontmatter naming `layer: to-be`, the design, the
milestone and the `decisions:` its lines follow. A line lives in one place: a statement one milestone builds moves
from the main file into that part, never copied.
- **Bounds.** The main file at most 80 KB, a milestone part at most 60 KB. An agent reads both whole in under
40,000 tokens, under a fifth of what it can hold, and has the rest for the code. A milestone whose part would
pass 60 KB is two milestones (playbook [04](04-build-handoff.md)), each usable on its own.
- **Numbered design lines.** A milestone part's statements are numbered, each one thing to build with a bold
lead, like a decision's clauses, and cited as "design NN M §n"; a subtask, a commit and a review finding cite
them so. Each line names what its builder needs: the seat, verb or event, the field and its form, the key and
who deletes it, the bound, the caller, the tool that reads or steers it, and the fate of each thing that exists
today and changes. A line a builder would have to complete is a design question left open.
- **A delivered milestone's part stays.** It records what was intended, as a to-be design does once it ships
(the overview, *The two design layers*); no agent reads it again, so it costs no builder anything, and what runs is
written in the as-is design. The history of how a milestone was cut or why it waited is the issue tracker's and the
records', not the design's.
- **Design 47's next amendment is written in this shape**: the main file keeps the purpose, the whole shape, the
acceptance tests and the milestones, within 80 KB; every milestone gets its part with every line it builds or
built, moved from the main file, the one about to be built first; and the narrative of past milestones and their
prepare steps leaves the design for the issues it names. Every other design is put in this shape
at its next amendment, and a new design is written in it.
How it is checked: the reviewer of a design's pull request reads `wc -c` of the main file and of each milestone
part against the bounds, and that every statement of a milestone part is numbered; a check among hq's Go tests is to
be built (the cycle test reads only a design's main file today).
## Answering a design question
A design question (playbook [07](07-feature-branches.md), step 0) is first answered by the lead, in one line in the
milestone's lead-answers file, tested against the principles (`00-META/principles.md`) and naming the principle
that settles it, with no design batch and no review round (playbook 07, step 0, *Who answers a design question*; the
operator, 2026-11-11). The preparer, a developer and a reviewer answer none. Only a question no principle settles
comes to this section: the operator answers it when its answer changes what the operator sees or how they work,
removes data or needs the operator's terminal; every other one `hq-design-writer` answers as a design line of the
milestone part with its reason, under the record it follows from, read in item 3's round, and told to the operator in
one line. The lead asks the operator's as the instruction on asking says, one question per ask, with what the
milestone part says, what it leaves open, where it was met (file:line) and a recommendation. Then:
1. **Within a record's question** (a field, a bound, an owner, an order of steps, inside what a record decides), the
answer is the design's own choice: `hq-design-writer` writes it as a design line of the milestone part, citing
the operator's answer and its date and the record it follows from.
2. **Outside every record's question**, or **against a record** (the design contradicts a decision in force), the
answer is a decision: written first as *Writing a decision record* or *Changing a decision* above says, then as
design lines.
3. **The answers are reviewed together.** Before develop starts, the four reviewers by architectural domain read the
milestone part as amended, in one round (playbook [01](01-research.md#review-by-architectural-domain)); answers
met during develop are read by them in one round before the code review.
4. **They land on the milestone's branch.** The amendment is committed on hq's branch of the milestone
(`feat/<slug>`), so its pull request joins the milestone's delivery group, and the builders read the milestone
part at that branch's head.
How it is checked: every design question in the milestone file names the hq commit that answers it; a lead's answer
that names no principle, or a design line added during a milestone with no operator's answer or record behind it,
other than the writer's answer to one that changes nothing the operator sees or how they work, removes no data and
needs no terminal, is a finding of the review by architectural domain.
## Amending an existing design
A design changes only through a decision.
A design changes only through a decision. A choice within what a decision decides (a field, a tool, an event,
a bound) is the design's own, written in it with its reason; it needs no record of its own. It is made when the
design is written or amended, never while it is built: a choice the design leaves to its builder is a design
question (above).
1. Write the decision record (above). If it reverses an earlier one, it `supersedes` it.
2. Edit the to-be design document, add the record to its `decisions:` as `"ADR NNNN"`, and set `updated:`
to today.
to today, keeping the design in the shape above (a line one milestone builds goes into that milestone's part).
A set of amendments across several designs (a milestone's findings, a graduation's) is written by the
agent `hq-design-writer` (`.claude/agents/`), started with a handover naming the designs, the records and the
branch. Review it by architectural domain before it is merged (playbook [01](01-research.md#review-by-architectural-domain)), and
rewrite its *Reviewed by architectural domain* section.
3. If the amendment came from an issue, give the issue the document path in the issue tracker:
`amended-design` on its `issue-tracker.move` to `resolved`, or `issue-tracker.link` with kind
`design` before then — never in the report's frontmatter
@@ -164,7 +267,8 @@ record (its module's table test holds one case per rule), and the four rules tha
changes, an accepted record extending a superseded one) run after every acceptance and raise a condition
when one fails. In this repository, the records test checks that a design's `decisions:` entries and every
"ADR NNNN" in the text are well formed, and that no record is written here; the index test checks the reading
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. A design's *Reviewed by architectural domain* section is read by the
reviewer of its pull request; nothing refuses a design without it yet. 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,
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
+1 -1
View File
@@ -91,7 +91,7 @@ import read, which every imported issue names (`imported.commit` in `issue-track
ADR 0281).
4. Resolve. `issue-tracker.move` to `resolved`, which the tracker takes only from `fixing`, with `fixed-by`, and, if the root cause was a design gap,
run playbook [02](02-graduation.md) and give `amended-design`. **A core issue** — its `located-in`
names the controller, the node-engine, the tool runner, the SDK, or the catalogue's bus, forge or build
names the controller, the node-engine, the tool runner, the SDK, or the catalog's bus, forge or build
agent — resolves with `replay`, the id of its replay in mesh-lab's replays register, proved to fail on
the commit before the fix and pass on it, or with `replay-none` saying why none is possible
(ADR 0237);
+10 -1
View File
@@ -41,6 +41,11 @@ A design becomes **milestones**, and each milestone becomes **issues**:
first. A milestone is named by what it lets someone do; how it ships is shaped by the modules its changes move
and the nodes that run them (ADR 0401 §6), read from the indexer's fingerprints through the status view's
`indexer_modules` and `indexer_candidates`, and from the nodes' haves (`node_haves`).
4a. **Give each milestone its part** (playbook [02](02-graduation.md), *The shape of a design*): every line the
milestone builds moves from the main file into `NN-milestones/<milestone>.md`, as numbered design lines, each
naming what its builder needs. A milestone whose part would pass 60 KB is split here into two milestones, each
usable on its own; this is the one place a milestone is ever cut, for what one agent can read, before any work
starts. The main file stays within 80 KB. Written by `hq-design-writer` in the same change as the milestones.
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.
6. **Open the issues.** For each confirmed milestone, `issue-tracker.open` one issue per repository part, its
@@ -50,7 +55,9 @@ A design becomes **milestones**, and each milestone becomes **issues**:
7. **Flip the status.** `status: in-progress`, `updated:` today; the design change goes through playbook
[08](08-small-fixes.md) (one hq pull request).
8. **Hand each milestone to playbook 07, one at a time.** The milestone is one release of playbook 07; its issues
are the release's issues; the next milestone starts when the previous one is delivered. A design waits
are the release's issues; the next milestone starts when the previous one is delivered. Playbook 07 builds from
the milestone part alone and starts its develop step only once that part answers every design question its
prepare finds (playbook 07, *Design complete*); a question is never answered by building around it. 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
@@ -72,6 +79,8 @@ Read by the lead and the reviewer today; a check among hq's Go tests is tracked
- A design in `status: in-progress` has a *Milestones* section, each milestone with its outcome and its
issues; an in-progress design without one is the smell of building without a breakdown.
- Each milestone of an in-progress design has its part under `NN-milestones/`, within 60 KB, its lines numbered
(`ls -l 03-DESIGN/01-to-be/NN-milestones/`); a milestone handed to playbook 07 without one is step 4a skipped.
- Every issue a milestone names exists in the tracker (`issue-tracker.get`), and an issue in `fixing` names
its milestone in its report.
- A milestone whose issues are all `resolved` is delivered (the status view said `delivered` for its source
+66 -11
View File
@@ -33,11 +33,22 @@ Three questions, answered from the machine:
`package-manager`, `seat`. A capability is detected and refused against; it is not something a
module can install.
3. **Write the resources in the order they must happen.** They are applied in the order written
3. **Choose each part's form, from the table in [design 20](../../03-DESIGN/01-to-be/20-writing-a-module.md) *What a
module is built of*.** Tools are a `bundle` the tool runner loads (`loads`, and every tool in `tools`); a service of
the module's own code is a `process` resource; a scheduled job says `schedule`, a one-time step `run-once`, a
migration of the module's own state `prepares`; a seat's implementation is a claim and the verbs it serves; an image
is an `image` or `upstream` artifact run by a `container`; a program of the operating system is a `package` and its
`service` resources. A bundle or a package is in Go or TypeScript, the languages the builder has build modules for.
4. **Ask for kept data in `requires`, by template** (`state`, `events`, `work-queue`, `log`, `objects`, `ledger`), each
entry with its `name`, `storage`, `template` and `options`. The old sections `state`, `logs`, `streams` and
`objects` are for the parts the bus module needs before it can answer, and for no new entry anywhere else.
5. **Write the resources in the order they must happen.** They are applied in the order written
and orphans are removed in reverse, so a `network` is written before the containers that join
it and removed after them.
4. **Put every secret in a file, never in `env`.** A declaration travels over the broker in plain
6. **Put every secret in a file, never in `env`.** A declaration travels over the broker in plain
text: a password in `env` is a password the broker sees. **The mesh delivers parts; a module
that needs them combined combines them.**
@@ -67,21 +78,64 @@ Three questions, answered from the machine:
Add `restart-on` naming the env file, or the container keeps the credential it started with
through every rotation.
5. **Pin every image by digest.** A tag moves. The manifest in a repository names artifacts; the
manifest the mesh holds names digests, and they are not the same document.
7. **Pin every image by digest.** A tag moves. An `upstream` artifact's `from`, and a container's `image` written
directly, name `@sha256:`: the builder refuses an upstream reference with a tag alone. The manifest in a repository
names artifacts; the manifest the mesh holds names digests, and they are not the same document.
6. **Decide generate or accept.** A new module's credential is generated. **An adopted one keeps
8. **Decide generate or accept.** A new module's credential is generated. **An adopted one keeps
the credential it already has** — `secret accept` — because minting a new password for a
database that already exists locks the application out of its own data.
7. **Add a provisioner only if the software cannot read a file.** A proxy that watches a
9. **Add a provisioner only if the software cannot read a file.** A proxy that watches a
directory needs nothing. PostgreSQL needs `CREATE ROLE`, an object store needs a bucket and a
policy, an identity provider needs a realm and a client — those need a small program beside
them. It reads what the mesh granted and reconciles; it does not decide anything.
8. **Prove it in the lab, against the real software.** Not that a container started — that the
thing works: the credential authenticates, a wrong one is refused, the containers reach each
other, the data survives a restart.
10. **Prove it in the lab, against the real software.** Not that a container started — that the
thing works: the credential authenticates, a wrong one is refused, the containers reach each
other, the data survives a restart.
## Where the parts of the mesh read a manifest differently
*Written 2026-11-12 from reading the manifest validator, the builder, the node-engine's vocabulary and the tool runner
on their main branches.* The manifest validator accepting a manifest does not yet mean every other part takes it.
Until each of these is closed in the part named, write the form in the right column:
| the manifest validator accepts | but | so write |
|---|---|---|
| a `bundle` or a `package` in any language word | the builder builds only Go and TypeScript bundles and TypeScript packages, and refuses the rest by name at the build | a bundle in Go or TypeScript |
| an `upstream` artifact whose `from` names a tag alone | the builder refuses an upstream reference with no `@sha256:` | the reference with its digest |
| a `log` asked for in `requires` by template | the tool runner's log calls reach only a log of the old `logs` section; a template log is reached only by its stream's read and fill calls, so a module cannot append to it through the runtime | a template log only where the module's own bus account publishes it; a log its tools append to through the runtime waits for the tool runner's fix |
| any `type` and any field on a resource | the node-engine refuses an unknown type and a field the type does not use, when the module is composed for a machine: in the change's composition step only for the machines the change touches, and for a module assigned nowhere never | each resource's fields as the node-engine's vocabulary has them (mesh-sdk's declaration package) |
| `schedule` and `run-once` on a `process` unjudged | the node-engine refuses a process with both, or a scheduled one with `restart-on`, `replaces` or `witness`, or an invalid cron | a process in one mode |
How it is checked: each row is closed by a change in the part it names, which deletes the row in the same pull
request; a reviewer of a manifest reads these rows.
## A module that calls or holds a seat, or shares code
*Added 2026-11-04, widened 2026-11-05* (ADR 0625, ADR 0627, ADR 0634, ADR 0635). Where each piece of shared code goes
is [design 20](../../03-DESIGN/01-to-be/20-writing-a-module.md) *Where a seat's code goes*: shared code lives with its
owner. In short:
- **Import the seat's contract and, where it has one, its client package**, from the seat's domain's contracts module
(`mesh-<domain>-contracts`, or `mesh-contracts` for the Core domain and a domain with none), never the holder module
and never a copy of the seat's types in your own code. The list of seats your module needs is read from the bus,
never compiled in.
- **Logic you would write over a seat that another caller would write too** belongs in that seat's client package,
through a change of the contracts module; it never goes into mesh-sdk, and never into the holder.
- **Logic and state layouts only your module owns**, where it holds no seat for them, are your module's own package;
another repository's module imports it only by a published version, so your manifest declares it as a package
artifact.
- **Logic your domain's modules share, owned by no one module and carried by no seat**, is your domain's SDK's
(`mesh-<domain>-sdk`); only what modules of more than one domain use goes into mesh-sdk.
- **Holding a seat** means implementing its contract in your module. A seat you define yourself gets a contracts
module of its own (it may share your repository); a role no other module could ever take over is your module's
own tools and events, not a seat.
The review of the pull request checks the home of every new exported name; the checks design 26 lists refuse a stray
file in a contracts module, a seat named in mesh-sdk, an import of a contracts module from mesh-sdk, and a client
reaching another seat's client.
## What the first port actually cost
@@ -99,8 +153,9 @@ time and the scaffolding was not.**
## Rules
- **Read the node-engine's log before theorising.** A declaration that was sent and not applied says so
there and nowhere else.
- **Read the check's verdict, then the node-engine's log, before theorising.** The change's check names every
problem the manifest validator and the composition step found; a declaration that was sent and not applied says
so in the node-engine's journal (`<node>/node-service-manager.journal`) and nowhere else.
- **A failing test is kept, not skipped.** It is the reproduction.
- **Never rotate during an adoption.** Rotation is a separate act, afterwards, deliberately.
- **A data directory is never removed by the mesh** (ADR 0030),
+385 -113
View File
@@ -24,7 +24,7 @@ A small fix (one repository, one issue, about an hour of work) follows
- **Review that keeps hardening.** One fix went through seven review rounds in five and a half hours, each
asking for more hardening than its issue asked (issue 704, 2026-10-14). A round is for what makes the
change wrong; everything else is an issue of its own.
- **The first check after the whole branch is written.** A feature's catalogue branch, pushed once after
- **The first check after the whole branch is written.** A feature's catalog branch, pushed once after
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.
- **A green pull request nobody marks.** An approved, green pull request that was not marked ready waited
@@ -44,19 +44,38 @@ A small fix (one repository, one issue, about an hour of work) follows
on the bus; two reviews read the code, no test ran it under a node's real permissions, and once it was live every
node's agent was refused that read and stopped composing (issue 1126). How-we-build's rule "A bus permission is
proven under the real permission" (ADR 0560) is what the review now asks for.
- **A change built on what was remembered, not on what runs.** In the days up to 2026-11-02 a release took the
controller's jobs apart from the records and the survey alone. Jobs nobody had named surfaced only after the
delivery, a moved module's wiring and a changed failure label's readers were never read, and every check found one
more fault. Each layer had checked the layer before it; none had read the running code. Step 0 item 3a is the answer.
- **Implementation that decided.** On 2026-11-10 one milestone was prepared and developed from a design of more
than 400 KB, by agents told to start from the knowledge base and to report what was not mesh-shaped. The prepare
wrote its own findings and a file of about 40 rulings by its two leads, a third layer of truth between the design
and the code; a developer refused an event the design names; and an audit found seven rulings against the records
or the design (among them a store kept twice and a copy of another part's data) and 29 questions the design never
answered, all found in develop. The design is now the only input to the prepare and to develop, the prepare's only
output is where each design line lands and what the design does not answer, and develop starts only when it is all
answered (step 0b, *Design complete*). Since 2026-11-11 the lead answers each such question in one line against the
principles, and only one no principle settles goes further (step 0, *Who answers a design question*).
- **A design that met the code only in develop.** On 2026-11-11, in the develop step of milestone 47/G, the lead
answered over 100 questions, most of them a design line meeting the code: a line naming a form the code cannot
take, or assuming a part already built that was not. The prepare had read the design alone. It now reads the code
each design line lands in as well, and lists every place where the two disagree before any developer starts
(step 0, items 3 and 10; the operator, 2026-11-11).
- **A module proven only after it reached the machines.** The mail modules were delivered with unit tests
only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.
## The shape
One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**,
opened **once**, after the review, and **one check** (ADR 0548). It runs in five steps, and its time is bounded. A milestone has no phases
opened **once**, after the review, and **one check** (ADR 0548). It runs in five steps, with the design completed between the first two (step 0b), and its time is bounded. A milestone has no phases
(the operator, 2026-10-17): these are its steps, and a report names the step it is in:
| Step | What happens | Bound |
|---|---|---|
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes |
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit, the integration tests compiled; nothing is pushed | the milestone's issues, 30 minutes each |
| 0. Prepare | from the milestone part and the design's main file, read whole, then the code each design line lands in: where each line lands, what exists there today and what relies on it, as one build map per repository; every design question, the design's silences, listed; and every place where a design line and the code disagree, listed | 15 minutes, longer when what exists is large: code never starts on an incomplete answer |
| 0b. Design complete | every design question and every place where design and code disagree is answered: by the lead in one line against the principles, or, where no principle settles it, by the operator or `hq-design-writer` (step 0); each answer is written down (the lead's in the milestone's lead-answers file, the others into the milestone part, playbook 02) and its build map rows added | until the list is empty: develop never starts before |
| 1. Develop | one fixing agent per repository builds every design line of its build map locally, each commit citing the lines it builds, the whole unit suite before every commit, the integration tests compiled, and once per head it reports those of the packages it touched and their importers run against an embedded bus (below); nothing is pushed | the milestone's issues, 30 minutes each |
| 2. Review | one reviewer reads all of it, the local commits, judging the integration tests by reading them against the code; reports go both ways; at most 3 rounds | 15 minutes |
| 3. Check | push the reviewed heads once and open the pull requests together; the checker's one check runs the integration tests; no further review unless the check fails | one check run |
| 4. Deliver | merge; the source revision moves, each module is built once and wanted on its nodes, its canary first | until the view says the source revision `delivered` |
@@ -93,10 +112,58 @@ small fix ([playbook 08](08-small-fixes.md)) while the milestone waits on a chec
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
a time), or one that needs a decision or the operator first, waits, and the report says which and why. Each detailed 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)
## Step 0 — Prepare (mesh-preparer, larger model; the lead runs the process)
**Agents work from hq's definitions, never from a prompt written by hand.** Every agent the lead starts is one of
`.claude/agents/`: `mesh-preparer` for this step, `mesh-developer` for step 1, `mesh-code-reviewer` for step 2,
`mesh-watcher` for every wait, and the four reviewers by architectural domain and `research-reader` of playbook 01.
The lead writes only the handover: what the agent works on (issue, the design and its milestone part at an hq commit,
branch, worktrees, commits) and where it reports; a handover carries no choice about what is built, and one that
does is the lead answering a design question outside its lead-answers file (step 0b). A subagent runs in the session that started it, from hq: its way of working comes from its definition and hq's playbooks; what it builds comes from the milestone part and the milestone's lead-answers file and nothing else; one given a coding task works in a worktree under hq's `.work/` and also reads the conventions of the repository it changes, and where those contradict hq, hq wins and the agent says so. How it works, what it reads first and what it judges come from its definition, and so does its model: a
`mesh-developer` runs on the smaller model, and the lead starts one on the larger model only for work whose parts meet
at an interface several of the milestone's subtasks build at once (the design names it whole; nothing is designed in
develop), written with its reason in the milestone file; a lesson about how an agent
works goes into its definition by an hq pull request (the operator, 2026-11-03: "please don't write prompts by hand,
only the handover prompt"). How it is checked: the lead's report names each agent by its definition; an agent started
from a prompt written by hand is a breach the lead names at the next report.
**The design is the only input; the implementation decides nothing** (the operator, 2026-11-10: implementation
follows the decided design and makes no decisions, least of all architectural ones). The milestone's part of its
design (playbook [02](02-graduation.md), *The shape of a design*) and the design's main file are what the preparer,
the developers and the code reviewer read whole, and build or judge against. Decision records, the mesh's principles
and the knowledge base were read when that design was written and reviewed by architectural domain; they are not read
again to judge the design while it is built, because a builder who re-judges it builds a second design. The
principles answer only what the design leaves open (step 0b), never a line it states. Where each
interaction travels (the seat, verb, event, provision, state on the bus, manifest field) is a design line; one the
design does not name is a design question, never the preparer's finding with its own alternative. The mesh paths
are held where they are decided: the reviewers by architectural domain judge the milestone part before it is built
(playbook [02](02-graduation.md), *Answering a design question*).
- **The prepare's only output** is one build map per repository (item 4), the list of design questions and the list
of places where a design line and the code disagree (item 10). It writes no findings of its own, no recommendations into the build map and no rulings, and no file of
the milestone holds a statement of how something should be.
- **Who answers a design question, and where design and code disagree** (the operator, 2026-11-11, about 21:48
CET). The lead tests each design question and each entry of `Design meets code` (item 10) against the principles
(`00-META/principles.md`) and answers it in one line in the milestone's lead-answers file, naming the principle that
settles it, without a design batch or a review round. Only a question no principle settles goes further: to the
operator when its answer changes what the operator sees or how they work, removes data or needs the operator's
terminal; otherwise to `hq-design-writer`, who answers it as a design line from the design and its records, with
its reason, read by the four reviewers by architectural domain in one round, and the lead tells the operator each
such answer in one line. When unsure whether a question changes what the operator sees or how they work, it goes to
the operator. The preparer, a developer and a reviewer answer none, nor does a lead of another session.
- **One milestone has one lead.** A session that held part of the work hands its branches and heads to the lead and
stops; between sessions travel heads, commits, design questions and the hq commits that answer them, never an
instruction on how to build.
How it is checked: every subtask in a build map and every commit of the milestone cites the design lines it builds
("design NN M §n"), and the review finds code that cites none (step 2, item 2a); the milestone's files hold no
rulings (`grep -n -i 'ruling' .work/<slug>/*.md` finds none); an answer of the lead's that names no principle, a
question answered other than as *Who answers a design question* says, or one sent on that no principle left open, is
a breach the lead names at the next report; the milestone file's `Design meets code` names, for each repository a design line lands in, the `origin/main`
commit the prepare read, and a developer's question that is a design line meeting code the prepare read is a miss
of the prepare the lead names at the next report.
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
@@ -108,25 +175,70 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
`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
the knowledge base, or by asking the operator. **Read what is decided before designing** (`knowledge-base.ask`):
a question about delivery, checks or builds is most likely answered in design 47 and its decisions already (the operator,
2026-10-26: "that is simply our delivery plan and milestones, we've already researched it"); propose new research
only for what no record decides. A fixing agent that meets an undecided design question
stops, and its run is spent again.
4. **Write one context file per repository**, `.work/<slug>/<repo>.context.md`. It is what a fixing agent
reads instead of rediscovering the code, and it is reused by every agent and every round:
- 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), 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
hold, and the patterns to copy (file:line);
- what not to touch, and what lands beside it in other repositories;
3. **Read the milestone part and the design's main file whole, then the code each design line lands in**, before any
code is written (the operator, 2026-11-11: the prepare reads the code as well as the design). The design is the
only thing the preparer reads to know what is to be built; the code, read at `origin/main` in each repository a
line lands in, says whether the line can be built as written. For each design line the preparer reads the files
and functions it changes, the contracts, types and packages it uses, and the rules the code holds there (a guard,
a check, a test that refuses), and asks three things: can the code take the form the line names; is every part the
line assumes built (a verb, a field, a package, an event, a module) there on main; does the line break a rule the
code holds. Each no goes onto the list `Design meets code` (item 10). Every question the code or the live mesh
raises that the milestone part does not answer goes onto the list of design questions (item 10). Neither is ever
answered by the preparer. A fixing agent that meets either stops that part, and its run is spent again: the
prepare is where both are found.
3a. **Read what exists today, and write it down before any code** (the operator, 2026-11-02: "if i ask to implement
feature A, you'd better first check what exists today with regards to feature A"). Research, decisions, the survey
and the review each check the layer before them; this is the step that checks the running code. Each repository's
build map (item 4) opens with a section `What exists today` that answers three questions, in writing:
1. **What exists today for this feature**, read from the code (file:line) and from the live mesh through the mesh
MCP server's tools (the seats, the modules on each machine, their state on the bus, their tools' answers), never
from memory and never from a record alone. Where the code or the mesh contradicts the milestone part, the answer
says so and the contradiction is a design question (item 10).
2. **Everything the change touches, replaces or removes, and what relies on each today.** For a part that is removed
or moved (a module, a seat, a job of a module, a file, a value's form, a label others read), every effect it has
is listed, found mechanically from its code and not recalled: each bus write (bucket key, stream subject, event),
each file it writes, each credential or grant it issues or rotates, each plan or record it makes, each process or
container it starts, each verb, tool or request it answers, and each timer it runs. Then, for each effect, every
reader that relies on it (a search across every repository and the live bus, file:line or address). The search is
written down as run (the pattern and where), so the review can run it again.
3. **For each item of the second answer, what happens to it after the change** (kept as it is, moved to a named seat,
module or verb, replaced by a named part, or dropped and why nothing needs it), **and the test that proves it**: an
acceptance criterion (item 5) at its level, or, for a dropped effect, the search showing no reader is left.
The fate is the milestone part's: the answer cites the design line that gives it, and an effect or a reader no
design line gives a fate is a design question. For every new exported type, field, constant, package or module,
the answer names the design line that places it (the milestone part's, or the design section *Where a seat's
code goes*): where new code goes is always the design's, or a lead's answer naming its principle (step 0b), never
a choice nobody wrote down.
**A seat's contract is a design line's form too.** For each seat contract the milestone adds or changes, the
answer compares what the contract on main says (its fields, subjects, callers and kinds) with the design lines
that name that seat, field by field; every difference is a design question (item 10), answered before develop
(2026-11-10: a seat contract and an event's subject that differed from the design surfaced only in develop).
Code starts only when the three answers are complete for every repository: no fixing agent starts on a build map
without them, and an effect found later is the prepare step missed, written into the answer and its issue before the
fix. In the days up to 2026-11-02 the controller was taken apart without this answer: jobs nobody had named (making
a provision's credentials for a new consumer, planning a module's next build) surfaced only after the delivery; a
module was moved without reading how its pieces were wired; a failure label was changed without reading what relied
on it; and each check found one more fault the preparation should have known.
4. **Write one build map per repository**, `.work/<slug>/<repo>.map.md`. It is what a fixing agent reads beside the
milestone part instead of rediscovering the code, and it is reused by every agent and every round. It says where,
never how: each entry is a design line's citation and a place in code, and a sentence in it that says how
something should be is a design question put in the wrong place.
- first, the section `What exists today` with item 3a's three answers;
- one row per design line the repository builds: the line ("design NN M §n"), the files and functions it
changes, the subtask that builds it (about 30 minutes, one change, its label), its acceptance criteria (item 5)
and the test that proves each;
- the order of the subtasks: **a subtask that uses what another adds** (a seat's contract, a type, a verb, an
event's form, a package) **starts from that subtask's commit, never beside it**, so the contracts and packages
come first and their users after; and **one writer per file**: each file the milestone changes has one subtask's
agent, named here;
- the patterns to copy (file:line), what not to touch, and what lands beside it in other repositories;
- the local facts: the worktree, the branch, the check commands, a temporary directory for tests outside
the home (`TMPDIR`), and the unit tests to run (never a database server: tests that need one are
integration tests, which the pull request's one check runs).
When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with
file:line, and the lead writes the file from its answers.
the home (`TMPDIR`), the unit tests to run (never a database server: tests that need one are integration tests,
which the pull request's one check runs), and **the one placement of the siblings** every agent of the
milestone uses (its `go.work` `use` lines or `replace` directives), as the running checker places them; no agent
writes a placement of its own.
When the reading is large, a read-only agent on the smaller model answers the preparer's exact questions with
file:line, and the preparer writes the map from its answers.
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
@@ -135,12 +247,14 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
(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 merge moves, read from the indexer's candidates of its group, and to which machines); and for a
group, its merge order. The context file
group, its merge order. The build map
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
**Each issue carries its acceptance criteria, written on the larger model from the milestone part** (the operator,
2026-10-18: the larger model writes them, and they guide every test). Each criterion cites the design's
acceptance test or design line it comes from; a case the issue's done-when needs that no design line covers is a
design question, never a criterion of the writer's own. 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
@@ -157,7 +271,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
the SDK's users (they pin an SDK commit, and a pin to a commit no branch holds is a download that fails later,
issue 1148); every program that parses a manifest strictly (search `ParseManifest` across every repository, with
the SDK commit each pins), which must take an SDK that knows a new manifest word (2026-10-21: design 60's
catalogue milestone found mesh-build-go and the delivery's indexer reading manifests with an SDK from before the
catalog milestone found mesh-build-go and the delivery's indexer reading manifests with an SDK from before the
word); the SDK's list of a defined seat's verbs, which must name a verb a module adds (2026-10-21: node-files' two
new verbs were refused on the claim); the node-engine's decoder for a new key on the wire; the route by which a
repository's first module registers; and a part that needs something the bus or a manifest cannot yet give, which
@@ -182,14 +296,67 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
- <the one check that runs it: the group's pull requests, one check each, none before the review>
- what the check does not cover: <the parts of the group that meet only live>
```
A key or stream with no one who deletes it and no time to live is a leak and is settled before work starts.
Each line cites the design line that gives its deleter or its time to live. A key or stream with no one who deletes
it and no time to live, or with a lifetime no design line gives, is a design question, answered before work starts;
the lead writes no lifetime of its own.
9. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
10. **List the design questions** in the milestone file, under `Design questions`: each with the design line it
concerns (or the section where a line is missing), what the line leaves to its builder, where the prepare met it
(file:line or the tool's answer), and the preparer's recommendation, said as a recommendation. A design that
contradicts a decision in force, a reader or an effect with no fate, a lifetime, a caller, a bound, a field's
form, a tool for a new state: each is a question.
Then, under `Design meets code`, every place item 3 found where a design line and the code disagree, each with
the design line, the code (file:line at the `origin/main` commit read), which of the three it is (a form the code
cannot take, a part the line assumes built that is not, a rule in code the line would break), and the preparer's
recommendation, said as a recommendation. The two lists are the prepare's last output and step 0b's input.
## Step 0b — Design complete
Develop starts only when the milestone's design questions and the entries of `Design meets code` are all answered,
each in an hq commit (the operator, 2026-11-10: a question the design does not answer stops that part, and its
answer is written down before the code; 2026-11-11: the lead answers it against the principles).
1. **The lead answers each question against the principles**, in one line in the milestone's lead-answers file
(beside the milestone part, cited as "design NN M A-n"), naming the principle that settles it (step 0, *Who answers
a design question*). **Before an answer names a field, a key, a bucket, a subject or a function, the lead reads it
where it lives** (the code at the join head, or the seat definition) and checks the answer against what is there,
not against the question's words (2026-11-12: three answers that day named a structure from memory and needed a
correction once a developer read the code). One no principle settles the lead sends on: to the operator, one per ask, as playbook
[02](02-graduation.md), *Answering a design question*, says, each ask added to its wait list, when its answer
changes what the operator sees or how they work, removes data or needs the operator's terminal; every other one to
`hq-design-writer` in its handover. A question the writer finds is the operator's it hands back.
2. **An answer the lead does not give is written into the milestone part** by `hq-design-writer` on hq's branch of
the milestone, as a design line (the operator's citing the operator and the date, the writer's own giving its
reason) (or, outside every record's question, after its decision record), and the four reviewers by architectural
domain read the amended part in one round.
3. **The preparer adds the new lines to the build maps** (item 4) and the question's entry names the hq commit that
answers it.
4. **The design is complete** when every entry of both lists names its answering commit. Then step 1 starts, from the milestone
part at that commit.
**A question met later stops only its part.** A developer or the reviewer who meets a question the milestone part
does not answer stops the subtasks that need the answer, says the question to the lead with the design line and
file:line, and goes on with the subtasks that do not; the lead adds it to the list and answers it, or sends it on,
as item 1 says, and the stopped subtasks resume from the hq commit that answers it. Nobody builds around it, guesses it or builds both ways.
**A decision accepted while the milestone is built** that changes its part stops the subtasks of the lines it changes
until the part is amended (playbook [02](02-graduation.md)) and their build map rows are redone; code written before
the design was complete joins the milestone only where each of its commits builds a design line of the complete part,
and the review reads it as new code.
How it is checked: the milestone file's `Design questions` and `Design meets code` all name an answering hq commit
older than the first commit of step 1 (`git log` of each repository's branch against hq's); a commit with no design line, or one built on
an answer no hq commit holds, is a finding of the review (step 2, item 2a).
## 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
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
- **One agent per repository, kept for the whole feature.** It reads its build map whole, and of the design the lines
its rows cite and the main-file sections those lines name (never the whole milestone part at once: on 2026-11-11
four developers ran out of context reading design 47's 580 KB before their second subtask), and builds the subtasks one after another in the build map's order, committing each
locally with a message naming the issue and, in its body, the design lines it builds ("Keep the assignments on
the bus (issue 652)", then "Builds: design 47 G §12, §13"). It builds what each line says, and nothing a line does
not say: no type, field, state, verb, event, bound, caller or tool of its own. Where it thinks a design line wrong
by the mesh's principles, it still builds it as written and says so in its report, for the lead to bring to the
operator; it never builds otherwise. 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.
- **Unit tests only, each well under a second** (how-we-build §5, ADR 0391). **The whole unit suite runs before
every commit** (the operator, 2026-10-17), not only the tests of what changed: it takes under a second, and a
@@ -201,30 +368,54 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
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 is run by the pull request's one check. The integration tests (those that start a store, a bus
or a process) are compiled with `go vet -tags integration ./...` while developing and run by that check, nowhere
else.
or a process) are compiled with `go vet -tags integration ./...` while developing and run by that check. **Once per head it
reports, not per commit, a developer also runs the integration tests of every package the change touches, and of
every package of the same module that imports one, against an embedded bus** (`go test -tags integration -count=1`
on those packages). Where the importers are many, it runs instead the tests of them that exercise the change
(`-run '<names>'`) and names that choice in its report. It is never a whole module's or level's run, so it stays
within the operator's rule of 2026-10-18 (step 3). On 2026-11-10 a kept call broke an integration test of a fix group whose
developers had run only the untagged suite, and the review's first round found it, not the developers. Before the
group's push the tests that exercise the change have run so at the reviewed heads (step 3), nothing else. **In mesh-sdk the whole
untagged `./...` runs at every commit, whatever package the change is in**: a test there holds its copy of the seat
list and fails on a change elsewhere. How it is checked: the developer's report names those packages and their
results, and the reviewer refuses a head reported without them.
- **Nothing is pushed and no pull request is opened in this step, and there is no try** (ADR 0548; the operator,
2026-10-26: "every release should only run 1 check or 1 try"). When every issue is committed, the lead brings each
feature branch up to the current trunk (fetch by name, then merge) and the review reads the local commits. Never call
`mesh-controller.try` or `mesh-delivery-checker.checker_try`; one is run only when the operator asks for it. A reviewer
`mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`; one is run only when the operator asks for it. A reviewer
or a session that wants the code on the forge may push a branch that has no pull request: it runs nothing. Never land
on a red check: read the result, then land in a separate step.
- **The fixing agent's brief** (the lead gives it with the context file): name your issue in every step and
- **The fixing agent's brief** (the lead gives it with the build map): name your issue in every step and
message ("issue 805: landing in mesh-catalog"); reach the mesh only through the mesh MCP server, never ssh;
never push, open, merge or close a pull request; never call a try; never write to a store's database by hand, a schema change
is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
move another issue; **leave the integration tests to the check** (no store or bus started by hand): compile them
with `go vet -tags integration ./...`. A single integration test may be run locally against an embedded bus when
it is the fastest way to find a cause, said in the review report and the pull request. After a failed check it
is a numbered migration and the consolidated schema; Go only unless the milestone part says otherwise; never
move another issue; **leave a module's whole integration level to the check** (no store or bus started by hand):
compile it with `go vet -tags integration ./...`, and once per head you report run the touched packages' and their importers' integration
tests against an embedded bus, or by name where the importers are many (above). **Judge a manifest and run the catalog's checks against what is live
now:** the seat list the seat `mesh-catalog`'s verb `seat-definitions` answers, read in the same turn and read again
when you report a head, with the
definitions the change's heads alter laid over it for the change's case (ADR 0636 §4), and the catalog at its trunk,
fetched fresh, beside it; never a captured copy, a test fixture or a list kept from an earlier run (2026-11-10: a
stale seat list made eight manifests refused locally that the running validator accepts, and stale copies hid two
failures). **Start a subtask only with room to finish it**: a subtask is sized so that its reading and its work
fit well inside a third of an agent's context; an agent with less than that left commits what is whole (tests
green) and stops between subtasks, never inside one (2026-11-10: developers that stopped at their bound inside a
subtask left half-built work for the next to read again). Its last report names its heads and the next subtask's
label; a fresh agent continues from the build map and the commits' design lines, and no file of the milestone
carries a handover of findings or choices. A single integration test may be run locally against an embedded bus when
it is the fastest way to find a cause, said in the review report and the pull request; before the group's push the
tests that exercise the change are run this way, by name (step 3). After a failed check it
usually pays to run that repository's whole integration level locally (`go test -tags integration -count=1 ./...` in each
module), by the lead or by the fixing agent on the lead's word, before the one more push: a verdict names only the first failed
test (issue 1921), and on 2026-10-23 R4g's faults surfaced one per run until a local run listed them all in a minute (a
guideline, the operator). Report back in under 150 words: commits, tests added and that they failed first, the
local results, anything you stopped on.
- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead
settles it, updates the context file, and continues the agent.
- **Two repositories that depend on each other** keep their order in the context files: the reader before the
writer, the controller's new manifest word before the catalogue manifest that uses it.
- **An agent that finds the code differs from its build map stops that subtask and reports** (file:line). A fact the
map got wrong (a file moved, a function named otherwise) the preparer corrects in the map, and the agent goes on;
anything that needs a choice is a design question (step 0b, *A question met later*), never settled by the lead.
- **Two repositories that depend on each other** keep their order in the build maps: the reader before the
writer, the contract before its users, the new manifest word before the catalog manifest that uses it. Two agents
never build against an interface neither has committed: the user's subtask starts from the commit that adds it.
- **Several agents in one repository** (only for a large repository with independent areas): each works on a
local branch of its own from the feature branch's head and lands by fast-forward under a lock
(`flock <feature worktree>.lock git -C <feature worktree> merge --ff-only <its branch>`), after its unit
@@ -241,15 +432,22 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
repository is judged on it until it moves, so the operator's Approve will merge nothing. Say it then, not at the
merge (issue 1726: on 2026-10-24 the gate dropped two merges it heard before it held its seat, and a
reviewed, approved pull request in each of the two repositories was refused at its Approve).
2. **One reviewer reads every repository's local commits** together, against the context files and the
decisions. **It is one agent, kept for the whole release** like the fixing agents: the lead continues it by
2. **One reviewer reads every repository's local commits** together, against the milestone part, the design's
main file and the build maps. **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
reads the milestone part, the issues, the build maps 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").
2a. **Every change is judged against its design line.** Each commit cites the design lines it builds; the reviewer
reads each line against the diff that cites it: the code does what the line says, all of it, and nothing it does
not say. Code no design line asks for (a new exported type, field, state, verb, event, bound, caller or tool, a
fate for what exists) is a finding: a choice the design does not make, taken out or held until the operator's
answer is in the design (step 0b). A design line in a build map with no commit, or a commit citing an answer no hq
commit holds, is a finding. The reviewer does not re-judge the design: a line it believes wrong by a decision or a
principle is a note to the lead for the operator, never a change asked of the code.
3. **The report goes to the same fixing agents** that wrote the code, continued by their ids, so they fix
with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
4. **Every new integration test is read against the code it drives.** Fixing agents only compile it, and no run precedes the review (step 1), so the reviewer judges it by reading; the check that follows shows whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. A review that finds a fault only by reading is the cheaper run. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
4. **Every new integration test is read against the code it drives.** Fixing agents run only the touched packages' integration tests and their importers' (step 1), and no whole module's run precedes the review, so the reviewer judges each by reading too: a passing local run and the check that follows show whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. A review that finds a fault only by reading is the cheaper run. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
5. **The reviewer reads the answers and the new commits**, and approves or reports again (step 7 says what a round
may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
6. **The reviewer reads every acceptance criterion against its test**: each criterion has a test at its level
@@ -263,10 +461,19 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
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).
**A test fixture writes state in the form its real writer writes it** (for example a resolved config value with its
source, as the controller stores it); a fixture that agrees with the mistake proves the mistake. How it is checked:
the review asks it of every new fixture, and says which writer it compared with.
6a. **The reviewer reads the milestone file's three answers** (step 0, item 8) against the change: it lists every bucket
key and stream the diff creates or writes (search the diff for the bucket and stream names) and finds each in the
lifetime answer with a deleter and event, or a time to live; it reads the switch-over answer and checks it was done as
written or that the operator's choice is recorded; it reads the integration-level answer against the check the pull requests will ask. A missing answer, or a key or stream the diff creates that the answer omits, is a change asked.
6b. **The reviewer holds the change to each build map's `What exists today`** (step 0, item 3a). It refuses to
review a repository whose build map lacks the section or leaves one of its three answers open: that is the
prepare step undone, sent back to the lead, and not a round. For every part the diff removes, moves or changes the
form of, it runs the recorded search again on the reviewed tree and finds each effect and each reader in the second
answer; for each, it reads in the diff that the third answer's fate happened and that its test exists. An effect or a
reader the search finds and the answer omits, or a fate the diff does not carry out, makes the change wrong.
7. **A finding outside the issues' done-when and their acceptance criteria is never a change asked, in any round.** More hardening, a
further case, a nicer form: the reviewer opens it with `issue-tracker.open` and names the number in its
report, and the round does not wait for it. What a round asks is only what makes the change wrong against
@@ -285,6 +492,18 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
its shape, fixes every hit in one change and lists each file and line in the report. How it is checked: a
report to the second round naming a kind and one place only, followed by a third round on the same kind, is
this rule skipped.
10. **The reviewer asks two questions of every change and every design, and answers both in its report** (the
operator, 2026-10-28: "design everything NATS-native"; "repositories are fully mesh agnostic"). The first:
*where does each piece of state live, and how does each seam (an integration, data, a part talking to
another) travel?* Anything that is not bus state, a stream or a seat's verb on the bus is a finding. The second:
*does a repository hold anything about this mesh's setup, such as node names, node-specific placement or
settings?* That is a finding: repositories are mesh-agnostic. On 2026-10-28 a placement design kept placement in
repository files with node names, because the accepted record it followed said so and nobody checked it against
these two principles. A finding in code the design does not place makes the change wrong (item 2a); where the
design line itself puts the state or the seam there, the finding is the design's: the reviewer names the line and
the record behind it, the lead brings it to the operator as a design question, and the code waits for the answer
in the design. How it is checked: the review report has a line for each question with its answer, and the lead reads both before the
push; a report without them is not an approval.
## Step 3 — Check (no review)
@@ -295,11 +514,24 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
processing power"). The integration tests run once, in the checker's merge check on the merge with the trunk's head, at the unit
and the integration level, through the build module of the repository's language and by that language's convention
(ADR 0445, ADR 0529); the check waits on the language's check stream until a build node has a free slot
(ADR 0525 §1). **No try:** never call `mesh-controller.try` or `mesh-delivery-checker.checker_try`, except when the
operator asks for one. While developing, no `go test -tags integration` run of a whole level, and no store or bus started
(ADR 0525 §1). **No try:** never call `mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`, except when the
operator asks for one. While developing, no `go test -tags integration` run of a whole module or level, and no store or bus started
by hand on the machine (the operator, 2026-10-18).
**A group is one check.** Before the push, every member pins the group's own mesh-sdk head exactly (the checker refuses an
older commit of the same branch), then all members are pushed and opened together, so the group's check runs once. A head the review replaces
**Before a group's single push, the integration tests that exercise the change have run locally against an
embedded bus**, at the reviewed heads: the touched packages' and their importers' tests that each developer ran for
the head it reported (step 1), or those picked by name (`go test -tags integration -count=1 -run '<names>'`) where
the importers are many; never a whole module's or level's integration run, and not only a compile with `go vet`; a
change in how a node composes or applies always has such a test (2026-10-28: a compiled-only group failed in the one
check on what a local run shows). How it is checked: the pull request's description names those tests and their
results.
**While a group is being checked, nothing else is merged or pushed into a repository that is one of its members:**
a moved main asks the group's check again. How it is checked: the lead's wait list names the group's members, and
`mesh-delivery.show` shows one check per head.
**A group is one check.** Before the push, every member pins the group's own package heads (mesh-sdk, and any other
package a member publishes) so it builds with `GOWORK=off`; the check itself builds against the group's heads whatever a
member's go.mod names (ADR 0573 §2, §4), so an older pin is not refused there, but the trunk builds after the merge read
the go.mod as written. Two members that import each other are pinned in order, each to the other's commit before its own
pin. Then all members are pushed and opened together, so the group's check runs once. A head the review replaces
counts for nothing against a running check; say so, and ask no other until the new head is reviewed.
**Before a session trusts an agent's "pushed", it reads the forge branch's head** (`git ls-remote origin refs/heads/<branch>`) and compares it with the commit reviewed; an agent may commit on a detached HEAD and report a push that never happened (2026-10-26: the first check of a reviewed fix failed "not our ref"). The pull request is opened only on that read head. Pull requests in two or
more repositories that share the branch name are one delivery group (ADR 0239 §2).
@@ -326,21 +558,33 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
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's check does not find it, since a check reads the catalogue's main
(also in a delivery group that holds the catalogue's pull request); the lead runs mesh-controller's unit
suite with `MESH_CHECK_BESIDE` naming a directory that holds the catalogue's branch, as mesh-controller's
**A catalog pull request that removes a module, or a subject a module names, is also judged by the
controller's unit tests against the catalog's branch**, because those tests read the catalog and no check
of the catalog 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 catalog's subjects until issue 1406 removed the name
mesh-issues still listened to. A controller's check does not find it, since a check reads the catalog's main
(also in a delivery group that holds the catalog's pull request); the lead runs mesh-controller's unit
suite with `MESH_CHECK_BESIDE` naming a directory that holds the catalog's branch, as mesh-controller's
`internal/beside` says.
2. **The checker's merge check runs the integration tests once, as the gate** (its one status, `mesh/check`). No
second review: step 2 reviewed this tree.
**Before the push, every member's suites run as the running checker runs them**: each sibling repository placed
beside it by main's beside rules, the node-engine at the commit the machines run, and the running manifest validator
over every manifest the change touches, given the live seat list and the catalog's fresh trunk (step 1's brief). On 2026-11-02 release sdk1 failed four checks one fault at a time (test
globals left set, a sibling placed otherwise, the checker's manifest archive, a composition step the old checker could not
build), each of which a run as the checker places things would have shown before the first push. How it is
checked: the pull request's description names the placement, the node-engine commit and the manifest validator run used
locally; a check that fails on a placement the local run did not use is named by the lead at the next report.
2. **The checker's merge check runs the integration tests once, as the gate** (its one status, `mesh/check`).
**A merge with no verdict is unchecked, never passed.** Until the pipeline runner replaces the checker, the
checker drops a check still running when its pull request merges (met twice on 2026-11-10): the lead reads the
check's verdict (`mesh-delivery.show`) after every merge, and a merged change whose check has none is reported to
the operator as merged unchecked, with what its local runs covered, never as passed. How it is checked: a report
that says a merged change passed is read against its verdict in `mesh-delivery.show`. 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 one more push of ADR 0548). The check is **read, never re-asked to see if it passes**: a
check asked again without a new, reviewed head spends the build capacity for nothing. **The failure is read from the check's whole log**, never
reasoned from its summary: `mesh-delivery.show` names the check's build id, and `mesh-controller.builds` with
`log` set to that id prints everything the builder said. A check's summary picks only some failing lines;
reasoned from its summary: `mesh-delivery-checker.checker_pull` names every failure the verdict holds, one line each, and the full report
of the check. A check's summary picks only some failing lines;
on 2026-10-17 it showed a store test's `FAIL` without its cause, and a fix was first guessed from the code.
**A failure the check hides or loses** (a verdict naming only the first failed test, a log not kept) is a fault of the
checking tool: it is fixed there, the same turn (instruction fix-a-bug-met-twice), not asked around.
@@ -352,6 +596,13 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
which tests run at which level is the build tag's (ADR 0445 §2), and hq's checks are Go tests like any repository's
(ADR 0527). The prepare step looks for this: a change to what the checker's build module selects (its recipe, a build
tag convention) is cut ahead of the milestone.
**The same holds for every part that judges a change**: the checker, the indexer and its manifest validator, the grants part and
the build modules. A release that changes one of them is judged by the version that runs, so whatever the running
version cannot judge is delivered first as a change of its own, or left out and added back once the new part runs.
On 2026-11-02 release sdk1 delivered the build module's relative replaces first, and took the checker's new
composition call out of the release, adding it back in the next pull request once the new node-engines answered it. How it is checked: the prepare step's
milestone file lists every judging part the release changes and how each is delivered first or left out; the review
refuses a group that holds one without that line.
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
shows the milestone's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
@@ -379,16 +630,31 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
moved to include it): one Approve, one delivery. The old Approve ask closes itself on the new head and the new
head is asked once its checks pass. How it is checked: a follow-up that sits in a second pull request while the
first still waits shows in `gitea.gitea_list_pull_requests` as two open pull requests of one lead.
6. **A member pins mesh-sdk only at a commit that is a pull request's head**, merged or open (2026-10-28: the check
refused a pin to a merge commit on main as "on no pull request", though its tree equalled the head's). How it is
checked: the check's pin step; a refusal "on no pull request" is this rule skipped.
7. **Two groups that pin the same module's mesh-sdk conflict in `go.mod` and `go.sum`.** The group that merges
second merges main in and re-pins once, to a pull request head that holds both changes, after the first group
is merged and not before (a re-pin before makes the first group conflict). How it is checked: the check's merge
step; a group that conflicts after the first one merged is this rule skipped.
8. **A group that adds a seat, a verb or a manifest field in mesh-sdk and uses it in a module's manifest in the
same group cannot pass its check** until the running assigner pins a mesh-sdk that defines it, because the
check composes with the running assigner's mesh-sdk (issue 1585). The assigner's pin ships first, as its own
release. How it is checked: the check's composition step; a composition refusing the new word in such a group
is this rule skipped.
9. **A new head on an open pull request needs its `Reviewed-by` line at that head before the Approve is asked,
even for a pin-only commit.** How it is checked: the Approve's ask names the review record;
an ask whose line names another commit is this rule skipped.
**One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads
**One exception: a catalog change that uses a new manifest word.** The catalog's merge check reads
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. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way.
then `mesh-delivery.recheck` the catalog'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.
then three releases, the node-engine's, the controller's, the catalog's. **Every release that must merge before
the catalog's is on a branch name of its own** (`feat/<slug>-node-engine`, `feat/<slug>-controller`): on the
feature's name it joins the catalog'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.
@@ -406,7 +672,10 @@ the source revision equals its fingerprint and every have its want). After each
the view's wants page or its tools: `assigner_wants` and `assigner_want` (a want not yet at the newest fingerprint
names, in `newest` and `rule`, the rule that holds it back, and its words say why), and `node_haves` (the node
agent's haves: fingerprint, composition digest, phase and health). The delivery is done when the view says
`delivered` for its source revision; then the lead resolves the feature's issues with `fixed-by` naming its pull
`delivered` for its source revision; **"on the machines" from the delivery tools is not yet "delivered"**: the lead
also reads what the nodes run (the view's haves per node) and the indexer's record of the module (its newest
fingerprint) before saying a change is delivered. How it is checked: the report names each node's build.
Then the lead resolves the feature's issues with `fixed-by` naming its pull
requests, and removes what is left: no `feat/<slug>` branch and no `.work/<slug>` worktree survive.
**What a merge moves is read before the operator is told.** A change to mesh-sdk rebuilds every module that takes
@@ -466,8 +735,9 @@ Nothing here is a work-around for a delivery that can run normally: while the bu
| Who | Model | Why |
|---|---|---|
| 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 |
| The lead (who works on what, asking the operator the design questions, following and reporting; it answers no design question) | larger | judgement of the process |
| The preparer (the build maps and the design questions, from the milestone part alone) | larger | reading the design and the code together |
| Read-only questions about the code for the build maps | smaller | reading, answered with file:line |
| Fixing agents (step 1, and the fixing in steps 2 and 3) | smaller | building to a written context |
| 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 |
@@ -511,61 +781,51 @@ Approve, a Retry or a Release waits for them, it reads `operator-channel.asks` a
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.
**Every ten minutes (the operator, 2026-10-17), every two minutes while a delivery moves (the view says modules `moving`), and at once when something lands, stops or
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 cadence is the instruction section "report-cadence"** (the operator, 2026-10-28; it replaces the full layout every
five minutes of "how-work-is-done"). 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:
- **Every five minutes** (ten until 2026-10-26, five since), every two minutes while a delivery moves (the view says
modules `moving`), and at once when something lands, stops or needs the operator, the lead sends the **short
report**: the tree exactly as `knowledge-base.progress` with `of: "design <nn>"` and `detail: minimal` answers it,
unedited, with the verb's read-at time, then at most three lines: what changed, what needs the operator, what is next.
Once the verb takes `since`, the short report asks `since: 5m`.
- **About every thirty minutes**, and whenever something stops, the lead sends the **detailed report**: the same verb
with `detail: detailed` (or `since: 60m`), then the full layout below.
- **Every report ends by scheduling the next** (a timer or a watcher set ahead), so that agents' incoming reports
never replace it.
- **An answer to the operator between reports is short and has no tree.**
**The tree is the verb's answer and is never drawn or corrected by hand.** `knowledge-base.progress` answers the
design's milestones, issues, pull requests and held groups in one call, each with when it last moved (issue 1947).
What it cannot read for certain from the design's milestone table it says in `warnings`; the lead fixes the table, it
does not correct the tree by hand.
**The full layout** (the detailed report) is the verb's detailed tree followed by these lines:
```
Feature <slug> · milestone <n> of <m> · step <name>, <minutes used> of <bound> min
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting
Design <nn> · <its title>
├── ✓ milestone 1 <its outcome, a few words> delivered 12:58
├── ▶ milestone 2 <its outcome, a few words>
│ ├── mesh-controller (agent: fixing, 2 of 3 landed)
│ │ ├── ✓ 652 assignments on the bus landed 14:02
│ │ ├── ▶ 736 placement words leave the layers fixing since 13:51, 23 of 30 min
│ │ │ └── ⏸ 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"
Needs you: <one line per question, with my recommendation> — or "nothing"
Allowance: week <n> % (<+d> since the last report), session <n> %
Worth it: <what in the last stretch served the goal, and what did not>
Next: <the next actions up to the delivery, in order>
```
**The tree is read, not assembled.** `knowledge-base.progress` with `of: "design <nn>"` answers the tree's
milestones, issues, pull requests and held groups in one call, each with when it last moved: `detail: minimal` for the
five-minute report, `detailed` about every thirty minutes and whenever something stops (issue 1947). What it cannot
read for certain from the design's milestone table it says in `warnings`; the lead fixes the table, it does not
correct the tree by hand. How it is checked: a report's tree that disagrees with the verb's answer of the same minute
is a tree assembled by hand.
How it is checked: the operator sees a short report at least every five minutes and a detailed one at least every
thirty; a report's tree that differs from the verb's answer of the same minute is a tree assembled by hand, a report
without the verb's read-at time is the same, and a report that does not end by scheduling the next is a breach the
lead reports.
**The short layout** (on a channel such as Telegram, or when nothing changed but time):
```
<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. In the session every report is the full layout, the whole
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.
On a channel such as Telegram the same short report is sent, the tree as a code block.
## The context of the lead's own session
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
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.
feature's step, its branches and heads, the build maps' paths, the design questions still open, the agents' ids,
the wait list and the next action; then the prompt the operator pastes. The milestone part, the build maps and the
commits' design lines make the feature resumable after a clear.
## The design's state is written back
@@ -600,7 +860,19 @@ 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
request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in
step 1, is the failure this playbook prevents.
- A context file exists per touched repository under `.work/<slug>/` while the feature runs.
- A build map exists per touched repository under `.work/<slug>/` while the feature runs, and every row cites a
design line of the milestone part.
- Every commit of the milestone names in its body the design lines it builds (`git log --format=%B | grep 'Builds:'`),
and the milestone file's `Design questions` each name the hq commit that answers it, older than step 1's first
commit.
- The milestone's part is within 60 KB and the design's main file within 80 KB (`wc -c`); a milestone developed from
a larger part is playbook 04's step 4a skipped.
- Every build map opens with `What exists today` holding step 0 item 3a's three answers, each search written as run
(`grep -n -A3 'What exists today' .work/<slug>/*.map.md`), and its time (the file's commit or the lead's note) is
older than the repository's first commit of the feature; the review report says it ran the searches again (step 2,
item 6b). A fixing agent started on a file without the section, or an effect of a removed or moved part met only
after the delivery, is the prepare step skipped, named in the next detailed report and written into the playbook's
lessons here.
- 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
@@ -645,7 +917,7 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
- **One release, one check** (ADR 0548): `gitea.gitea_get_pull_request` shows each pull request created after its
review record's time, and `mesh-delivery.show` shows one check per head and, for a group, one head per member; a pull
request that existed before its review approved it, a second head with no failed check before it, or a check of a
head asked more than once, is this rule skipped. A call of `mesh-controller.try` or `checker_try` that the operator
head asked more than once, is this rule skipped. A call of `mesh-controller.try`, `mesh-delivery-checker.try` or `checker_try` that the operator
did not ask for shows in the checker's asks and is the same breach.
- A milestone that adds or changes a module whose pull request has no integration test that starts the module
whole is the failure of the check step's fourth item.
+47 -14
View File
@@ -33,12 +33,31 @@ processing power"). Everything before the push is local and fast, and no try is
## Steps
1. **Read the issue and the code it names** (the session's own model, or one read-only agent on the smaller
model answering exact questions with file:line). If a design question is open, it is settled before any
code is written: on the issue, through the knowledge base (`knowledge-base.ask` first: what is decided is read before anything is designed), or by the operator. A fix that turns out to
need a second repository, or more than about an hour, stops here and becomes a feature; one that needs a
decision stays a small fix. **The session's own model (the larger) then writes the issue's acceptance
model answering exact questions with file:line). **Read what exists in the fix's area today, briefly, before any
code** (playbook [07](07-feature-branches.md) step 0 item 3a, at a small fix's size): what the code and the live mesh
(through the mesh MCP server's tools) do there now, and for each value, label, file or job the fix changes or removes,
every reader that relies on it, found by a search across the repositories (written down as run) and not from memory,
with what happens to each after the fix. It is a few lines in the issue's note, under `What exists today`, and the
review runs the search again; a reader it finds that the note omits makes the fix wrong. **A fix builds what a
design says, and decides nothing** (the operator, 2026-11-10; playbook [07](07-feature-branches.md), step 0): its
input is the issue and the design that covers the area (the design whose `code:` names the repository, or the
issue's design link), read in the parts the fix touches and in the design section that places code, *Where a
seat's code goes*. The note names, for everything the fix adds or changes (an exported type, field, constant,
package or module, a state, a verb, an event, a bound, a caller), the design line that gives it. A fix that only
makes the code do what its issue and its design already say needs nothing more. Anything no design line gives is
a **design question**, answered as playbook [07](07-feature-branches.md), step 0, *Who answers a design question*,
says, before any code builds it: the session's lead answers it in one line against the principles
(`00-META/principles.md`), written in the issue's note, naming the principle that settles it; one no principle
settles goes to the operator, one question with its recommendation, when its answer changes what the operator sees
or how they work, removes data or needs their terminal, and otherwise to `hq-design-writer` as a design line
(playbook [02](02-graduation.md), *Answering a design question*), read by the four reviewers by architectural
domain in one round and told to the operator in one line. A handover to the developer quotes the design lines and never
decides a placement itself (2026-11-04: a class placed in mesh-sdk by a lead's handover, against that section,
passed review and its check). A fix that turns out to need a second repository, or more than about an hour,
stops here and becomes a feature; one that needs a design question answered 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
naming the level of the test that proves it and the design line or issue done-when it comes from, as an
`issue-tracker.note` headed `Acceptance criteria`. A
playbook or design edit has none: its check is `go test ./...` at hq's root, which runs the four checks (ADR 0527).
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`, never in
@@ -51,19 +70,27 @@ processing power"). Everything before the push is local and fast, and no try is
caught only by the review); read `git show --stat` before pushing.
The tests are written from the acceptance criteria, at least one per criterion at its level, each citing the
criterion it proves by its label (`<issue>/AC<n>`, playbook 07 step 0 item 5). Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
`go test ./...` at the root). The integration tests are compiled (`go vet -tags integration ./...`) and not run:
nothing is pushed, no pull request is opened and no try (`mesh-controller.try`, `mesh-delivery-checker.checker_try`) is called, unless the operator asks for
one. A single integration test may be run locally against an embedded bus when it is the fastest way to find a cause.
`go test ./...` at the root). The integration tests are compiled whole (`go vet -tags integration ./...`), and once
per head reported those of the packages the fix touches and of their importers in the module run against an
embedded bus, or the tests of them that exercise the fix by name where the importers are many, named in the report;
never a whole module's or level's run (playbook 07 step 1). Nothing is pushed, no pull request is opened and no try (`mesh-controller.try`, `mesh-delivery-checker.try`, `mesh-delivery-checker.checker_try`) is called, unless the operator asks for
one. A single integration test may be run locally against an embedded bus when it is the fastest way to find a cause;
before the push, the integration tests that exercise the change have run that way at the reviewed head
(playbook 07 step 3), and the pull request names them and their results. A test fixture writes state in the form its real
writer writes it; the review asks it.
4. **One review of the local commit** (a reviewer that did not write it, larger
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; it reads each acceptance criterion
against its test, as playbook 07 step 2 item 6; a finding outside the issue's done-when and its acceptance
criteria is opened as an issue of its own, never asked as a change; it judges the integration tests by reading them against the code, as playbook 07 step 2 item 4 says). The reviewer's findings go back to the same agent that wrote the fix, continued by
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; it judges each change against the design line its note names, as playbook 07 step 2 item 2a, and code no
design line gives is a finding; it reads each acceptance criterion
against its test, as playbook 07 step 2 item 6; it runs the `What exists today` search again, as playbook 07 step 2 item 6b; 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; it judges the integration tests by reading them against the code, as playbook 07 step 2 item 4 says; it answers playbook 07 step 2 item 10's two questions, on state and seams and on mesh-agnostic repositories, in its report). The reviewer's findings go back to the same agent that wrote the fix, continued by
its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the
operator's Approve of its merge is its review, and the review record says so.
5. **Push the reviewed head once and open the pull request, only after the review approves.** The trunk is what runs and nothing holds merged work back
(ADR 0401), so a fix waits for no other change's delivery. No pull request, draft or not, exists before the review
approves the head, and no fix round is pushed to an open pull request: every push to an open pull request asks a new
check. The checker's merge check runs the unit and the integration level once, as the gate (playbook [07](07-feature-branches.md) step 3); the pushed head is read from the forge first, as playbook 07 step 3 item 1 says.
check. The checker's merge check runs the unit and the integration level once, as the gate (playbook [07](07-feature-branches.md) step 3); the pushed head is read from the forge first, as playbook 07 step 3 item 1 says. A merge whose check has no verdict
(the checker drops a check still running at the merge) is reported to the operator as merged unchecked, never as
passed (playbook 07 step 3 item 2).
A failed check is read from its whole log (never re-asked to see if it passes), then the repository's whole integration level
is run locally (`go test -tags integration -count=1 ./...` in each module) so every failure shows at once, where a verdict
names only the first (issue 1921); the failures go back to the same agent, fixed in one change, the reviewer reads only the change
@@ -87,7 +114,8 @@ processing power"). Everything before the push is local and fast, and no try is
7. **Follow its delivery to done**: a watcher waits on the status view's events (`mesh-delivery-view.moving`,
`.settled`, `.stuck` and `.delivered`) and reads the view's `assigner_wants` and `node_haves` after each wake, as
playbook [07](07-feature-branches.md) step 4 says; the delivery is done when the view says `delivered` for its
source revision. Then prove the fix on the mesh (the symptom gone, measured), and resolve the issue with
source revision, and the haves per node and the indexer's record of the module name the build (playbook 07 step 4;
"on the machines" alone is not "delivered"). Then prove the fix on the mesh (the symptom gone, measured), and resolve the issue with
`located-in` and `fixed-by` the same hour.
A fix that stops before its delivery moves its issue back to `open` with a note saying how far it got
@@ -137,11 +165,16 @@ reading itself and uses one subagent only for the review.
- A pull request merged by hand in a temporary mode names the verdict of its one check in its description
on the forge (`gitea.gitea_get_pull_request`); one without is the step skipped.
- One fix, one check (ADR 0548): a pull request that existed before its review approved the head, or a head whose check was
asked more than once with no new reviewed head, is steps 4 and 5 skipped; so is a `mesh-controller.try` the operator did not ask for.
asked more than once with no new reviewed head, is steps 4 and 5 skipped; so is a `mesh-controller.try` or `mesh-delivery-checker.try` the operator did not ask for.
- Its issue moves to `resolved` within the hour the view says its source revision `delivered`.
- A pull request with no review record at its head (the Approve's ask says so first), a fix to a module's
running pushed with no integration test of the module whole, or a review report
asking a change outside the issue's done-when and acceptance criteria, is the failure of steps 4 to 6.
- A fixed code issue's note names the design line for each thing the fix adds, and its commit names the same lines
(`Builds:`); a fix adding what no design line gives, with no answered design question behind it, is step 1 skipped.
- A fixed code issue shows a note with `What exists today` (`issue-tracker.get`) older than its first commit, its
search written as run; one without is step 1 skipped, and a reader of a changed value met only after the delivery
is that note left short.
- 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.
+39 -24
View File
@@ -198,7 +198,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<!-- 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="190" y="34" text-anchor="middle" class="mute">15 min · then design complete</text>
<text x="410" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">1 · Develop</text>
<text x="410" y="34" text-anchor="middle" class="mute">30 min per issue · local only</text>
<text x="630" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">2 · Review</text>
@@ -221,8 +221,8 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
</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>
<text x="190" y="78" text-anchor="middle" class="t">Build map · questions</text>
<text x="190" y="96" text-anchor="middle" class="mute">from the milestone part alone</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</text>
<text x="410" y="96" text-anchor="middle" class="mute">each branch, local</text>
@@ -234,15 +234,15 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<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>
<text x="410" y="150" text-anchor="middle" class="t">Build each design line, commit</text>
<text x="410" y="168" text-anchor="middle" class="mute">Builds: line · unit suite each 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">reads tests · every criterion</text>
<text x="630" y="240" text-anchor="middle" class="mute">each change against its line</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">no try</text>
@@ -254,11 +254,17 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<text x="1000" y="294" text-anchor="middle" class="t">Merge · wants</text>
<text x="1000" y="312" text-anchor="middle" class="mute">backups first, canary first</text>
<!-- operator -->
<rect x="110" y="344" width="160" height="52" rx="8" fill="var(--card)" stroke="var(--op)" stroke-width="2.5"/>
<text x="190" y="366" text-anchor="middle" class="t">Answers each question</text>
<text x="190" y="384" text-anchor="middle" class="mute">written into the design</text>
<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 dash" d="M180,108 V342" marker-end="url(#ar2)"/>
<path class="edge" d="M270,370 C290,370 280,170 298,166" marker-end="url(#ar2)"/>
<text x="186" y="232" class="mute">questions</text>
<text x="276" y="262" class="mute">answered</text>
<path class="edge" d="M410,128 V110" marker-end="url(#ar2)"/>
<path class="edge" d="M630,200 V182" marker-end="url(#ar2)"/>
<path class="edge dash" d="M610,182 V198" marker-end="url(#ar2)"/>
@@ -275,10 +281,14 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<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><span class="tag new">new</span>The milestone part of the design is the only input: the prepare maps each design line onto code (a build map per repository, each row citing its line) and lists every design question, what the design does not answer. It writes no findings or rulings of its own.</li>
<li><span class="tag new">new</span>The prepare reads the design whole, then the code each design line lands in, and lists under Design meets code every place where they disagree: a form the code cannot take, a part the line assumes built that is not, a rule in code the line would break. The lead answers each in one line against the principles before develop starts.</li>
<li><span class="tag new">new</span>Design complete: the lead answers each question in one line against the principles, naming the principle; only one no principle settles goes to the operator (when it changes what they see or how they work, removes data or needs their terminal) or else to the design writer as a design line, read by the reviewers by architectural domain. Develop starts only when every answer is in an hq commit.</li>
<li><span class="tag new">new</span>What exists today, read from the code and the live mesh, never from memory: every effect of a part removed or moved (bus writes, files, credentials, plans, processes, calls answered), every reader relying on each, what happens to each after the change and its test. It opens each build map; no code before it is complete, and the review refuses work without it.</li>
<li><span class="tag new">new</span>Subtasks ordered so a user starts from the commit that adds its contract; one writer per file; one placement of the siblings for every agent.</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>Acceptance criteria: given / when / then, each naming its test level and the design line it comes from.</li>
<li>Every part the change reaches is an issue of the milestone; their order is the assigner's rules', never a cut.</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>
@@ -287,19 +297,22 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<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>Build each design line as written and nothing it does not say; every commit names its lines (<code>Builds: design NN M §n</code>).</li>
<li><span class="tag new">new</span>A question the design does not answer stops that subtask and goes to the lead for the operator; never guessed or built around.</li>
<li>One commit per subtask, naming its issue; 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><span class="tag new">new</span>Integration tests compiled whole; once per head reported, those of the touched packages and their importers run against an embedded bus (by name where the importers are many); never a whole module's run.</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>
<li>Code that differs from the build map: stop that subtask and report, file:line.</li>
<li><span class="tag new">new</span>Start a subtask only with room to finish it; stop between subtasks, never inside one.</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) are compiled there and never run: the checker's one merge check, asked by the one push after the review, runs them as the gate. There is no try (ADR 0548).</p>
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) are compiled there whole, and once per head reported only those of the touched packages and their importers run there, against an embedded bus, never a whole module's or level's: the checker's one merge check, asked by the one push after the review, runs them as the gate. There is no try (ADR 0548).</p>
<div class="box scroll">
<svg viewBox="0 0 1080 170" role="img" aria-label="Test levels over a feature's timeline">
<defs><marker id="ar3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
@@ -311,10 +324,10 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<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>
<text x="10" y="104" class="t">Integration</text><text x="10" y="120" class="mute">touched packages only</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>
<text x="290" y="108" text-anchor="middle" class="mute">compiled whole; touched packages run</text>
<rect x="770" y="90" width="120" height="28" rx="6" fill="none" stroke="var(--mesh)" stroke-width="2" stroke-dasharray="5 4"/><text x="830" y="109" text-anchor="middle" class="mute">push once</text>
<rect x="920" y="90" width="120" height="28" rx="6" fill="var(--mesh)"/><text x="980" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">merge check</text>
</g>
@@ -353,8 +366,10 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<h3 style="margin-top:12px">What every round reads</h3>
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
<li>Every criterion against its test: a test at its level that would fail without the change.</li>
<li>Every new integration test traced through the code by reading it: no run exists before the check.</li>
<li>Every new integration test traced through the code by reading it: a local run of the touched packages shows only that it passed.</li>
<li>That each integration test enters by the path the live trigger takes.</li>
<li>Where each piece of state lives and how each seam travels: anything but bus state, a stream or a seat's verb is a finding, even if a record put it there.</li>
<li>That no repository holds this mesh's setup (node names, placement, settings): repositories are mesh-agnostic.</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. ADR 0548 and playbook 07 step 2 item 4 change the last: bus tests are judged by reading them against the code, and the one check runs them after the review.</div>
</div>
@@ -413,15 +428,15 @@ 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."],
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.","Write the numbered requirements from the answers, each with its check, before any option.","Reserve a number (kind research), never pick one by hand.","Propose the effort: Overview, Requirements, Questions, Method, sections named by title, no file references.","Research; findings in the section Findings, one version per settled set.","Try at the cheapest level that answers the question.","Write the Domains: modules, seats, glossary words added or retired.","Write the Direction: decisions, rejected options, acceptance tests.","Ask the operator to confirm the direction, one point at a time.","Review the direction by architectural domain: security, seams, running qualities, health, events and logs, testing, upgrade, tools.","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",
next:"02",bounds:"Setup 30 min · proposing 15 min · each experiment 30 min · direction 30 min · review by architectural domain 20 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; the records test 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."],
steps:["Check the conclusion against mission, context and effect.","Reserve a number per question the effort answers.","Propose one record per question: its options and the one chosen; design choices go in the design.","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: a main file and a part per milestone, leaving the builder no choice. (new)","A design question: the operator answers, written as a design line. (new)","Add the decisions, updated:, and the effort's glossary words.","Review the design by architectural domain before it is merged.","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"],
@@ -437,7 +452,7 @@ const P = [
{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 functionality, never by an order between parts; 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."],
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 functionality, never by an order between parts; fold any not usable alone.","Give each milestone its part: numbered design lines, at most 60 KB; the main file at most 80 KB. (new)","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"],
@@ -453,7 +468,7 @@ const P = [
{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."],
steps:["Read what runs: compose file, environment, where data sits.","Name what it provides and requires.","Declare capabilities for machine facts.","Choose each part's form (design 20).","Kept data in requires, by template.","Write resources in the order they must happen.","Every secret in a file, never in env.","Pin every image by digest, upstream too.","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"],
@@ -461,15 +476,15 @@ const P = [
{n:"07",name:"Features",kind:"build",c:"--build",
trig:"Work that changes code across issues or repositories, too large for a small fix.",
who:"A lead (larger model), one fixing agent per repository (smaller), one reviewer (larger).",
steps:["Prepare: name the branch, make the worktrees, settle every question.","Write one context file per repository.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, what the one check does not cover. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per issue.","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk; nothing is pushed, no try runs. (new)","Review all of it, reading the integration tests against the code; at most 3 rounds.","A group pins the group's own mesh-sdk head in every member. (new)","Push the reviewed heads once and read each forge head against the reviewed commit; no draft pull request, no fix round on an open one. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by; the one check runs. A failed check is read, fixed, reviewed, pushed once more. (new)","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
steps:["Prepare: name the branch, make the worktrees, read the milestone part and the design's main file whole, then the code each design line lands in. (new)","List every place where a design line and the code disagree, beside the design questions. (new)","Every agent is one of hq's definitions (mesh-preparer, mesh-developer, mesh-code-reviewer, mesh-watcher); the lead writes only the handover. (new)","The prepare's only output: a build map per repository, each row a design line and where it lands, and the design questions. No rulings. (new)","Design complete: the lead answers every question against the principles; one no principle settles goes to the operator or the design writer; then develop. (new)","Read what exists today from the code and the live mesh: every effect of what is removed or moved, every reader of each, what happens to each and its test. No code before it is complete; the review refuses work without it. (new)","The build map opens with what exists today; subtasks ordered contract first, one writer per file.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, what the one check does not cover. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per subtask, each citing its design lines; a question stops only its subtask. (new)","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk; nothing is pushed, no try runs. (new)","Review all of it against the design lines, code no line asks for a finding, reading the integration tests against the code; at most 3 rounds; the report answers the state-and-seams and mesh-agnostic questions. (new)","A group pins the group's own mesh-sdk head in every member. (new)","Push the reviewed heads once and read each forge head against the reviewed commit; no draft pull request, no fix round on an open one. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by; the one check runs. A failed check is read, fixed, reviewed, pushed once more. (new)","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
prod:"One pull request per repository, one source revision delivered, resolved issues, no branch or worktree left.",
next:"02 (when it ships)",bounds:"Prepare 15 min · 30 min per issue · review 15 min, ≤ 3 rounds · one check, pushed once",
prev:["Pull requests as scratchpads","Context bought again and again","Review rounds spent on what a run would show","A node agent's bus reads, proven only by review"],
prev:["Pull requests as scratchpads","Context bought again and again","A change built on what was remembered, not on what runs","Review rounds spent on what a run would show","A node agent's bus reads, proven only by review"],
check:"The checker's one status, mesh/check; the review record; the tracker's move."},
{n:"08",name:"Small fixes",kind:"build",c:"--build",
trig:"One issue in one repository, about an hour of work.",
who:"The session, with one reviewer on the larger model.",
steps:["Read the issue and the code; settle any open question.","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Nothing is pushed and no try runs before the review. (new)","One review of the local commit.","Push the reviewed head once; only then open the pull request. (new)","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
steps:["Read the issue, the design lines it builds and the code; a design question is answered by the lead against the principles (one no principle settles by the operator or the design writer) before code. (new)","Read what exists in the fix's area today, briefly: every reader of what the fix changes. (new)","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Nothing is pushed and no try runs before the review. (new)","One review of the local commit.","Push the reviewed head once; only then open the pull request. (new)","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
prod:"One pull request, one delivery, a resolved issue.",
next:"—",bounds:"About an hour · one review, one push, one check",
prev:["A fix reviewed after its pull request opened","A long part run twice","A fix waiting on someone else's delivery","Ten SDK merges and eight controller merges in a day, one per fix"],
+2 -2
View File
@@ -14,7 +14,7 @@ and a forge address is an operational detail (see [`README`](../README.md)).
| Repository | Owns |
|---|---|
| `hal` | The monorepo — the node runtime, the module catalogue, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
| `hal` | The monorepo — the node runtime, the module catalog, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
| `hq` | This repository, under the company organisation — mission, research, design, decisions, issue diagnosis. Company-scoped (ADR 0019); the mesh is its first product. The source of truth for *why*. Carries no implementation. |
| *(one per application)* | Every standalone application, site or side-project gets its own repository, with `module.yml` at the root. Registered with the mesh as a build source; built and deployed by the same pipeline as anything in the monorepo. |
| `migration` | **Private.** The record of one installation replacing the predecessor mesh with this one: the runbook, a dated log of every step and what it cost, the per-service data procedures, the readiness checks, and the scripts. Private because it is the opposite of this repository in every way that matters — it names machines, addresses, ports and paths, because a procedure that cannot be followed is not one. Where hq asks *what did we decide and why*, that repository answers *what happened on the machines, in what order, and what to do next*. Its `HANDOFF.md` is where somebody picking the work up starts. |
@@ -48,7 +48,7 @@ Named by role, because the layout is itself part of the as-is design — see
| Area | Holds |
|---|---|
| Module catalogue | One directory per module, each with a manifest. Core modules sit under the mesh's own namespace; everything else at the top level. |
| Module catalog | One directory per module, each with a manifest. Core modules sit under the mesh's own namespace; everything else at the top level. |
| Node runtime | The daemon and interactive runtime that every node runs. |
| Bootstrap scripts | First-node initialisation, joining an existing mesh, and node rescue. |
| Shared library | The SDK every module builds against. |
+1 -1
View File
@@ -11,7 +11,7 @@ decisions:
# The mesh as it stands
A set of machines, each running the same runtime, each loading only the parts of the catalogue
A set of machines, each running the same runtime, each loading only the parts of the catalog
it has been assigned. They hold no shared filesystem and make no direct connections to one
another. What makes them a mesh is a database that knows what should run where, and a message
broker that carries everything between them.
@@ -105,9 +105,9 @@ the resource rather than on the node that built the module.
## A documented rule with no enforcement
Every module exposing capabilities is documented as required to declare the mesh's core runtime
as a dependency. **Zero of the catalogue's modules do.**
as a dependency. **Zero of the catalog's modules do.**
This is recorded here rather than quietly corrected, because it is the clearest instance of the
rule this repository states about itself: a rule whose enforcement does not exist is
indistinguishable from a wrong one, and costs more, because people believe it. Whether the rule
or the catalogue is wrong has not been decided.
or the catalog is wrong has not been decided.
@@ -24,7 +24,7 @@ is what makes a node a participant — it is reachable whether or not anyone is
protocol. Its capability surface is larger, because it includes stand-ins for every capability
discovered on peers.
The two are the same code with the same catalogue. A capability is written once and is
The two are the same code with the same catalog. A capability is written once and is
available to both.
## Anatomy naming, and what it obscures
@@ -52,7 +52,7 @@ For each assigned module the runtime, at startup:
4. For a service module, ensures it is installed and running.
Installation is idempotent and does the unglamorous work: ensure the runtime directory,
reconcile the link from the catalogue's definition into it, generate the environment, run any
reconcile the link from the catalog's definition into it, generate the environment, run any
outstanding local migrations, create data directories with the right ownership, then start the
service under supervision.
@@ -85,7 +85,7 @@ import is one-time and not persisted.
Three bootstrap scripts, and which one runs depends on the situation:
- **First node.** Nothing exists yet, so the script stands up the database the rest of the
mesh reads from, publishes the catalogue, and starts the mesh. This resolves the
mesh reads from, publishes the catalog, and starts the mesh. This resolves the
circularity of a mesh whose source of truth is itself a provisioned module.
- **Joining.** The node registers, takes its assignment from the database, and syncs.
- **Rescue.** A node that cannot reach the mesh is brought back far enough to.
+8 -8
View File
@@ -9,9 +9,9 @@ decisions:
- "ADR 0009"
---
# The catalogue, and what its shape says
# The catalog, and what its shape says
The catalogue holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
The catalog holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
ninety-one run *on* the mesh rather than being *of* it
(ADR 0001).
@@ -30,7 +30,7 @@ By namespace, and the namespace records origin rather than purpose:
## What the flat level actually contains
Grouped by what they are *for* — a grouping the catalogue itself does not express:
Grouped by what they are *for* — a grouping the catalog itself does not express:
| Purpose | Roughly |
|---|---|
@@ -51,7 +51,7 @@ connectivity is made four times.
## What the shape records
**The catalogue's shape records what was installed, not what anything is for.** One module is
**The catalog's shape records what was installed, not what anything is for.** One module is
the unit of one piece of software, because that is the only granularity the module system
offers.
@@ -64,7 +64,7 @@ deliberately does not yet settle the domain list.
## Where the shape came from
The catalogue's shape is not arbitrary and it is not a series of mistakes. **This repository
The catalog's shape is not arbitrary and it is not a series of mistakes. **This repository
began as a dotfiles repository**, and most of what looks inexplicable is inherited from that
directly.
@@ -88,7 +88,7 @@ The generalisation from *place files on my machines* to *manage a mesh of nodes*
move and it worked. What it did not do is revisit the assumptions underneath, because they were
never stated as assumptions — they were just how the thing already worked.
**This is the most useful single fact for anyone changing the catalogue**, and it is why the
**This is the most useful single fact for anyone changing the catalog**, and it is why the
linking principle in particular reads as a deliberate architectural choice when it is an
inheritance. See ADR 0012, whose case
this strengthens: the argument for links was never made *for a mesh*.
@@ -109,10 +109,10 @@ module out of the repository safe.
**Placement is already decided.** A standalone application belongs in its own repository
(ADR 0015), and reviewers reject
it in the monorepo. The catalogue's flat level is not a dumping ground by policy; it is one by
it in the monorepo. The catalog's flat level is not a dumping ground by policy; it is one by
history.
## Known inconsistencies in the catalogue itself
## Known inconsistencies in the catalog itself
Recorded because a reader will meet them:
+2 -2
View File
@@ -11,7 +11,7 @@ decisions:
# The seats, as they run
Written from the controller's code and the catalogue's manifests on their main branches. This is
Written from the controller's code and the catalog's manifests on their main branches. This is
the second piece of the new shape to exist, after [the lab](11-the-lab.md), and the first that the
predecessor has no equivalent of: there, any well-formed name became a seat by being claimed.
@@ -24,7 +24,7 @@ have; nothing else can add to it.
Five seats deliver a provision: the mesh's store delivers the relational database, the mesh's broker
the message transport, the artifact-store seat the registry, and two more the package registry for
one ecosystem and the git service. The remaining nine — the controller and catalogue seats, and the
one ecosystem and the git service. The remaining nine — the controller and catalog seats, and the
node-scope ones for the builder, the DNS port, the packet filter, intrusion prevention, the
private network, the resolver's configuration and the showcase — mark a role without answering for
anything a consumer requires.
+3 -3
View File
@@ -21,7 +21,7 @@ decisions:
**The `mesh-console` container no longer exists.** Read 2026-10-17 from `mesh-tools` (package
`node-tools`), `mesh-host` and `mesh-controller` on their forges' main, and from the live mesh
through `mesh_overview`, `mesh_machine`, `mesh_runtimes` and `mesh_describe`. Checked: the
catalogue (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
catalog (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a
module of that name. How the tool runner reaches a node, its count of modules and tools and the
controller's verbs were read again on 2026-10-26, after the delivery of release 6 (ADR 0539).
@@ -120,11 +120,11 @@ to-be document still describes it as assigned to "whichever machines an operator
actually runs is the opposite — every node the mesh holds carries one, because it is core
infrastructure the mesh itself depends on (discovery, the controller's own verbs, every module's
tools), not a convenience assigned to a workstation. There is accordingly no assign or unassign verb
for it: removing it from a node is not an operation the controller or the catalogue exposes.
for it: removing it from a node is not an operation the controller or the catalog exposes.
## What this repository's prior text got wrong
The document this replaces described `mesh-console` as a catalogue module served by a container and
The document this replaces described `mesh-console` as a catalog module served by a container and
assigned only to workstations. That module and its container are gone; the manifest is not — it
moved to `node-tools/module.json`, still declares `invokes: ["*"]`, and gained `waits` and the
`mcp-endpoint` provision, described above. What also moved is which language builds it: the
+2 -2
View File
@@ -18,7 +18,7 @@ machine and its licence manager; on the day this was written, three buckets exis
## What runs
- **The controller** creates a key-value bucket `<module>_<name>` for every declared state, from the
catalogue — on every start and, since the first module that declared state found it missing, on every
catalog — on every start and, since the first module that declared state found it missing, on every
push before the memberships that name it. A bucket nothing declares any more is reported and kept.
Every bucket carries the mesh's caps: 256 KiB a value, 64 MiB a bucket.
- **The grants**: the machine's runtime is granted, for each bucket a module it carries owns, writing
@@ -46,7 +46,7 @@ machine and its licence manager; on the day this was written, three buckets exis
## How it is checked
The controller's catalogue and broker tests (names, grants, memberships, a bucket asserted in place
The controller's catalog and broker tests (names, grants, memberships, a bucket asserted in place
against a real server); the runtime's tests over a real bus (current values without deletions, refusals,
the TypeScript SDK through the runtime); `module check` names a read whose owner on the shelf keeps no
such state.
@@ -15,7 +15,7 @@ decisions:
**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
catalog'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.
@@ -113,4 +113,4 @@ Not here: see [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md).
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.
guard in the catalog. Their unit tests hold the node record's refusals and the guard's account rule.
+1 -1
View File
@@ -18,7 +18,7 @@ Where the two disagree, the implementation wins and the disagreement is stated.
| [`07-knowledge.md`](07-knowledge.md) | The mesh keeps no store: what it knows is what modules answer, and the record is read by one |
| [`08-agents-and-work.md`](08-agents-and-work.md) | Agents as employees, tasks, workflows, and the meeting model |
| [`09-interfaces-and-observability.md`](09-interfaces-and-observability.md) | How the mesh is reached and watched — tools, board, proxy, health, thoughts |
| [`10-module-catalogue.md`](10-module-catalogue.md) | The catalogue's shape, and what its shape says |
| [`10-module-catalogue.md`](10-module-catalogue.md) | The catalog's shape, and what its shape says |
| [`11-the-lab.md`](11-the-lab.md) | The lab — the first piece of the new shape that exists, and what it does not yet do |
| [`12-the-seats.md`](12-the-seats.md) | The seats the mesh defines, who holds one, and where a seat changes resolution |
| [`13-the-console.md`](13-the-console.md) | The mesh's tools on the machine a person sits at, served by a module the mesh assigned there |
+8 -8
View File
@@ -431,17 +431,17 @@ It is the same rule the node-engine follows about a service that does not exist
(ADR 0004) — absence must be distinguishable
from a failure to answer — applied to coverage instead of to a machine.
## A bed reads the catalogue it proves
## A bed reads the catalog it proves
*Written 2026-09-21, from resolving [04-ISSUES/073](../../04-ISSUES/073-beds-carry-copies-of-catalogue-manifests/00-report.md);
decided in ADR 0089.*
The rule above — the run rebuilds what it tests — was held for binaries and images and not for
manifests. Beds built the manifests they install inline, as literals copied from the catalogue
when each bed was written; the copies did not move when the catalogue did, and a catalogue change
manifests. Beds built the manifests they install inline, as literals copied from the catalog
when each bed was written; the copies did not move when the catalog did, and a catalog change
was proven by no bed at all, while every bed stayed green against its copy.
**A bed that installs a catalogue module reads that module's manifest from the catalogue the run
**A bed that installs a catalog module reads that module's manifest from the catalog the run
was pointed at.** It rewrites what the lab must — a build artifact becomes the image the machine
holds, an image is pinned, a host port is remapped where one machine carries colliding modules,
an address may point at a stand-in the bed raises — and nothing else.
@@ -451,15 +451,15 @@ server, a secret in the environment, a requirement edge cut so no second provide
that is a mesh test. It does not get a name of its own if it runs the module's runtime — a
module's name is its tool namespace and its broker scope
(ADR 0093) —
so it reads the catalogue and installs what the module requires; a name of its own is for a
so it reads the catalog and installs what the module requires; a name of its own is for a
fixture that runs no real runtime.
**The receipt names the catalogue's commit** with the others', so a run taken before a manifest
**The receipt names the catalog's commit** with the others', so a run taken before a manifest
changed says so — the same rule as for the binaries, for the same reason.
*How it is checked:* a unit test in the lab refuses an inline manifest literal that names a
catalogue module unless the bed is declared, with its reason, in the test's own list, and refuses
a declaration for a copy that is gone; the receipt test asserts the catalogue is claimed whenever
catalog module unless the bed is declared, with its reason, in the test's own list, and refuses
a declaration for a copy that is gone; the receipt test asserts the catalog is claimed whenever
the run is pointed at one.
## Consequences
+1 -1
View File
@@ -31,7 +31,7 @@ offered one driver, everything worked, and snapshots took **seventy-six times lo
needed to. Nothing failed. Nothing warned. A lab in that state runs correctly and is simply too
slow to use — and the inner loop it exists to provide quietly does not exist.
The second is the more dangerous shape and it is a variant the mesh has not catalogued before.
The second is the more dangerous shape and it is a variant the mesh has not cataloged before.
Its usual failure is *reported success and did nothing*. This is **reported success and did it
seventy-six times slower**, which no error surface catches because nothing is wrong.
+1 -1
View File
@@ -177,7 +177,7 @@ raises the bootstrap forge under the forge module's container name, with the mod
its data directory, so the module holds it by the found rule; the network is the one difference a take
has left to say. *How it is checked:* a host test joins a kept network and refuses one it cannot; a
host test keeps a left-out module's record and hold and removes an absent module's; a bootstrap test
holds the installer's constants to the module's manifest where the catalogue is checked out beside it.
holds the installer's constants to the module's manifest where the catalog is checked out beside it.
**What filters the machine, and the found firewall kept retired** — revision, 2026-10-02
(ADR 0168). The node-engine
+1 -1
View File
@@ -326,7 +326,7 @@ shape moves — it names a thing and the node-engine fetches it — and the cont
genesis must raise before the controller can exist. It still raises one, for the store and the broker,
which is where somebody else's software belongs.
The mesh's own components — the node-engine, the controller, the catalogue, the builder, the vault — are
The mesh's own components — the node-engine, the controller, the catalog, the builder, the vault — are
delivered as binaries into directories named for their versions, by the mechanism
ADR 0141 describes. Third-party
software stays a container. The split is not about isolation; it is about who built the thing.
+5 -5
View File
@@ -425,8 +425,8 @@ time.*
A network manager rewrites `/etc/resolv.conf` on every connectivity change unless it is told not to
(ADR 0117), so the module holding
`node-uplink` — the one telling it — writes the file itself, and there is one owner for it, the one
whose program would otherwise overwrite it. Each of the catalogue's managers' modules
(NetworkManager, systemd-networkd; dhcpcd's left the catalogue with
whose program would otherwise overwrite it. Each of the catalog's managers' modules
(NetworkManager, systemd-networkd; dhcpcd's left the catalog with
ADR 0226, held by no machine) renders the same template from the resolver's holders and
requires the mesh's resolver, so a machine is refused when nothing in the mesh resolves rather than
given a file listing nothing. The managers' own mechanisms were weighed and not used: NetworkManager's
@@ -440,7 +440,7 @@ node declaring one path are refused. The module that wrote the file before, its
retire. The file changes owner in one apply on each machine: the node-engine hands a whole file to the
resource declaring its path now rather than removing it first, so a machine is never without it.
*Checked by the controller's tests that the uplink modules carry one identical template and that
nothing else in the catalogue writes the path, a resolution test refusing a second writer, and the
nothing else in the catalog writes the path, a resolution test refusing a second writer, and the
node-engine's handover test, in which the file is present at every step of the apply.*
**A machine's names are one seat's** (ADR 0223).
@@ -643,7 +643,7 @@ to stop working.*
### The name is a label, not a domain
*2026-09-09, from running the whole mesh in the lab.* A route contribution carried its public name
in full — the forge as `git.example.tld`, spelt out in the module. Pointing the same catalogue at a
in full — the forge as `git.example.tld`, spelt out in the module. Pointing the same catalog at a
different domain — a lab standing in for production, or a second operator's mesh — meant rewriting
that name on every routed module. The mesh was holding a **map of names to services**, which is the
one thing it must not: a public name is two facts owned by two different places, and neither is the
@@ -976,7 +976,7 @@ run-once step before it starts. The step names the binding it reads and the prox
step, so when the authority moves the root is fetched again and the proxy is recreated with it
(ADR 0099).
*How it is checked:* the route-forwarding bed installs the authority, the proxy and a consumer
from the catalogue and asserts the routed name is served.
from the catalog and asserts the routed name is served.
**And a machine trusts that authority because a module put its root in its trust store**
(ADR 0147). The proxy's fetch
+10 -10
View File
@@ -57,10 +57,10 @@ produced from there. A module whose path is the repository root is the ordinary
not a separate shape.
*Corrected 2026-09-12. This section previously said the manifest was at the root, full stop, which
made a repository hold exactly one module. Nothing that exists is shaped that way: the catalogue
made a repository hold exactly one module. Nothing that exists is shaped that way: the catalog
holds sixty-seven modules in their own directories, the system being replaced has always built a
module from a repository and a path, and the root-only reading left every existing module
unbuildable — pointed at the catalogue the builder finds no manifest, pointed at a module's source
unbuildable — pointed at the catalog the builder finds no manifest, pointed at a module's source
it finds no manifest either.*
## A manifest is checked where it is written
@@ -84,7 +84,7 @@ read without a store (ADR 0122
keeps the store authoritative, so a claim on a mesh seat is judged fully only at registration, and the
check says that too).
*How it is checked:* the controller's test runs the check over the catalogue checkout beside it and
*How it is checked:* the controller's test runs the check over the catalog checkout beside it and
over a manifest with a known fault, and asserts the first passes and the second names the fault; the
test that used to be the only check, `TestEveryCatalogueManifestParses`, now stands beside a command
anybody can run.
@@ -218,7 +218,7 @@ for, arrived at by an implementation detail.
until it is told otherwise; the mesh changing its mind is not a machine acting on it, and
collapsing the two is how a mesh comes to report success for something that has not happened.
*Checked end to end: a commit, a build, a catalogue entry, and a machine that ends up running what
*Checked end to end: a commit, a build, a catalog entry, and a machine that ends up running what
the source says — with both halves that make the answer trustworthy. It is still running the old
one until it is pushed, and it stops being reported as behind once it has caught up, because a
status that says "behind" for ever is one nobody reads.*
@@ -398,7 +398,7 @@ that distinction once already.*
## The four the loop cannot build
*2026-09-12. Written down because it keeps being rediscovered as if it were new, once per
component. It is one rule; the instances are listed below. Revised the same day, when the catalogue
component. It is one rule; the instances are listed below. Revised the same day, when the catalog
gained an owner and turned out to be a fourth — which is the argument for stating the rule rather
than the list.*
@@ -412,23 +412,23 @@ have a route and one does not:
| The controller | It is what installs modules. Nothing can install it before it runs. | **Carried inside the installer** and published once there is a registry (ADR 0067) |
| The registry | It *is* where artifacts are delivered from. A store cannot be delivered through itself. | **Pulled from the public internet** — its image is an ordinary public one, never built ([`04-ISSUES/029`](../../04-ISSUES/029-the-artifact-store-cannot-be-delivered-by-the-artifact-store/00-report.md)) |
| The builder | It is what builds. Nothing builds it before it runs. | Built at genesis by the init builder (ADR 0070) |
| The catalogue | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder (ADR 0070) |
| The catalog | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder (ADR 0070) |
**How they arrive is settled and not yet built.** The installer carries an init builder, which
clones the source and builds the controller, the catalogue and the builder before a mesh exists
clones the source and builds the controller, the catalog and the builder before a mesh exists
to install anything. Two things about that are open and named in
ADR 0070: where the init builder
clones from, given the forge normally runs on the mesh it would be rebuilding, and what it
publishes into, given the registry is installed later in the order today.
**There is a fourth, and the list was closed too early.** The catalogue owns the module graph
**There is a fourth, and the list was closed too early.** The catalog owns the module graph
(ADR 0070), so it cannot be
installed by something that needs it in order to install anything — the same shape as the other
three. This section previously said the list was closed at three, which was written before the
catalogue had an owner and is corrected here rather than left to be reasoned from.
catalog had an owner and is corrected here rather than left to be reasoned from.
**And the answer for all four is now one mechanism, not four special cases.** Genesis carries an
*init builder* and builds the core modules on the machine — controller, catalogue and builder —
*init builder* and builds the core modules on the machine — controller, catalog and builder —
rather than carrying a finished image of any of them. So the question is no longer "how does this
one get here first?" asked once per component; it is answered once, by the thing that is carried
being a builder rather than a result.
@@ -102,7 +102,7 @@ anything on the machine that can talk to the runtime. So a secret reaches a proc
the file the node-engine wrote and points the program at it, and the mesh's own programs take a `_FILE`
twin for every variable that carries a credential. Software that reads only its environment is
not forbidden; it is **declared**, on the container, with a reason, so the manifest says which
secrets are exposed that way. **Checked** at composition: the catalogue engine refuses a
secrets are exposed that way. **Checked** at composition: the catalog engine refuses a
`${secret:…}` in a container's `env` outright, and a secret-carrying file named in `env-file`
unless the container carries the declaration.
+1 -1
View File
@@ -170,7 +170,7 @@ the one the mesh runs for its own: it offers a place to push, and claims that ro
machine.
**A rule was enforced only at the far end.** A module may not declare an action, and the node-engine
refused one correctly — but the controller accepted it into the catalogue, resolved it and
refused one correctly — but the controller accepted it into the catalog, resolved it and
pushed it, so the refusal arrived on a machine with nothing tying it back to the manifest. The
rule held; it was just unusable, which is the same shape as the network shape that cost five
failing tests before anyone read the node-engine's log. It is now refused where it is written.
+6 -6
View File
@@ -132,7 +132,7 @@ adopted as modules, and the machine's service is still reachable.
Genesis ends with a mesh of one that runs, and that is not the same as a mesh that works. What it
has is a controller, a store, a queue and a registry. What it cannot yet do is **produce
anything** — and almost every module in the catalogue is waiting to be produced, because a manifest
anything** — and almost every module in the catalog is waiting to be produced, because a manifest
names what its artifacts are and nothing has made them.
So installing continues:
@@ -172,7 +172,7 @@ need no registry. The gitea module's own server is an upstream image, so buildin
**The store genesis raises is the `postgres` module's server, so adopting it keeps it** (ADR 0338, the
store's twin of the bus's ADR 0333). The bundle raises the store under the name, on the image, in the data
directory and with the superuser password file that the catalogue's `postgres` module declares, and every step
directory and with the superuser password file that the catalog's `postgres` module declares, and every step
of the bundle and of the installer reaches it by that name. At the store step the module declares the
container the bundle raised, so the store is adopted where it runs and is not restarted. The module does not
change, so no running mesh's store moves. *How it is checked:* a test reads the module's manifest and holds
@@ -190,7 +190,7 @@ something it made rather than by a placeholder.
mesh running; building it from its own repository and path is what makes it upgradeable. The first
time that happens is the moment the mesh stops depending on the installer for anything.
**And then the catalogue.** Every module with source of its own is built the same way. Until this
**And then the catalog.** Every module with source of its own is built the same way. Until this
has happened a mesh can install only what is public or carried, which is the foundation and little
else.
@@ -233,7 +233,7 @@ was brought up the supported way, so the rule that it must be is, today, unenfor
**Nothing asks for the core modules yet.** Installing ends with a builder that works — a raised
machine will build the shared base and a module on top of it when asked — and nothing asks. The
paragraphs above describing the catalogue being built are a thing somebody now types, rather than a
paragraphs above describing the catalog being built are a thing somebody now types, rather than a
thing that cannot happen.
**A module's declaration still has to be copied onto the machine by hand.** The installer reads the
@@ -242,7 +242,7 @@ longer among them: it lives in the controller's own repository
(ADR 0069), the build the
installer runs reports it with the artifact resolved, and genesis registers that
([issue 072](../../04-ISSUES/072-the-controllers-manifest-exists-twice/00-report.md)). The
builder's manifest belongs in the same repository and is still read from the catalogue; that is
builder's manifest belongs in the same repository and is still read from the catalog; that is
the last of this.
**A machine has no account for a registry that asks for one.** The mesh grants a consumer a
@@ -275,7 +275,7 @@ issue tracker holds it as issue 602.
| An image is named exactly | A machine refuses a bundle naming an image by tag. The refusal is exercised, not assumed. |
| The installer is what installed this | **Nothing.** See above. |
| The builder can arrive on a fresh mesh | The installer carries it and installs it as its last step, and the genesis bed raises a machine by running the installer. A bed that raises one any other way fails its own acceptance check. |
| The builder is assigned before the package registry exists | The build-agent manifest's own test holds the requirement optional and the genesis document declared and issued outside the mesh; the controller's resolution test over the real catalogue assigns the builder with no provider, lists the requirement unbound, and binds it once gitea is offered. The builder's tests refuse a package, an `.npmrc` image and an `@novox/` TypeScript build with neither binding nor genesis document, before anything is built, and build a Go artifact; they read the genesis document only without a binding. The installer's test seals the document at the packages step and gives the builder no `serves` setting and no `npm-password`. **Not yet proved live:** a genesis bed from bare passing the builder, packages, sdk and base steps. |
| The builder is assigned before the package registry exists | The build-agent manifest's own test holds the requirement optional and the genesis document declared and issued outside the mesh; the controller's resolution test over the real catalog assigns the builder with no provider, lists the requirement unbound, and binds it once gitea is offered. The builder's tests refuse a package, an `.npmrc` image and an `@novox/` TypeScript build with neither binding nor genesis document, before anything is built, and build a Go artifact; they read the genesis document only without a binding. The installer's test seals the document at the packages step and gives the builder no `serves` setting and no `npm-password`. **Not yet proved live:** a genesis bed from bare passing the builder, packages, sdk and base steps. |
| The controller a mesh runs is one it built | The genesis bed asserts the running controller is pinned to a digest this mesh's own registry serves, for an image built from a named repository and commit — not one the installer carried. |
| A core module is built rather than only carried | The controller is rebuilt from its own repository and path, and the running mesh is upgraded to the result — the same path any module takes. |
| Installing produced a mesh that can produce | A module with source of its own is asked for and comes back pinned to a digest this mesh's registry assigned, not to a placeholder. **Done by hand on a raised machine, not yet by a bed** — it built the shared base and then a module naming that base. Nothing automated asserts it, which makes this the weakest check on this page. |
+9 -9
View File
@@ -43,7 +43,7 @@ Turning a module's source into artifacts the mesh can pin, publish and deliver
truthfully what was produced and what it was produced against.
Everything else is somebody else's: *what* to build is the controller's, *what a build means* is
the catalogue's (ADR 0072), *where an artifact runs* is the
the catalog's (ADR 0072), *where an artifact runs* is the
controller's again. The builder's whole responsibility is the middle.
## The language
@@ -83,7 +83,7 @@ output into a second stage; and sets an environment variable naming the compiled
module repeats it. Miss any step and the failure arrives somewhere else — as a crash loop, a
placeholder digest, a module that builds and does nothing.
The evidence that this is too hard is in the catalogue: **most modules are not converted**, and the
The evidence that this is too hard is in the catalog: **most modules are not converted**, and the
two converted during one session were each wrong twice before they were right, against a working
example sitting open in the next window.
@@ -163,8 +163,8 @@ makes the drift worse while hiding it.
| A module with its own code needs no Dockerfile | A module declaring only a language builds, and its artifact is pinned to a digest the mesh's registry assigned. |
| An archive is produced and delivered | A module declaring an archive is built, and the machine assigned it has the unpacked files — verified on the machine, not in the build's own output. |
| A Dockerfile still works | A module that declares one builds exactly as before, because software that needs a particular base has not stopped existing. |
| Nothing is announced that was not made | A build made to fail announces nothing, and the catalogue's graph is unchanged afterwards. |
| A build keeps what it was told | A catalogue started after a build asks for what it missed and receives the manifest and the artifacts stood on, not a summary. |
| Nothing is announced that was not made | A build made to fail announces nothing, and the catalog's graph is unchanged afterwards. |
| A build keeps what it was told | A catalog started after a build asks for what it missed and receives the manifest and the artifacts stood on, not a summary. |
| The toolchain is the mesh's, not the author's | Changing the toolchain makes every module built against it stale, and each names what moved. |
## The whole surface, in one place
@@ -207,7 +207,7 @@ declared in one of three ways, for the three things a path can be: the module's
or file resource, or where a secret, grant or contribution lands), the operator's (an `accesses`
entry), or the machine's (a facility a declared capability grants — `container-runtime` grants its
socket). *How it is checked:* the parser refuses an undeclared mount naming the path and the three
remedies, and a test parses every manifest in the catalogue beside the checkout.
remedies, and a test parses every manifest in the catalog beside the checkout.
### What it builds
@@ -247,7 +247,7 @@ counts as a copy and what as a base.
| `directory` | a directory with a mode and an owner, **placed by the mesh** under the node's root: `place: "."` is the assignment's own root, `place: "mesh"` the mesh's directory for the module, a pathless one sits beneath the root by its id; a stated path is the placement for data that must stay where it is, and may itself sit beneath a placed one (`${dir:<id>}/…`). Everything else names it as `${dir:<id>}` ([issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md), [174](../../04-ISSUES/174-the-meshs-own-files-for-a-module-are-placed-by-the-definition/00-report.md)) | ✅ |
| `file` | literal content, with `${bound:…}`, `${secret:…}`, `${dir:…}`, `${port:…}`, `${machine:…}` and `${setting:…}` filled in — the last an operator's value from the assignment's settings, refused by name when unset (ADR 0155) unless the manifest gives a preference a default (ADR 0262) | ✅ |
| `user` | a login | ✅ |
| `access` | a pre-existing path it may use and must not own, **named by id** and placed by the assignment (`accesses: {<id>: <path>}` on its settings); mounts say `${access:<id>}`; a path in the definition is the default an assignment replaces, tolerated while the catalogue converts ([issue 153](../../04-ISSUES/153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md)) | ✅ |
| `access` | a pre-existing path it may use and must not own, **named by id** and placed by the assignment (`accesses: {<id>: <path>}` on its settings); mounts say `${access:<id>}`; a path in the definition is the default an assignment replaces, tolerated while the catalog converts ([issue 153](../../04-ISSUES/153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md)) | ✅ |
| `archive` | files fetched by digest and unpacked | ✅ |
| `package` | a package that must be present | ✅ |
| `network` | a named container network | ✅ |
@@ -292,7 +292,7 @@ This is the bus's shared-work pattern, not a build-specific one: any module decl
`accepts` has its work shared by its holders the same way. Building is the first use.
*Built and proven live 2026-10-03.* `build-agent` holds `node-build-agent` on all four machines; the first
build taken by a workstation's agent was a catalogue module at 09:35 UTC; the one-holder `builder` is
build taken by a workstation's agent was a catalog module at 09:35 UTC; the one-holder `builder` is
retired. The switch found five gaps, each an issue: a worker whose type changed stranded its holder
([206](../../04-ISSUES/206-a-seats-worker-changing-type-strands-the-holder-and-the-build-that-would-fix-it/00-report.md)),
a re-made worker replayed the stream's history ([207](../../04-ISSUES/207-a-re-made-worker-replayed-every-ask-the-stream-kept/00-report.md)),
@@ -316,7 +316,7 @@ controller's seat verb through the mesh MCP server — reads it back afterwards.
id beside it, and `build` says the id it asked with. The mesh keeps no second copy: the stream is the
log. The mesh MCP server's `build` tool asks and answers at once with the id; the outcome is taken in — the
build recorded, the module registered with its source — by whoever hears it, the waiting command or
the daemon following the role's event, so a build nobody waited for still reaches the catalogue
the daemon following the role's event, so a build nobody waited for still reaches the catalog
([issue 176](../../04-ISSUES/176-the-consoles-build-tool-neither-waits-nor-registers/00-report.md)). A viewer of builds, when one is built, is a subscriber over these subjects and the outcome; the
builder needs nothing more for it.
@@ -383,7 +383,7 @@ than retried for ever. Live: the store's size before and after the first nightly
*2026-09-29 —
ADR 0142.*
The toolchain list was typescript and python, and only typescript had a base module in the catalogue.
The toolchain list was typescript and python, and only typescript had a base module in the catalog.
Meanwhile the controller — written in the language this project is mostly written in — was built as
an image from a hand-written Dockerfile, which is the per-repository incantation this whole mechanism
exists to abolish.
+224 -135
View File
@@ -3,27 +3,64 @@ layer: to-be
status: proposed
code:
- mesh-catalog modules/showcase
- mesh-controller internal/builder
- mesh-sdk src
updated: 2026-10-02
- mesh-delivery modules/mesh-delivery-indexer/judge (the manifest validator)
- mesh-delivery modules/mesh-delivery-builder
- mesh-sdk go/catalogue (the manifest's shapes)
- mesh-sdk go/declaration (the node-engine's resource vocabulary)
- mesh-contracts
updated: 2026-11-12
decisions:
- "ADR 0150"
- "ADR 0099"
- "ADR 0053"
- "ADR 0578"
- "ADR 0595"
- "ADR 0619"
- "ADR 0634"
- "ADR 0635"
- "ADR 0625"
- "ADR 0627"
- "ADR 0573"
- "ADR 0605"
- "ADR 0606"
- "ADR 0607"
- "ADR 0609"
- "ADR 0593"
- "ADR 0499"
- "ADR 0498"
- "ADR 0478"
- "ADR 0436"
- "ADR 0366"
- "ADR 0316"
- "ADR 0267"
- "ADR 0259"
- "ADR 0262"
- "ADR 0348"
- "ADR 0564"
- "ADR 0596"
- "ADR 0201"
- "ADR 0193"
- "ADR 0192"
- "ADR 0189"
- "ADR 0175"
- "ADR 0152"
- "ADR 0150"
- "ADR 0142"
- "ADR 0135"
- "ADR 0099"
- "ADR 0074"
- "ADR 0053"
- "ADR 0052"
- "ADR 0040"
- "ADR 0039"
- "ADR 0053"
- "ADR 0005"
---
# Writing a module
A worked guide. One module, four capabilities, four languages, and the packages it publishes.
A worked guide: what a module is built of, the manifest form of each part, and the packages it publishes.
The reference for *what can be said* is [`18-building-a-module`](18-building-a-module.md); the
reference for *what the code and the mesh say to each other* is
[`19-the-module-protocol`](19-the-module-protocol.md). This is how you actually write one.
The reference for *what a manifest may say* is the manifest validator, the rules of the seat `mesh-catalog`'s holder
that judge every manifest (ADR 0578 §1), with [design 32](32-what-a-module-declares.md) for what the bus makes of it;
the reference for *how a build is modelled* is [design 18](18-building-a-module.md); the reference for *what the code
and the mesh say to each other* is [design 19](19-the-module-protocol.md). This is how you actually write one.
## First: what is in the SDK, exactly
@@ -47,141 +84,181 @@ Split the way the protocol is split:
mesh set. Reading a file is not a thing an SDK needs to teach.
- **No API clients.** A Plex client belongs in the Plex module. It changes when Plex changes,
which has nothing to do with any other module — *frequent **and** cascading is the disease.*
Logic over a *seat* is not such a client: it is the seat's client package, in the seat's contracts
module (below), never in the SDK and never in a holder.
- **No storage, no HTTP framework, no logging library.** Use the language's.
If you find yourself wanting to add something to the SDK, the test is ADR 0039's: **does editing it
recompile unrelated modules, and does it change often?** Both, and it does not belong.
recompile unrelated modules, and does it change often?** Both, and it does not belong. It goes to
its owner instead: a seat's contract or client, the module that owns it, or its domain's SDK (*Where a seat's code
goes*, below; ADR 0634 §1).
## A module with four capabilities, in four languages
## Where a seat's code goes
A module is **one piece of software** (ADR 0040) and
may still be written in several languages — each artifact names its own, compiles alone and is
packed alone.
*Added 2026-11-04, widened 2026-11-05* (ADR 0625, ADR 0627, ADR 0634, ADR 0635; [design 26](26-the-seats.md) *Where
a seat's code goes* is the whole rule). Shared code lives with its owner (ADR 0634 §1): a module that calls or holds
a seat, or shares code with other modules, meets these places, and each piece of code has one, the first that fits:
```
showcase/
module.json
events/ ← TypeScript: reacts to what the mesh does
tools/ ← Go: answers questions
provisioner/ ← Rust: grants instances of what it provides
ingest/ ← Python: a scheduled job
```
| the code | lives in | written |
|---|---|---|
| a seat's types, subjects, the layouts of its states and a typed call per verb or accept | the seat's **contract**: a package of its own per language in its domain's contracts module (`mesh-<domain>-contracts`, or `mesh-contracts` for the Core domain and a domain with none), for a seat defined whole; a seat at the seat list's grain has only its definition, which every reader reads from the bus (ADR 0635 §1, §2) | generated from the seat's definition, never by hand |
| logic two or more callers or holders would otherwise each write over that seat (an ask and its awaited answer, publishing a file in parts to a registry) | the seat's **client package**, beside its contract, owned by the contracts module | by hand; it uses only its own seat's contract, mesh-sdk's runtime and its own domain's SDK, never another seat's client, and names no holder, no module's tool and no module's subject |
| logic and state layouts one module owns, where it holds no seat for them | that module's **own package**, which other modules import from it: by path in the same repository (ADR 0609 §2), by a published version from another (ADR 0573 §1), so its manifest declares it as a package artifact; its fingerprint counts only that package's files (ADR 0593 §2), so its importers rebuild only when it changes | by hand, in the owner module |
| logic and forms no seat carries and no one module owns, used by one domain's modules or seats only (moving a file in parts, the build steps' shared logic) | that domain's **domain SDK**, `mesh-<domain>-sdk`, a package-only module (ADR 0634 §2) | by hand; it imports only mesh-sdk and contract packages, names no module's tool or subject, and holds no seat's type or state layout |
| what every module needs whatever the seat (the bus connection, the tools bundle's runtime, state), and a part modules or seats of more than one domain share that names no seat, verb or seat's type | **mesh-sdk** | by hand; it takes no seat-specific type, imports no contracts module, and reads the seat list from the bus |
| the seat's implementation | the **holder** module | by hand, against the contract; never the place of the contract or a client |
### The manifest
**So a module imports the contracts and clients of the seats it uses, the packages of the modules whose state it
reads, its domain's SDK where it uses it, and mesh-sdk.** Its users rebuild when something they use changes, not when
any seat or any domain does. A seat you define yourself, outside the mesh's own contracts, gets a
contracts module of its own, which may share your holder module's repository (ADR 0625 §7): a second holder then
imports only the contract, never your holder. A contracts module and a domain SDK are modules like any other, found by
their manifests in folders of a repository and delivered to no node (ADR 0634 §8). And a role no other module could
ever take over is no seat at all: it is your module's own tools and events (ADR 0625 §8).
```json
{
"module": "showcase",
"build": { "artifacts": [
{ "name": "events", "kind": "bundle", "language": "typescript",
"entrypoints": ["index.js"] },
{ "name": "tools", "kind": "bundle", "language": "go",
"entrypoints": ["tools"] },
{ "name": "provisioner", "kind": "bundle", "language": "rust",
"entrypoints": ["provisioner"] },
{ "name": "ingest", "kind": "bundle", "language": "python",
"entrypoints": ["ingest.py"] }
]},
"resources": [
{ "id": "events", "type": "process", "name": "showcase-events",
"artifact": "events", "run": ["node", "index.js"] },
{ "id": "tools", "type": "process", "name": "showcase-tools",
"artifact": "tools", "run": ["./tools"] },
{ "id": "grants", "type": "process", "name": "showcase-grants",
"artifact": "provisioner", "run": ["./provisioner"] },
{ "id": "ingest", "type": "process", "name": "showcase-ingest",
"artifact": "ingest", "run": ["python", "ingest.py"],
"schedule": "0 3 * * *" }
]
}
```
*How it is checked:* by the checks design 26 lists: the generators' tests, mesh-sdk's import and package-list tests,
the build modules' regenerate-and-compare step, the catalog check of a client and the manifest validator's refusal of a
contracts module that claims a seat; the home of each new exported name, a domain SDK's importers included, and a role
made a seat or a seat left as one module's tools, by the review of the pull request that adds it (ADR 0634 §10).
**Four artifacts, four toolchains, four processes, one module.** Nothing here says how any of them
is hosted — that is the mesh's, and it is why these are `process` rather than four containers.
## Who reads a manifest, and what each one judges
### Step 1 — say what it is, in each language's own terms
*Rewritten 2026-11-12 from the manifest validator's code on mesh-delivery's main (the operator, 2026-11-12: "first
look into the indexer's code that reads the manifest and update our module-development documentation accordingly").*
Each part is an ordinary project in its language, depending on the mesh SDK for that language the
way it would depend on anything:
A manifest is read by four parts, and each judges only what is its own:
```
events/package.json "@novox/mesh-sdk": "^1.2.0"
tools/go.mod require novox.example/mesh-sdk v1.2.0
provisioner/Cargo.toml mesh-sdk = "1.2"
ingest/pyproject.toml dependencies = ["mesh-sdk~=1.2"]
```
| part | reads | judges | where a refusal is said |
|---|---|---|---|
| the **manifest validator**, the rules of the seat `mesh-catalog`'s holder (the indexer) | every manifest it takes in, and every manifest a change touches, at the change's head | the manifest strictly: every field known, at every depth; every rule a manifest is held to alone (a name's form, an artifact's words, a claim on a seat of the seat list, kept data, events, secrets, health, a container's `run-once`, `schedule` and `while-stopped`) and every rule between the manifests of one catalog | the change's check, naming each problem; and the indexer's verdict, which every other part takes (ADR 0578 §1, §2) |
| the **builder** and its build modules | `build` | which build module builds each artifact (`kind` and `language`) and whether an upstream reference is pinned by digest (ADR 0478, ADR 0498) | the build's refusal, naming the artifact |
| the **node-engine**, through its composer in the change's composition step and on each node | the resources of each module composed for one node | each resource's `type` and fields strictly, by the node-engine's vocabulary (mesh-sdk's declaration package): a field a type does not use is refused, never ignored, and an `action` is refused (ADR 0005) | the composition step of the change, for each machine the change touches; the node's verdict at apply |
| every other part (the assigner, the grants part, the tool runner, the provisioner) | the manifest, decoded leniently | nothing: it takes the indexer's verdict (ADR 0578 §3) | — |
**Resolved from the mesh's own package registries**, which are the ordinary registries for each
ecosystem, hosted by the mesh. An author does nothing unusual: `npm install`, `go mod tidy`,
`cargo build`, `pip install` all work, and a developer's laptop resolves exactly what a build does.
**A field the running manifest validator does not know** makes the indexer set the manifest aside to wait for a newer
validator; the change's check, which runs the validator built from the change's head, refuses it (ADR 0578 §1, as
changed). So a field is added to the validator in the same change that first writes it.
### Step 2 — write each part against its capability
The manifest validator does not judge a resource's own fields: those are the node-engine's vocabulary, judged where a
module is composed for a machine. A module assigned to no machine is therefore judged by the validator alone.
Each uses only the part of the protocol it needs. The event consumer never learns what a grant is.
## What a module is built of
```
events/index.ts on("module.builder.built", …) → the events capability
tools/main.go tool("showcase_state", …) → the tools capability
provisioner/main.rs grant → credential → the provisioning capability
ingest/ingest.py emit("module.showcase.ingested", …) → events, emitting only
```
A module is **one piece of software** (ADR 0040), and may be built of several parts. Each part is an artifact in
`build.artifacts`, named locally, and most are run by a resource in `resources` that names the artifact by its
`artifact` field: the resolved manifest names the artifact's digest where the repository's manifest names the
artifact (a `source` and a `digest` for a bundle or an archive, an `image` for an image).
### Step 3 — the mesh does the rest
**The artifact kinds** the manifest validator accepts are five, and a builder builds each with one build module:
You do not write a Dockerfile, a unit file, or a port number. The builder picks the toolchain from
the language, compiles each artifact alone, and publishes it. The controller assigns the machine
and the ports; the node-engine writes the units.
| kind | its words | built by |
|---|---|---|
| `bundle` — the module's own code | `language`; for Go also `system` (one of the systems the mesh builds for), `from` (the package it compiles, relative to the module) and `binary`; for TypeScript `entrypoints` and, where it keeps imports, `external`; `loads`, `env` and `bus` for a tools bundle (below); `paths` for a language other than Go | the Go or the Node build module: a bundle in Go or TypeScript (ADR 0478); a bundle in another language is a build module of its own first |
| `package` — code other modules compile against | `language` (Go or TypeScript), `series`, `paths` (the files it is built from, ADR 0499), `generated-from` for a contracts module's | its language's build module, which asks the registry seat of its language to publish it (ADR 0605 §3, ADR 0606) |
| `image` — a container image built from a recipe | `from` (a Dockerfile), `target`, `compiles` (the Go package a recipe compiles, ADR 0267), `context` (a second repository its build reaches into) | the image build module |
| `upstream` — somebody else's image, copied | `from`, a reference pinned by its digest (`@sha256:`) | the image build module (ADR 0498 §4) |
| `archive` — files packed as they stand | `from` (a directory of the module), or an upstream release by `url`, `sha256` and `version`, with `file` or `within` (ADR 0316) | the Go build module |
### What it costs you to use four languages
Every artifact may also say `needs` (an edge no other line says), and every kind but `upstream` its `series`. A `bundle` reached by nothing (no
`loads`, no `tools`, no resource naming it) is refused by name (issue 216); a resource naming a `package` is refused,
since a package is a build input and runs nowhere.
**Honestly: four sets of dependencies to keep current, and four SDKs that must agree.** The mesh
makes it possible, not free. A module in one language is simpler, and the reason to use four is
that one of them genuinely suits a part better — not that you can.
**The parts, and the manifest form of each:**
| part | the manifest form | what runs it |
|---|---|---|
| **tools** | a `bundle` with `loads` naming what the runtime loads (for a Go bundle its `binary`), and the module's `tools` listing every tool it serves; `replaces` for the shell commands a tool replaces | the node's tool runner (ADR 0175); with `runs-as`, a **runtime of its own** under the module's account (below) |
| **a long-running service of its own code** | a `process` resource: `artifact` (a bundle), `name` (what the unit is called), `run` (the command, relative to the unpacked bundle), `user`, `env` and `env-file`, `restart-on`, `health`, `yields`, `replaces` (the ids it takes over from) | the node-engine, as a unit that stays up |
| **its own bus account for a process** | the process's `bus`: the `emits`, `state`, `queues`, `invokes` and `asks` it is granted, a subset of what the manifest declares; an own secret holding the account, owned by the process's `user` (ADR 0436 §1) | the grants part composes the account from it |
| **a scheduled job** | a `process` or `container` resource with `schedule`, a five-field cron; never with `run-once`; a process with no `restart-on`, `replaces` or `witness`; a scheduled container may name `while-stopped` (below) | the node-engine, again on each fire (ADR 0053) |
| **a one-time step** (a seed, a first-start step, a validator of a file) | a `process` or `container` resource with `run-once` true: a **run-once step**, which gates every resource after it; `restart-on` makes it run again when what it names changes (ADR 0052, ADR 0099) | the node-engine, to completion |
| **a migration of its own state before a new version runs** | `prepares` true, and a `container` or `process` that stays up running an artifact the module built: the mesh runs that program in its preparation mode (ADR 0135); never a run-once step written by hand for it | the node-engine, before the new version starts |
| **a seat's implementation** | `claims`, each `{name, scope, serves}` for a seat on the seat list (the verbs it serves; a kinded seat's `kind`); a seat the module defines for itself in `seats`; the verbs answered by its tools bundle or a process | as its tools or its process |
| **an image** | an `image` or `upstream` artifact, and a `container` resource naming it by `artifact`; or a container naming an image pinned by digest in `image` directly, for an image the module does not copy; a container stays up unless it says `run-once` or `schedule` | the node-engine, on the container runtime the module's `capabilities` name |
| **a host program from the operating system** | a `package` resource (`package`, `absent`) and a `service` resource per unit it ships (`unit`, `state`, `boot`, `scope`, `restart-on`, `reload-on`, `health`); never an `action` | the node's package manager and service manager |
| **files** | `file` and `directory` resources (a directory by `place` or `path`), an `archive` resource unpacking an `archive` artifact, template files in the module's `templates` directory | the node-engine and the provisioner client |
**A runtime of its own.** A module that says `runs-as` names an account it makes (a `user` resource of that name,
never root nor the operator's); its tools bundle is then served by a runtime of its own, as that account, on a bus
account of its own (ADR 0259 §8), whose readiness is the manifest's `runtime-health` (ADR 0366). Its secrets belong to
that account (`secrets-owner`), and its bus account's secret is the own secret `broker`. A tools bundle that holds an
account of its own instead says `bus` on its artifact, a Go bundle only, with the own secret `broker-<binary>` in a
directory the runtime's account may pass (ADR 0564).
**What it keeps on the bus.** Kept data is asked for in `requires`, each entry in its long form: `name` (a local name,
lower-case letters, digits and hyphens), `storage` (`key-value`, `stream`, `object-store` or `ledger`), `template`
(`state`, `events`, `work-queue`, `log`, `objects`, `ledger`) and its `options`, or a bare stream's `configuration`
(ADR 0595 §1 to §3). The manifest validator holds each option to its bound. The **old sections** `state`, `logs`,
`streams` and `objects` are kept by the parts the bus module needs before it can answer, and by no new entry
anywhere else: those parts ask nothing by template, and any other module's use of an old section is refused once no
accepted module outside them uses that section any more (ADR 0619 §1, ADR 0595 §11). One local name is in one form.
A module reads another module's data by asking its tools or seat verbs, never by its bus state (ADR 0201, as changed
by ADR 0596).
**What it says and hears.** `emits` names its events locally; `consumes` names `<emitter>.<event>`, with `*` for one
name and `**` for the rest; `invokes` names each tool `<module>.<tool>` or verb `seat:<seat>.<verb>` it calls, or `*`,
and an entry's long form `{verb, fields}` names the fields of the answer it relies on (ADR 0152); `uses` names the
seats it sends to; `needs-approval` marks a tool, verb or event that waits for the operator's approval.
**What someone may change.** Each config value a module reads is declared once under `config` (or its alias
`settings`, never both), with its type, its default where it is a preference, and whether it is trusted; a file takes
it by `takes-config` or a template file's `${config:<key>}` (ADR 0262, ADR 0348).
*How it is checked:* the manifest validator's tests (mesh-delivery modules/mesh-delivery-indexer/judge) refuse each
form above that a manifest gets wrong, by name; the builder's plan test refuses a kind or language no build module
builds; the node-engine's declaration tests refuse a field a resource type does not use; and the showcase module in
mesh-catalog declares one part of each kind its language allows and composes on a node.
## The showcase, worked
The catalog's `showcase` module is the worked example: **one TypeScript bundle, several entrypoints, every mode**.
| part | artifact | resource | its words |
|---|---|---|---|
| the module's code | `code`, a `bundle` in TypeScript, `entrypoints` for the tools, the provisioner, the service, the run-once step and the report | — | — |
| a run-once step | `code` | `migrate`, a `process` | `run` the step's entrypoint, `run-once` true, `env-file` |
| the service | `code` | `server`, a `process` | `run` the service's entrypoint, `user` the module's account, `env-file`, `restart-on` its settings file |
| a scheduled report | `code` | `reporting`, a `process` | `run` the report's entrypoint, `schedule` a five-field cron |
| a container from somebody else's image | `helper`, an `upstream` pinned by digest | `tools`, a `container` | `network`, `volumes` mounting its broker secret, `args` |
| packed files | `files`, an `archive` from a directory | `packed`, an `archive` | `path`, `unknown` |
| its settings file | `templates`, the module's templates directory | `settings`, a `file` | `path`, `mode`, `template` |
| a seat it defines for itself | — | — | `seats`, and `claims` of it |
Nothing in it says how any part is hosted beyond its resource type — that is the mesh's: the builder picks the
toolchain from the language, compiles each artifact alone and publishes it, the assigner places the module, and the
node-engine writes the units. **A module in several languages is several bundles**, each naming its own language and
compiled alone; each language is one more build module the mesh must have, and one more set of dependencies to keep
current. The reason to use two is that one genuinely suits a part better, not that you can.
### Its code, in each language's own terms
Each part is an ordinary project in its language, depending on the mesh's packages the way it would depend on
anything, resolved from the mesh's own package registries, which are the ordinary registries for each ecosystem
hosted by the mesh (the registry seats, ADR 0605): `npm install` and `go mod tidy` work, and a developer's laptop
resolves what a build does. **A Go module requires a mesh package at the zero version only**: the builder builds
against the newest version of the package series the manifest's `packages` accepts (ADR 0573 §2, §4, ADR 0606), so no
member of a change names another's version.
Each part uses only the part of the protocol it needs: the event consumer never learns what a grant is, and a run-once
step emits only.
## Publishing a package is a capability
A module may publish libraries as well as run code. **The SDK is not special; it is simply the first
module that did this**, and its own consumers are the mesh's modules.
```json
{
"module": "plex",
"build": { "artifacts": [
{ "name": "server", "kind": "upstream", "from": "plexinc/pms-docker@sha256:…" },
{ "name": "client-ts", "kind": "package", "language": "typescript", "from": "clients/typescript" },
{ "name": "client-rust", "kind": "package", "language": "rust", "from": "clients/rust" },
{ "name": "client-py", "kind": "package", "language": "python", "from": "clients/python" }
]}
}
```
A `package` artifact is built and **published to the mesh's registry for that ecosystem**, under the
version its own project file declares. Another module then depends on it the ordinary way:
```
"@novox/plex-client": "^2.0.0"
```
A `package` artifact, in Go or TypeScript, is built from the trunk and **published to the mesh's registry seat for its
language** under its next version, `<series>.<n>`, which the builder counts (ADR 0606 §1 to §3); a branch's build is a
tagged version, taken only by a build that asks its tag (ADR 0607). Another module then depends on it the ordinary
way, accepting a package series by its manifest's `packages`.
**Why this belongs to modules rather than being a separate thing.** A client for a piece of software
changes when that software changes, and the module that owns the software is the only thing that
knows. Putting the Plex client anywhere else is the shared-library disease with extra steps.
### What this leaves open
- **Which private registry.** That there *is* one is settled —
ADR 0014 says each module consumes its dependencies
from the private registry, the mesh's own shared library included. Which software serves it is
not: the catalogue holds `verdaccio`, and a git host usually serves package registries too.
- **And the bootstrap does not have one.** ADR 0014 assumes a registry exists; on a fresh mesh
nothing has installed one when the first SDK is built. That is the same pivot as everything else
and it has not been designed.
- **Who may publish.** A builder pushing a package needs an account on that registry, which is a
credential in the bootstrap path and does not exist yet.
- **Versions and ranges.** Everything else the mesh delivers is pinned by digest, and a range is
resolved at build time from whatever the registry holds. A lock file records what was chosen, and
the builder records that as the build edge — but *a mesh that can rebuild a commit and get a
different library* is a real change from how everything else here works, and it should be a
decision rather than a consequence.
What design 20 once left open here is decided: the registries are the registry seats (ADR 0605), who publishes is the
build module of the package's language, and a build resolves a series, never a range, recorded as its build edge
(ADR 0606, ADR 0573).
## A file and the thing that reads it are guarded by a gate
@@ -189,47 +266,59 @@ knows. Putting the Plex client anywhere else is the shared-library disease with
The apply is not a transaction: every resource is attempted, every failure reported, and only a
failed gate stops what follows it (ADR 0053).
So a push can half-happen, and the pairing that matters is a configuration file beside the
So a send can half-happen, and the pairing that matters is a configuration file beside the
service that reads it. A module keeps that pair correct by declaring them in this order: the file;
a `run-once` container that validates it; the service, with `restart-on` naming the file. A file
the validator refuses reaches the disk and nothing else — the gate fails, the service after it is
left as it was, and the machine reports the push failed. The service never serves what the
validator refused. That is the grouping, made from what the manifest already has; no primitive
a run-once step (a `run-once` container or process) that validates it; the service, with `restart-on` naming the
file. A file the validating step refuses reaches the disk and nothing else — the gate fails, the service after it is
left as it was, and the machine reports the send failed. The service never serves what the
step refused. That is the grouping, made from what the manifest already has; no primitive
withholds a file from the disk, and a service that reads its file live rather than at start is the
one shape this does not protect, and should not be written.
*How it is checked:* the lab's coupled-pair spike declares exactly this pair, pushes a refused
*How it is checked:* the lab's coupled-pair spike declares exactly this pair, sends a refused
file, and asserts the file on disk is the new one, the service serves the old one, and the machine
reports the push failed.
reports the send failed.
A `run-once` step may itself name what it reads under `restart-on`; for a step the word means
A run-once step may itself name what it reads under `restart-on`; for a run-once step the word means
*run again* — a step that fetches a fact from a provider names the binding it reads, and runs
again when the provider moved. The service that consumes what the step made names the step, so it
is recreated with the new fact
(ADR 0099). *How it is
checked:* the node-engine's unit tests run a step again when its named file changed and not otherwise,
and recreate a container naming a step after the step ran.
checked:* the node-engine's unit tests run a run-once step again when its named file changed and not otherwise,
and recreate a container naming a run-once step after it ran.
## A recurring step may hold its own module's containers still
## A scheduled run may hold its own module's containers still
*Written 2026-10-02, from ADR 0189
and [issue 108](../../04-ISSUES/108-the-registry-has-no-garbage-collection-once-it-has-two-doors/00-report.md).*
Some work cannot be done underneath a running service: an artifact store's collector rollouts the
storage and requires every writer stopped. A `run-once` step runs *beside* containers and a
scheduled one is the same container again, so until this a module had no way to say it — and the
storage and requires every writer stopped. A run-once step runs *beside* containers and a
scheduled run is the same container again, so until this a module had no way to say it — and the
mesh inherited a store that has never collected anything, because the predecessor said it with a
shell script and a script beside a module is not a resource in it.
A scheduled step may name `while-stopped`: resource ids of **its own module's** containers, which
the node-engine stops before the run and starts again after it, in the reverse order, **whatever the step
A scheduled container may name `while-stopped`: resource ids of **its own module's** containers, which
the node-engine stops before the run and starts again after it, in the reverse order, **whatever the run
did**. Three boundaries, each refused where it can be seen earliest — its own module's containers
only, because a module that could quiesce a neighbour could stop the mesh; scheduled steps only,
because at apply the declaration is applied in order and a step already gates what follows, so a
only, because a module that could quiesce a neighbour could stop the mesh; scheduled runs only,
because at apply the declaration is applied in order and a run-once step already gates what follows, so a
one-time offline job says *before* rather than *instead of*; and restoring that is not conditional
on anything, because the only real risk of the field is a window that never closes.
*How it is checked:* the node-engine's unit tests assert stop–run–start in that order, the restart after a
step that **failed**, the reverse order for several containers, and a service left down said
loudly. The controller refuses, from the definition alone, a window with no schedule, one on a
run-once step, one naming a container the module does not declare, and one naming itself.
run that **failed**, the reverse order for several containers, and a service left down said
loudly. The manifest validator refuses, from the manifest alone, a window with no schedule, one on a
run-once container, one naming a container the module does not declare, and one naming itself.
## Acceptance
- A manifest written by this design's tables is accepted by the manifest validator, built by the builder and composed
by the node-engine for a machine, with no form one of them refuses: checked by the showcase module's check on every
change to it.
- Every refusal names the module, the part and the field, where its author reads it: the change's check.
## Milestones
None of its own. This design is the guide every module's change follows; a change to what a manifest may say is a
decision and a change of the manifest validator, the builder or the node-engine first, and this design follows it.
@@ -65,14 +65,14 @@ and waits — which looks exactly like a builder with no work.
**Genesis ends with a mesh that runs, which is not the same as a mesh that works.** It has a controller, a store, a broker, a registry and a builder. It holds no module graph, has no private
network, no packet filter, and cannot resolve a name. Calling that "installed" is what let the
catalogue be missing from a test for weeks without anything complaining.
catalog be missing from a test for weeks without anything complaining.
| # | step | what happens | why it is here |
|---|---|---|---|
| 13 | the shared base is built | the toolchain and runtime every module with code of its own stands on | nothing else with code can be built until it exists |
| 14 | a store module is built and run | a database **provider**, which the foundation's store is not | the foundation's store is the controller's own memory, and offers nothing to anything |
| 15 | the catalogue is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt |
| 16 | the catalogue asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalogue itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) |
| 15 | the catalog is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt |
| 16 | the catalog asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalog itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) |
| 17 | the controller is rebuilt from its own repository | and rolled out through the module path | the moment the mesh stops depending on the installer for anything |
| 18 | `mesh-wireguard` is assigned **and the node placed** | a private network | assigning installs the module; placing says where this machine is on it. Both, or the machine has no peers. Assigned by its own name since ADR 0226 retired the `networking` bundle |
| 19 | the packet filter is assigned | rules generated from what modules declared | until this, every rule the mesh computes has never been applied to anything |
+2 -2
View File
@@ -44,7 +44,7 @@ after the phases that change its build path are in — not before.
**Why here.** The Go controller and the TypeScript SDK disagree about what a grant carries
(`consumer` is the module in one, the node in the other). That is exercised by the installer's own
provisioning — the catalogue's database — so it belongs before more is built on it.
provisioning — the catalog's database — so it belongs before more is built on it.
- [x] 1.1 the drift was not live — inspection showed the wire agrees (contributions file; envelope
required headers). The dead types that disagreed are removed, ADR 0074 and doc 19 corrected
@@ -91,7 +91,7 @@ a protocol that agrees, a registry to publish to. Issue 051.
the controller's records and the database of each module that asks for one. *(Adopted in place: the module's
server names the same container and the same pinned upstream image the foundation runs, so the
applier reconciles it rather than raising a second postgres. Proven 22/22 in the one-node lab —
the catalogue, a `postgres-database` consumer, gets its database from it. Follow-up: the store
the catalog, a `postgres-database` consumer, gets its database from it. Follow-up: the store
binds `0.0.0.0` from genesis so a mesh consumer can reach it, but the packet filter is
installed later — a brief pre-filter window where `mesh-store` is open before `from: mesh`
clamps it; bring the filter up earlier or bind narrower at genesis.)*
+1 -1
View File
@@ -11,7 +11,7 @@ decisions:
# 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.
**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
(ADR 0027):
+2 -2
View File
@@ -185,7 +185,7 @@ recovery addresses both alike.
### A provider with one credential
*Decided 2026-10-01 (ADR 0158); the controller's half built the same day (mesh-controller PR 184): the offer's word, the need carrying the shared secret's name, the vault's one value under one generation stamp, remade for every holder on a later binding or a rotation, the rotate command sending every holder. What remains is each provider's definition saying `credential` and `taken`, with a start that applies the file — the media catalogue's work.*
*Decided 2026-10-01 (ADR 0158); the controller's half built the same day (mesh-controller PR 184): the offer's word, the need carrying the shared secret's name, the vault's one value under one generation stamp, remade for every holder on a later binding or a rotation, the rotate command sending every holder. What remains is each provider's definition saying `credential` and `taken`, with a start that applies the file — the media catalog's work.*
Software that holds one credential — a download client's web password, an indexer's one API key —
cannot give each consumer a login, so ADR 0048's form does not fit it and its values were accepted
@@ -202,7 +202,7 @@ acceptance. *How it is checked:* the rows of ADR 0158's table; the controller's
### A credential the provider's software issued
*Decided 2026-10-12 (ADR 0305, issue 526), accepted on the operator's approval the same day; built on
branches in the controller and the media catalogue.*
branches in the controller and the media catalog.*
Some software is given its one credential rather than told it: a media server signs in to its vendor and
keeps the token the vendor issued. The mesh can neither make nor rotate such a value, and a copy typed for
@@ -0,0 +1,181 @@
---
layer: to-be
design: 25
milestone: bindings-first-view
updated: 2026-11-11
code: [mesh-contracts seats/mesh-broker, mesh-sdk go/seatlist, mesh-catalog modules/nats, mesh-delivery modules/mesh-grants]
decisions:
- "ADR 0595"
- "ADR 0615"
- "ADR 0621"
- "ADR 0596"
---
# Design 25, milestone bindings-first-view — the grants part asks the bus module for every binding
**What it builds.** The grants part writes a module's binding (the names on the bus of its bus-state entries) into
its membership when it hears the bus module's `created`, rewrites it on `recreated` and removes it on `dropped`
(design 25 bus-state §4; ADR 0595 §1, §7). Until now those live events were the binding's only source, so an event
the grants part never heard lost the binding for good: on 2026-11-11 a registry holder's membership lacked three of
the entries its manifest asks for, although the bus held all three, and about 190 publishes since 2026-11-09 were
never said `published` (issue 1966). This milestone gives the grants part a first view of every binding from the
owner of that data, the seat `mesh-broker`, at every start and when asked again by hand, so a missed event is
recovered with nobody's hand. The live events stay the way a binding changes between starts.
**Where it comes from.** The operator, 2026-11-11 (about 03:35 CET, in the terminal), chose "Ask the bus module":
at every start, and on a re-ask, the grants part takes a first view from the bus module's seat of the entries it
holds and composes the bindings from it; the owner answers for its own data. Within that answer, these lines follow
ADR 0595 §1 and §7 (the binding is the bus module's to say and the grants part's to write), ADR 0596 §2 (another
module's data is asked through its seat, never read from its bus state) and the first-view rules of ADR 0615 §3 to
§6, which decide them for the indexer's and the assigner's state and which this milestone applies, as the design's
choice, to the bus module's. Each choice below says so. The operator, 2026-11-11 13:30 CET (in the terminal, to lead-47), asked whether
the grants part should take a bucket's binding from the bus module's existing `list` answer when the new field
`binding` is missing, to break the circle §7 names, answered "Yes, build it": §9.
**Cited as** "design 25 bindings-first-view §n". It amends design 25 bus-state §4, which says so.
## The lines
- §1 **The first view is the seat `mesh-broker`'s `list`, asked with no `module`.** No new verb: `list` already
answers every entry the bus module created, from its own state `asked`, by module and local name, and lacks only
the binding (§2); a second verb answering the same entries would be a second form of one fact. The grants part
declares `seat:mesh-broker.list` in its manifest's `invokes`, and composes its own grant of it as it composes any
invoker's. `list` names no class, so from milestone G of design 47 it is a `read` and takes that class's callers,
invokers, persons and sessions (design 47 G §391); until then its callers are "any caller whose role grants it",
which the grants part's invoke grant meets. The verb stays answered where the bus module's tools answer it today,
by its tools bundle in the tool runner of the machine holding the seat, and gains no runtime of its own: ADR 0621
§1 gives the indexer's and the assigner's first views a runtime of their own so that the tool runner never asks
through itself, and here the asker is the grants part, which composes every account without this answer (§5), so
no part waits on itself. Why: the design's choice under the operator's answer and ADR 0596 §2. How it is checked:
§8, the whole case.
- §2 **Each entry of `list`'s answer carries its binding.** The seat's `Held` gains the field `binding`, of the
seat's own form `Binding`, the same value the bus module's `created` and `recreated` carry for that entry in their
field `binding`, taken from the entry's plan in the bus module's state `asked`. **`Binding` is reshaped to the form
on the wire.** Today the seat's definition says `Binding` is a bus name, a storage type, a template and a stream's
subjects, while the bus module sends, and the grants part needs, mesh-sdk's membership binding: `local` (the local
name), `storage`, `template`, `stream` and `subject` (a stream's, and a ledger's history's subject), `bucket`,
`object-store`, and for a ledger `history`, `state`, `indexes` (each index's bucket by its name), `snapshot` and
`types`, which `created` and `recreated` carry and only `dropped` clears (corrected 2026-11-11, develop question D-11 of design 47's milestone G: an earlier wording had the bus module's events leave them empty). The
definition's `Binding` takes exactly those fields, with those names and forms, so `created`, `recreated` and
`list`'s `binding` carry one form, the generated contract decodes what the bus module sends, and no byte changes
on the wire; the fields design 47 G §399 adds come on top of it. Why: the design's choice following this line's
own words (one value in the events and the answer), met by the developer on 2026-11-11 and directed by lead-47. It is present on every entry whose objects the bus holds (the
entries whose answer carries `settings`) and absent on one it does not hold (waiting to be made, or refused), as a
refused create hands no binding (design 25 bus-state §4). `show` and `orphans`, which answer `Held` too, carry it
alike. A field G's part adds to `Binding` (design 47 G §399: `objects` and `sent-objects`) reaches `list`'s answer
with it, since the form is one. What changes: the seat's definition in mesh-contracts (`seats/mesh-broker`), its
generated Go contract, and mesh-sdk's bootstrap copy of the definition in `go/seatlist`, which its test against
mesh-contracts holds equal; the bus module's tools fill the field. **The size**: 18 entries in the new form are
asked by the manifests on main (counted 2026-11-11), so the answer is a few KiB, an estimate; it stays one reply
well under the bus's largest message (1 MiB) while the entries number in the hundreds, and from G a reply over that
limit goes as an object by design 47 G §407. Why: the design's choice. How it is checked: unit (mesh-contracts' generated contract), a
`created` body as the bus module's keeper writes it, a ledger's among them, decodes into the generated type and
encodes back to the same bytes; unit (the bus module's
tools), a row where `list`'s `binding` for an entry equals the `binding` its `created` carried, and a row where an
entry not held has none; the seat's fixtures.
- §3 **It is asked at every start and when asked again by hand, never on a timer.** The grants part's process asks
`list` once at each start, as it asks its other first views (design 25 bus-state §5), and again when a person
calls `grants_first_view_again` with `state` `bindings`, or with no `state`, which asks every first view (§6).
Nothing else asks it: no timer, and no event, since the bus module's events carry the indexer's revision of the
verdict they acted on and no revision of the bus module's own, so a gap in them cannot be seen (ADR 0615 §5 asks
again on a gap only where one can). **At a start the answer comes first**: the grants part takes no event from its
durable consumer of `mesh-broker.created`, `recreated` and `dropped` until the answer is applied, or until the
bound of §5 passes. Since the shared stream keeps the events in order and each event sets or removes one entry's
whole binding, the events held then, taken after the answer, leave each binding as the last word on it said,
whichever came later. Why: the design's choice under ADR 0615 §4 to §6. How it is checked: §8.
- §4 **How the answer is applied.** For each entry of the answer that carries a binding, the grants part writes the
binding into the module's membership on every machine the module is assigned, kept in its state `memberships` as
on `created`, where it holds none, and rewrites it, as on `recreated`, where it holds a different one; where it
holds the same, it writes nothing. Each binding written or rewritten is said in its log, naming the module, the
local name and the first view as the cause, and recomposes that module's memberships as an event's would. An entry
listed without a binding (§2) changes nothing the grants part holds for it, but for an entry the bus holds in an
answer of a bus module's build from before this milestone, which carries no `binding` on any entry: §9 composes
that binding, and it is then applied by this line as one the answer carried. **A binding the bus no longer
lists is kept, never removed by a first view**: the grants part removes a binding only on `dropped` (ADR 0595 §1,
§7), so a bus module whose own state `asked` was lost or rebuilt, and answers few entries or none, strips no
module of its data. Instead it raises the condition `mesh-grants.binding-unlisted`, one per such binding, naming
the module and the local name and the operator's step (read the entry with the seat's `show`; an entry its
manifest no longer asks for leaves by the bus module's `drop`, whose `dropped` removes the binding); a later answer
that lists the entry, or a `dropped` for it, clears it. **On a re-ask the events keep flowing**: an answer is
applied only to entries no `created`, `recreated` or `dropped` named since that ask was sent, so an answer never
writes over a newer event. Why: the design's choice under ADR 0595 §7, and ADR 0615 §6's rule that a reader keeps
what it holds rather than taking an empty answer as the truth. How it is checked: §8.
- §5 **While the bus module does not answer, the grants part composes the rest and says so.** It composes every
account and keeps every binding it holds in `memberships`, as it does today; only a binding it never heard waits.
Past two minutes without an answer, the bound of its other first views (design 25 bus-state §5), it raises its
condition `mesh-grants.first-view-unanswered` naming the seat `mesh-broker` and the verb `list`, as that condition
names the owner and the tool of its other first views, and it then takes the events from its durable consumer as
they come, so the live path never waits on the first view. It keeps asking, from 10 seconds, doubling, at most
every 5 minutes, the backoff the bus module keeps for its own first view (design 25 bus-state §1); the answer
clears the condition. This form holds until milestone G of design 47 is delivered; from then the grants part, one
of G's observers (design 47 G §387), writes `first-view-unanswered` and `binding-unlisted` (§4) as findings in its
state `findings`, said as `finding-written` and `finding-cleared` on its finding stream (design 47 G §383) and
stated at its start (§384), naming the same things. Why: the design's choice under ADR 0615 §6. How it is checked:
§8; from G, G's own checks of §383 and §387.
- §6 **The tools that read and steer it.** `grants_first_views` shows the first view `bindings` beside the others:
the seat and verb (`mesh-broker.list`), when it was asked and answered, how many entries the answer listed, how
many bindings it wrote and rewrote, how many of those it composed by §9 and the entries §9 left unbound (by module,
local name and storage type), the bindings it found unlisted, how many asks and hand acts, and whether its
condition is raised, kept in the grants part's own state `user-list` under the key `first-views` as the others are.
`grants_first_view_again` takes `bindings` among its `state` values, by a person's act with why, recorded as a hand
act; its description names it. From G these two tools are served by mesh-sdk's `go/views` as every part's are
(design 47 G §245), with `bindings` among the first views they show. Why: tools-are-how-a-module-is-spoken-to. How
it is checked: unit (grants part's tools), `bindings` shown and asked again with its hand act recorded.
- §7 **What exists today, and its fate.** The live-event path stays whole (design 25 bus-state §4). The bus module's
`list`, `show` and `orphans` keep every field they answer and gain `binding`. The grants part reads no other
module's bus state and gains no grant on the shared stream `EVENTS` beyond its durable consumer of the three events:
a missed event is recovered by asking its owner, never by reading the stream back by hand. **The order, and what
the first wording missed.** This line first said the order between the repositories needs no edge of the module
graph, because a grants part meeting an answer without `binding` wrote nothing from it and kept working on the
events. It missed that the bus module's own build goes through the registry (corrected 2026-11-11, issue 1966): its
image build asks the seat `container-image-registry`'s `mirror` for its base image, which the registry holder
serves only with its state `mirrors`, a binding whose `created` the grants part never heard; so the bus module's
new build could not be built while the grants part waited for that build's `binding`, a circle. §9 breaks it: the
grants part composes the key-value and object-store bindings from the running bus module's answer, so it reaches
the machines in a delivery of the grants part (mesh-delivery) alone, with no new build of the bus module (the
operator, 2026-11-11 13:30 CET, in the terminal: "Yes, build it"). The order is then the grants part with §9
before the bus module's build of this milestone, which mesh-contracts, mesh-sdk and the nats module (mesh-catalog)
carry; the edges between the grants part and the bus module otherwise lie on the foundation's cycle, where ADR 0615
§2 has the owner's answer and the reader's wait carry the order.
- §8 **How it is checked.** **Whole**: a case in the grants part's existing whole-module integration test (mesh-delivery
is over its integration budget, so no new test function): a fixture bus module answers `mesh-broker.list` with an
entry whose `created` was never said, and after the grants part starts, the module's membership holds that
entry's binding; a binding the grants part holds that the answer does not list stays and raises
`mesh-grants.binding-unlisted`; with `list` unanswered, the part composes every account, raises
`mesh-grants.first-view-unanswered` naming `mesh-broker` and `list` past its bound, and applies a `created` heard
meanwhile; an answer that comes after a `dropped` heard since the ask does not write the dropped binding back.
**Unit**: the bus module's tools (§2), the grants part's tools (§6), and a table of the grants part's reconcile
with one row per case of §4. **Live**: after the delivery, at the grants part's first start, the registry holder's
membership names every entry its manifest asks for (`grants_membership`), and `grants_first_views` shows
`bindings` answered with no binding unlisted. **§9's cases**: unit rows in the same reconcile table, one per
case of §9 (a key-value and an object-store entry composed; a stream and a ledger entry left unbound and logged;
an entry without `settings` left unbound; an entry whose `settings` lack the bus object named from its `bus-name`
left unbound and logged; an answer carrying `binding` taken as it is, nothing composed). **§9 live**: after the
delivery of the grants part alone, before any new build of the bus module, `grants_membership` for the registry
holder on the machine it runs on lists `holds`, `windows` and `mirrors` beside the entries it held, and
`grants_first_views` shows `bindings` answered with those three composed by §9.
- §9 **An answer without `binding` is composed from the bus module's own fields, for the storage types it names
whole.** A bus module's build from before this milestone answers `list` with no `binding` on any entry; each entry
still carries its `module`, `local-name`, `bus-name`, `storage-type`, `template`, `options`, `fill`, and `settings`
keyed by the bus object's name (a key-value bucket's stream as `KV_` and its bus name, an object store's as `OBJ_`
and its bus name, a stream by its own name). For an entry of such an answer the grants part composes the binding
only when its `settings` hold the bus object its `bus-name` names (so the bus holds it, as §2 has `binding`
present only on a held entry), and only for the storage types whose binding the answer names whole: for
`key-value`, `local` from `local-name`, `storage` from `storage-type`, `template` from `template` and `bucket` from
`bus-name`; for `object-store`, the same with `object-store` from `bus-name`. It composes nothing for a `stream`
(the answer does not carry its subjects) or a `ledger` (it does not carry its parts), nor for an entry whose
`settings` lack its bus object: those are left as today, unbound until the bus module's `created` or `recreated`,
or a later answer that carries `binding`. An answer is taken as one without `binding` only when no entry in it
carries the field; an answer where any entry carries it is applied by §4 alone, nothing composed. **It is the bus
module's own word**: every field the binding takes is the bus module's answer about what it holds, from its seat,
and the grants part adds no name and reads no bus state of its own, so ADR 0595 §1 and §7 (the binding is the bus
module's to say and the grants part's to write) and ADR 0596 §2 hold. **How it is said**: once per answer, a log
line of the grants part naming the seat `mesh-broker` and the verb `list`, that the answer carries no `binding`,
how many bindings it composed and every entry it left unbound by module, local name and storage type; each
binding then written is logged by §4 with the cause "first view, composed from `list`'s fields". `grants_first_views`
shows the same (§6). **Its fate**: it stays after the bus module's build of this milestone is everywhere, since it
acts only on an answer no entry of which carries `binding`, which that build never gives, and covers a bus module
held back or set back to an earlier build. Why: the operator's answer of 2026-11-11 13:30 CET (in the terminal,
to lead-47, for issue 1966), under ADR 0595 §1 and §7; the fields, the storage types and the log are the design's
choice following that answer and the bus module's `list` answer as it stands today (issue 1966, read live
2026-11-11). How it is checked: §8, §9's cases and §9 live.
@@ -0,0 +1,406 @@
---
layer: to-be
design: 25
milestone: bus-state
updated: 2026-11-11
code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog modules/nats]
decisions:
- "ADR 0595"
- "ADR 0596"
- "ADR 0597"
- "ADR 0259"
- "ADR 0363"
- "ADR 0386"
- "ADR 0387"
- "ADR 0436"
- "ADR 0564"
- "ADR 0578"
- "ADR 0603"
- "ADR 0612"
- "ADR 0613"
- "ADR 0614"
- "ADR 0615"
- "ADR 0619"
- "ADR 0620"
- "ADR 0621"
- "ADR 0622"
- "ADR 0625"
- "ADR 0626"
- "ADR 0627"
- "ADR 0631"
---
# Design 25, milestone bus-state — what a module keeps on the bus, created by the bus module
**What it builds.** The lines of [design 25](../25-the-bus-on-nats.md) § 12 that are built under issue 1966: who
creates a module's bus state and how the module receives its binding (§ 12.2), the seat `mesh-broker`'s verbs,
events and fill (§ 12.3), the ledger (§ 12.4), the calls (§ 12.5) and the order of the build and the move (§ 12.6).
The three layers and the templates are the design's main file (§ 12.1), and so is how each rule is checked
(§ 12.7). The sections keep their numbers, so "design 25 § 12.2" still names them. *Moved here from the
main file on 2026-11-11, when the main file was put in the shape of playbook 02, and numbered; their words are
unchanged but for the note on bus-state §4 that design 25 bindings-first-view amends it.*
**How a line is cited.** Each statement is cited as "design 25 bus-state §n". A bare "§n" inside a statement is a
clause of the record that statement cites, as it was before the move; "§ 12.n" is a section of this design.
### 12.2 Who creates it, and how a module receives it
- §1 **The trigger is the manifest validator's verdict** (ADR 0595 §4), heard as the indexer's event `manifest-accepted`, which
carries the module's entries whole, so the bus module never reads the indexer's state and never polls. The indexer
says it for every newly accepted manifest, with its entries empty when it asks for none, and the bus module reads an
empty one as "this module asks nothing now", which is what its orphans need. A module whose manifest leaves the
trunk (its directory deleted) gets an empty `manifest-accepted` too, so its entries reach `orphans` and raise
`orphaned`; a manifest the manifest validator set aside to wait gets none, so its entries stay asked. The indexer's tool
`indexer_say_accepted` says `manifest-accepted` again for one module by hand, from the verdict it holds, recorded as a
hand act, for a verdict the bus module is thought to have missed. It goes, as every event of the
indexer, on the module events stream (ADR 0363) under the indexer's own subjects. A publish of it that fails is
published again, and one still failing writes the indexer's finding naming the module, cleared once said (today
the indexer only logs it). It hears the verdicts on a durable consumer, so one given while it was down is heard
when it is back. **Its first view** at start is the indexer's
tool `indexer_bus_state` (every accepted manifest's bus-state entries, by module and digest), asked once; the
events keep it current after that, and a stream that keeps a verdict only for its age is never its source. A
verdict replayed from its consumer is applied only if newer than what the first view answered for that module, by
the indexer's revision alone (a digest has no order), so a replay never takes an entry back. An `indexer_bus_state`
that does not answer makes the bus module's health unhealthy, naming the verb, and is asked again on a bounded
backoff (from 10 seconds, doubling, at most every 5 minutes); nothing is created or orphaned meanwhile. Beside the
backoff, the bus module's tool `nats_first_view_again` asks `indexer_bus_state` at once by hand, recorded as a hand
act. It keeps
what it last heard per module in its own state `asked` (an old `state` section, its own kept data staying there,
ADR 0595 §10), from which `list`, `check` and `orphans` answer; a key goes only on an empty `manifest-accepted` for
the module, never because a manifest the manifest validator set aside to wait is missing from the first view. Then it runs `check` over what it holds, creating what
is missing. The manifest validator checks every entry once, at the merge check and at take-in (ADR 0578; ADR 0595 §8), and also
each ledger type's operations: a set-entry or a remove names `into` and `key`, `fields` and an index's `on` name
fields of the schema, and `digest-of` names a text field.
- §2 **A module that asks bus state waits for the bus module** (ADR 0614 §5). Each bus-state entry of a manifest is a
requirement on the provision `bus-state`, which the bus module's manifest provides; the module graph reads it as any
requirement, so the assigner holds a consumer's new build until a build of the bus module that offers `bus-state`
runs healthy (ADR 0603 §5). That each entry exists before its user starts is the bus module's own work, above, not
an edge.
- §3 **The bus module has a process of its own** beside its tool bundle, with a declared health check. Its credential
creates streams and buckets under modules' names only, and deletes only through `drop` and `recreate`. Its health
turns unhealthy, naming the cause, when an accepted verdict has waited on its durable consumer, unanswered, for
more than three minutes, or when its last pass over fill, which runs every minute, is more than three minutes old, so the only guard against a store filling never stops without a sound; another module's entry past the raise mark
raises that module's condition and leaves the bus module healthy. **Its process has a process account of its own,
granted more than any other process's** (ADR 0626 §1; ADR 0436 §1, §2). The process declares `bus` as any process
may, with one key more, `keeps`, which is allowed on this process alone; the grants part composes the account from
that declaration as it composes every process account, and the vault mints it as the module's own secret. Beyond
what a process's `bus` grants, `keeps` grants the bus's create and update subjects for streams, buckets and object
stores under modules' names only (`<module>_*`), the snapshot request for an entry before `recreate` or `drop` with
its chunks' acknowledgement subjects and its own inbox, and the delete subjects for those two acts. It is granted
no subject on which a tool or a seat's verb is answered (ADR 0436 §3), and no subscription to any restricted
stream's own subjects: the snapshot is the one way a restricted stream's data reaches it, kept only in the bus's
snapshot store, and the process reads no message of it. No other account holds those subjects, the tool runner's
included. **The snapshot account stays as it is** (ADR 0626 §2, § 3 above). **The manifest validator refuses any other bus
declaration of the bus module** (ADR 0626 §3): anything it would say or hear on the bus beyond its process's `bus`
and the bus state the foundation keeps on the old sections (ADR 0595 §10), `keeps` on any other process or module,
and a `bus` of its process that names a subject on which a tool or a verb is answered.
- §4 **The binding travels on the bus module's events** (ADR 0595 §1, §7, as their progressive insight of 2026-11-03
has it, waiting for the operator's Approve). The grants part, which issues every membership, hears them on a durable
consumer: it writes the **binding** (the names on the bus) into the module's membership on `created`, on every
machine the module is assigned, rewrites it on `recreated` and removes it on `dropped`, kept in its own state
`memberships`, so no part writes another's state and a binding outlives the 7 days its event is kept on the shared stream (§ 12.3).
*Amended 2026-11-11 by design 25 bindings-first-view §1 to §5*: the events are no longer the binding's only
source; at every start, and when asked again by hand, the grants part also takes a first view of every binding
from the seat `mesh-broker`'s `list`, so a missed event loses no binding for good. A module whose binding is missing at start fails loudly, naming the entry; it never starts blind. A `refused`
create hands no binding. **A refused or overdue creation is loud**: overdue means no `created` or `refused` within
five minutes of an accepted verdict that asks for the entry. The condition is raised by the watchman, which today raises the manifest validator's
refusals as findings and gains, through its `consumes`, the accepted verdicts and the seat `mesh-broker`'s `created`
and `refused`; never by the bus module, which cannot raise it while it is down; it names the module
and the entry; a later `created` clears it, and so does a later accepted verdict of that module that no longer asks
for the entry.
- §5 **A foundation part's first view of another's state is a tool its owner answers in a runtime of its own** (ADR 0615,
ADR 0621). The four
foundation parts that read each other (the grants part, the tool runner, the indexer and the assigner) stop
reading the indexer's and the assigner's bus state in the milestone of ADR 0612, with the other 19 readers of that
state (ADR 0615 §1). **Each owner serves them as tools of its own module**, the indexer `manifests`,
`modules`, `verdicts` (ADR 0622 §1) and `graph` (ADR 0631 §1), the assigner `placement`, `seat-holders` and `wants`, and declares `runs-as`, so its tools bundle runs in
a runtime of its own on the module's own bus account, never in the node's tool runner, and the tool runner never
asks through itself (ADR 0621 §1). No new manifest form is added: a process still answers no tool call or seat verb
(ADR 0436 §3). **Each verb is granted to exactly its readers** (ADR 0621 §2, as ADR 0615 §3 lists them): `placement`
to the grants part, the tool runner and the indexer; `seat-holders` to the tool runner; `wants` to the grants part;
`manifests` and `modules` to the grants part, the tool runner and the assigner; `verdicts` to the grants part
alone, its `callers` list holding the grants part only (ADR 0622 §1); `graph` to the assigner alone, its `callers`
list holding the assigner only, the indexer itself refused, and the assigner's `reads` entry of the indexer's
`graph` goes (ADR 0631 §1). These seven are the first-view tools, four of the indexer and three of the assigner.
`verdicts` follows the first-view rules whole (ADR 0622 §2, under ADR 0615 §4 to §6): its answer carries the
revision, the grants part then follows `manifest-accepted` and `manifest-refused` and asks once more on a gap in
their revisions, it raises the condition naming the indexer and `verdicts` past the bound and never reads the
indexer's state, and `grants_first_views` and `grants_first_view_again` cover it. `graph` follows them whole too
(ADR 0631 §2, under ADR 0615 §4 to §6): its answer carries the revision, the assigner then follows the indexer's
`indexer.views` events for the bucket `graph`, which carry the revision, and asks once more on a gap, said in its
log; started while the indexer is down, it raises the condition naming the indexer and `graph` past the bound and
never reads the indexer's state; and `assigner_first_views` and `assigner_first_view_again` cover it. A reader declares each in `invokes`
as `<module>.<tool>` only where it is granted, which is the grant the grants part composes as for any tool, and
each tool checks the proved caller against a `callers` list holding exactly its readers and refuses any other, the
owner itself included. **The manifest validator of the milestone is its own group's** (ADR 0621 §4): since no field is added,
the running manifest validator takes the indexer's and the assigner's manifests in as it does today, and the merge check judges
them with the manifest validator built from the group's own head; no release of the manifest validator goes ahead of the milestone. **The
owner's runtime answers before any first view of its own** (ADR 0621 §3): at once from its module's own state, as
soon as it is connected and before it has asked any other part anything, with the revision of its state it answered at,
so the indexer and the assigner, each asking the other at start, never wait on each other (§4). The reader then
follows the owner's declared events, each carrying the revision of the state it follows, and never asks the verb
again on a timer; a gap in revisions makes it ask once more, said in its log (§5). The owners' events are new
families of their own, apart from the indexer's `manifest-refused` and `manifest-accepted` and the assigner's
`put`: the indexer's `indexer.views` and the assigner's `assigner.views`, one event per changed key with its
bucket, key and revision and the key's value whole, or a delete mark for a key removed, so a reader keeps its
picture from the events alone and asks the verb only on a gap (ADR 0615 §5). **Both families are declared in
their owners' old `streams` sections**, each an entry with `max-age-days` 7 and `max-mib` 256: the old section keeps
a stream by limits, which drops its oldest when full (only a work queue refuses the new instead), so a reader that
misses a dropped event sees the gap in revisions and asks once more. The indexer's own kept data stays on the old
sections (ADR 0595 §10 names it among the foundation); the assigner's goes beside its existing `streams` entry
`placement`, under ADR 0619, which extends ADR 0595 §10's foundation to the parts
the bus module's own composition, placement, build or delivery needs before it can answer: the assigner, the tool
runner, the builder, the checker, mesh-config and the vault. **The size, an estimate**: read on the bus on
2026-11-04, the buckets the readers read hold about 3 MiB of current values at the indexer (its manifests about
5 KiB a value) and about 1.5 MiB at the assigner (its wants about 0.25 KiB a value). The milestone's one mesh-sdk
rebuild rewrites every module's keys, up to three times in its window (design 51 *How many builds*), so a week of
it is some 20 MiB on `indexer.views` and some 10 MiB on `assigner.views` with ordinary merges added, each well
under its 256 MiB. Each event carries one value whole, the largest today a manifest of tens of KiB, under the bus's
largest message, 1 MiB (the bus server's `max_payload`); an owner whose value with its event would pass it emits
the key's revision alone, as a gap, so the reader asks the first view once, and the owner writes its finding
naming the key.
A waiter on an existing event, `manifest-accepted` or the
assigner's `put`, never wakes on them. **When an owner's revisions go back**: should an owner's state be rebuilt
by the bus module's `recreate`, its revisions start again; a reader that hears a revision lower than the one it
holds takes it as a gap and asks the first view again, and keeps what that answer says, whatever revision it held
before. A `recreate` that could not keep the data answers empty at revision 0, so the grants part keeps its list
in force (below): a login revoked meanwhile stays until the owner's first change, and the grants part's
first-view condition stays raised meanwhile, naming the owner, the empty answer and the operator's step: let the
owner write its state again from its sources (the indexer by taking each module in again, `indexer_take_in`; the
assigner by its next composition of the wants), then ask the grants part's first view again,
`grants_first_view_again`. It is cleared by an answer above revision 0. A reader that starts while its owner is down raises a
condition naming the owner and the verb past its bound and keeps asking until it answers, never reading the
owner's state instead; once it had its first view, it keeps working on what it heard (§6). The bound is longer
than an owner's start (two minutes), so a normal start, readers and owners coming up together, notifies nobody;
the owner's answer clears the condition. An answer that is empty at revision 0 (an owner that holds nothing yet)
is a first view of nothing, never a reason for the grants part to drop a login it holds: it keeps the list in
force until an answer at a revision above 0. Each reader's tools say the owner, verb and revision its first view
holds, when it asked and when it was answered (a read tool), and ask the first view again by hand (a re-read tool,
recorded as a hand act): the grants part's `grants_first_views` and `grants_first_view_again`, the tool runner's
`mesh_first_views` and `mesh_first_view_again`, the indexer's `indexer_first_views` and
`indexer_first_view_again`, and the assigner's `assigner_first_views` and `assigner_first_view_again`. The tool
runner's two are tools of the mesh MCP server, beside `mesh_runtimes`, since the tool runner serves its own state
there: the mesh MCP server gains them, and the operator sees them among its tools. **On a fresh mesh** the
assigner starts before any new-form bus state exists and needs no binding of the bus module first: its own state
and `assigner.views` are on its old sections, which the grants part creates, so it answers the indexer, the grants
part and the tool runner their first views while the bus module still waits on it. The order of owner before reader is kept by the module graph's served-verb edge for the 19 readers (ADR 0614 §2), and,
for the four, whose verb edges lie on cycles the graph leaves out (ADR 0614 §3), by the owner's answer at once and
the reader's wait (ADR 0615 §2).
- §6 **The other 19 readers ask the owners' read tools, one per bucket they read** (ADR 0615 §1, §2). Beside the seven
first-view tools above, each owner serves a read tool for every bucket of its state one of the 19 reads today,
named `indexer_read_<bucket>` or `assigner_read_<bucket>` and marked as a read: fifteen. The indexer's nine:
`indexer_read_manifests`, `indexer_read_modules`, `indexer_read_predefined`, `indexer_read_config_candidates`,
`indexer_read_candidates`, `indexer_read_packages`, `indexer_read_graph`, `indexer_read_heard` and
`indexer_read_taken`. The assigner's six: `assigner_read_placement`, `assigner_read_seat_holders`,
`assigner_read_wants`, `assigner_read_findings`, `assigner_read_asks` and `assigner_read_differences`. These are
the nine buckets of the indexer and the six of the assigner the 19 read (§ 12.6), `modules`, `wants` and
`placement` among them for the status view; the indexer's tenth, `verdicts`, is read by the grants part alone,
through its first-view tool. **They are ordinary module tools**: granted, as any tool is, to every
reader that declares it in `invokes`, and callable by a session; the `callers` list of ADR 0621 §2, ADR 0622 §1 and ADR 0631 §1 covers
only the seven first-view tools. **The status view keeps all its tools**, their names, arguments and answers
unchanged, its `indexer_*` and `assigner_*` tools included: as a reader it answers them by asking the owners' read
tools instead of reading their buckets. Checked live after the delivery: a session's call of each status view tool
answers, and `mesh_search` finds each owner's read tool by its bucket's words.
- §7 **Only the owner reaches it** (ADR 0596 §2). The membership grants the owner's account its own entries and no
other module's: the new form has no readers. What another module needs of that data it asks through the owner's
tools or seat verbs, and what the owner lets others hear is its declared events, subscribed to through
`consumes` (ADR 0363, unchanged). A part watches only its own state, and hears another's events (ADR 0596 §4).
### 12.3 The seat's verbs, events and fill
- §8 **The seat gains eight verbs.** The seat `mesh-broker`, until now a seat with no verbs, gains these (ADR 0595 §5, §6). It is one of the ten seats
defined whole in the milestone of ADR 0612, its contract a package of its own in `mesh-contracts` (ADR 0613 §1,
ADR 0627 §1, ADR 0625 §1; design 26 *A seat's definition*), which settles where its definition lives (ADR 0595 §12). An entry's arguments are the module, its local
name, its storage type, its template, its options and its configuration; what is held answers the settings held and
the fill (count, bytes, the cap); `drop` and `recreate` carry the warrant and why.
| Verb | Does | Who may call it |
|---|---|---|
| `list` | every entry, its template, its settings, its fill against its cap | any caller whose role grants it (ADR 0386) |
| `show` | one entry, the same | the same |
| `check` | compares what the bus holds with what was asked, entry by entry | the same |
| `orphans` | entries the bus holds that no manifest asks for; never one whose manifest the manifest validator refused or outgrew, nor one of the foundation's old sections | the same |
| `create` | creates one entry now, for an entry an accepted manifest asks for | an act of the bus module's process alone, answered on no subject |
| `apply` | applies a difference that only grows: a larger cap, a longer age | the same |
| `recreate` | rebuilds an entry whose settings differ in a way that cannot grow, from its data where the storage type allows | the same, on the operator's warrant, which the process verifies itself |
| `drop` | removes one orphan | the same, on the operator's warrant, which the process verifies itself |
- §9 **Four of them are acts of the process alone.** `create`, `apply`, `recreate` and `drop` stay in the seat's definition as acts its holder's process alone performs,
answered on no subject (ADR 0626 §4, ADR 0436 §3): they are not verbs whose subjects a credential is granted, but
acts on the bus's own create, update and delete subjects, which only the process's account holds, so no module can
ask the bus for them. A person asks for them through the bus module's tools, which hand the request to the process
through its own state (below); `recreate` and `drop` act only on the operator's warrant (ADR 0259 §6), asked
through the operator channel. Before `recreate` or `drop` acts, the process takes a stream snapshot of the entry
(ADR 0595 §5), and it acts only once the snapshot is whole. A drifted entry is said, never quietly fixed: only a
difference that grows is applied without a person.
- §10 **Events**: `created`, `refused` (with why), `drifted` and `drift-cleared`, `full` and `full-cleared`, `orphaned`
(cleared by a `drop`, or by a manifest asking for the entry again, which emits `created` for it), `recreated` and
`dropped` (ADR 0595 §6, as its progressive insight of 2026-11-03 has it, waiting for the operator's Approve);
each carries the module, the local name, the bus name, the template, and the settings asked against those held.
**They follow every seat's events**: each is said on `mesh.seat.mesh-broker.event.<event>`, on the shared stream
`EVENTS` (§ 3), kept 7 days, and heard through `consumes` `mesh-broker.<event>` like any seat's event. **The bus
module keeps their history apart**, because a drift or an orphan is read back days later by a person: a stream of
its own in its old `streams` section, on its module subjects, kept 30 days (ADR 0363's bound), created by the grants
part as today, with a cap of 256 MiB, to which the process appends every event it says. Its own kept data stays on
the old sections (ADR 0595 §10), because it cannot wait for its own process to create it. That stream drops its
oldest when full, which loses no live state, because every drift, orphan and fill stands as a condition until it
clears; the conditions hold the live state. The tool `history` of one entry reads it back: every event the bus
module said of the entry, in order. This settles where they are kept, which ADR 0595 §12 left to the design, and
needs no new manifest form: a module's stream never takes a seat's subjects, and every seat keeps one form for its
events.
- §11 **Fill is the bus module's one periodic read** (ADR 0595 §6): every minute, for every entry that refuses when full,
because the bus says nothing as a store fills. The condition `full` is raised at a raise mark and cleared below a
lower clear mark, so an entry sitting at the mark does not flap; the marks are settings of the bus module, by default
a raise at 75 percent of the cap and a clear below 70 (ADR 0595 §6, as its progressive insight of 2026-11-03 has it).
The grants part's `mesh-grants.history-full` moves to it: the new condition is raised first, then the old key cleared;
no manifest on main asks for a history stream or objects today, so the move loses nothing. A bare stream that refuses
when full is read the same way. **The conditions**, each naming the module and the entry: `full` (cleared below the
clear mark), `drifted` (cleared by `drift-cleared`), `orphaned` (cleared by a
`drop` or by a manifest asking for it again) and a refused create (cleared by a later `created`).
- §12 **Its tools** (tools-are-how-a-module-is-spoken-to): the four reading verbs, marked reads, answer every entry the
bus module created and its fill, and `history` its events for one entry; `create` and `apply`, marked acts, and
`recreate` and `drop`, marked dangerous and so granted only by name (ADR 0386 §6, ADR 0387), are its tools for a
person. **A person's request reaches the process through the process's own state** (ADR 0626 §4), because a process
account answers no tool call (ADR 0436 §3). Such a tool, in the bus module's bundle, holds no grant of the bus's
create, update or delete subjects, and neither does the tool runner's account: it writes the request into the bus
module's state `requests` (an old `state` section, as `asked` is, ADR 0595 §10), stamped with the caller the tool
runner proved and recorded (ADR 0386 §8), and watches that key for the process's answer, never asking again on a
timer. The process watches its own state `requests` (ADR 0596 §4) and takes a request up when it is written. The stamp names
who asked, for the record; it is no boundary, because every session on a machine reaches the bus as the tool
runner's account (ADR 0436 §9). **So the process verifies the operator's warrant itself**: for `recreate` and `drop`
it trusts nothing in the request, reads the operator channel's record of the ask under its own account and checks
that the warrant binds exactly this act on this entry (ADR 0259 §6), as the mail sender does (ADR 0436 §9); a request
without a valid warrant it refuses, deleting nothing and writing why into its answer; with one, it takes the entry's
stream snapshot first. **The process wakes on the warrant, never on a timer**: a warrant is said on the operator
channel's `decided` for the asker that raised the ask (ADR 0259 §6; ADR 0620 §2), here the bus module itself, which
hears its own `decided` on a durable consumer of the process, whose filter the grants part derives from the
process's `uses` of the operator channel seat (not from a `consumes`, which a process has none of), and runs its pass over the waiting `recreate` and
`drop` requests when one arrives, so a warranted `drop` acts within seconds of the operator's answer, and one given
while the process was down is heard when it is back. **A warrant acts once** (ADR 0259 §6): before it acts, the
process claims the warrant's ask id by writing the key `acted.<ask id>` in the state `spent` of the process's own bus state, which the tools bundle is not given (a person's request and the
record of what it spent are never in one bucket, and no tool can write the record), created by compare-and-set after the snapshot and before any deletion, and a request naming an ask it already
acted on is refused, deleting nothing and saying which act that warrant already performed. **An ask lasts 12 hours**,
shorter than the 24 hours `requests` keeps a request: a request whose ask ended unanswered is ended and said, and a
warrant is not acted on once its ask ended; the tool raises the ask before it hands the request over. A request
that cannot act yet says why in its field Waiting, and a hand act the process could not record is written on the
request in its field Unrecorded. The tool `nats_requests` reads the waiting and ended requests, and the warrants
spent from `spent`, which is declared `read-only: true` (design 32 § 4) so that the tool reads it and holds no publish on it, and drops a waiting request by hand, recorded as a hand act. `create` and `apply` need no warrant, since they only make or grow what an accepted manifest
asks for, and the process refuses either for an entry no accepted manifest asks for. A tool acts only for a caller
whose role grants it; the process records each act as a hand act (`mesh-hand-acts`) and answers what it did. What
stands between the tool runner's account and the process's delete power is that check of the warrant, so its tests
are this section's central check (§ 12.7, in the main file). **The mesh deletes kept data only through `drop` and `recreate`** (ADR 0595 §4, as its
progressive insight of 2026-11-03 has it): the bus module's older remove tools (its stream, bucket and consumer
removals, ADR 0564) refuse an entry of the new form, naming `drop`'s tool, and keep working for the old sections. The bus seat's `apply` (a difference that grows) and the
ledger's `apply` (§ 12.4) are two verbs on two subjects, granted apart; this design writes them qualified. Its health
names the process's last pass, the entries it could not create, and the entries past the raise mark.
### 12.4 The ledger
- §13 **The ledger is a storage type of its own.** A **ledger** is a storage type whose state is derived from its history (ADR 0597). It is built on a stream and
key-value buckets, and asked for with the template ledger.
```
module ──apply(instance, operation)──▶ tool runner on its own machine
│ 1 checks the operation against the type
│ 2 reads the history's last sequence for the instance; replays if the state is behind
│ 3 appends to the history, that sequence expected ──▶ history (stream, a subject per instance)
│ 4 derives the state by the operation's effect ──▶ state (key-value, updated at the revision read)
│ 5 writes the instance's snapshot ──▶ snapshot subject (latest only, never expires)
▼
answers: taken, with its sequence (and "state behind" when the state bucket is full)
module's process: a watcher keeps each declared index (a second key-value bucket) from the history
```
- §14 **A type** is a schema and the operations allowed on it; an **instance** of a type is named by a path
(`listing/a17`, `decision/0290`); every change is an **operation**, kept in the instance's history.
- §15 **A ledger effect** is what an operation does to the state, one of a fixed set: set fields, set an entry of a
map, append to a list, remove (ADR 0597 §2). An effect copies only the fields its type names, and may add the
operation's sequence and author. Where a type pairs a text field with its digest, `apply` refuses an operation
whose digest does not match. A module's own rules are checked by the module before it calls `apply`, never by the
effects. Only the owning module calls `apply` on its ledger, for every effect, remove among them; a person
removes through the owner's tools. This settles who may call the remove effect, which ADR 0597 §13 left to the
design.
- §16 **One writer wins, and the state never goes back** (§5): the expected sequence makes the bus refuse a racing
writer; the state is written as an update of the revision read, never a plain put; when that update loses to
another writer, `apply` answers success with its operation's sequence and re-reads, never appending again.
- §17 **A full state bucket loses no operation** (§6): `apply` answers that the operation is taken, with its sequence,
and that the state is behind because the bucket is full; the next `apply` after room is made catches it up.
- §18 **The author is proven** (§7): `apply` stamps the owning module's authenticated account and the account that
called the owner's tool. The owner passes that author on by naming the tool call it serves: the tool runner, which
served that call and proved its caller, stamps the caller it proved, so the module cannot set the author to words of
its own; an `apply` naming no call in flight carries the owner's account alone, and one naming another module's
call is refused. A copy of old records (design 55 § 8.1) is stamped with the owner's account and keeps the old
author as a separate field marked imported, never as the proven author.
- §19 **Every instance has a snapshot** (§8): the latest state on a subject of its own, never expiring, written by every
`apply`. It makes a kept ledger's rebuild fast and an expiring ledger's possible; a repair that finds it behind
the state rewrites it.
- §20 **Only the tool runner's ledger credential publishes on a ledger's history** (§9): one per module assignment, that
is per node the module runs on, the account `ledger.<node>.<module>` in the tool runner's own secret `ledgers` on
that node (ADR 0597 §9, as its progressive insight of 2026-11-03 has it, waiting for the operator's Approve), held by
the tool runner and never by the module's own code, whose account may read its ledger and not publish on it. A
person's `rotate` of that secret is refused by the vault, as a bus account's is. The grants part issues it with the
module's membership on that machine, scoped to the subjects of that module's ledger
histories and snapshots, and removes it when the module is unassigned from the machine. Only the
owner's account reads the history, the snapshot and the state.
- §21 **An index** is a second key-value bucket kept by a watcher in the owning module's process, rebuildable from the
history; it raises a condition when it lags behind (§4).
### 12.5 The calls, and where they live
- §22 **The new calls live in a package of their own.** The new calls live in a small mesh-sdk package of their own, served by the tool runner and imported only by the
modules that use them; the module library, which 112 modules import, does not change (ADR 0595 §9). They are the
object store's (put, get, list, delete, info, watch); a stream's read-back and fill; a work queue's size and dead
letters; and the ledger's: `apply`, `get`, `history`, `watch`, `query`, and its repairs `rebuild` (the state from
the history or the snapshot), `reindex`, `snapshot` and `lag` (ADR 0597 §10). The tool runner serves the repairs as
tools of every module that owns a ledger, limited to its own ledgers, so no owner declares them and the next ledger's
owner has them on its first day. mesh-sdk's catalog and membership
packages change once for the typed `requires` entry and the binding, rebuilding about 30 modules.
### 12.6 The move, and the order of the build
- §23 **The build, each step undoable on its own** (ADR 0595 §10): (1) mesh-sdk's typed `requires`, binding and calls
package; (2) the manifest validator's rules; (3) the bus module's process, verbs and events, creating only for the new form, with the grants part's writing of
the binding on `created`; (4) the tool runner's calls, the ledger's `apply` and its ledger credential among them; (5)
modules move, each owner first. Each undo: undoing step 2 refuses the new form again, which no module uses before
step 5; undoing step 3 leaves what it created in place, and a module moved after it fails loudly on its missing
binding; undoing step 4 leaves a moved module without its calls, which the placement holds off that machine. The
placement holds a moved module off a machine whose tool runner does not serve the typed binding and the calls the
module asks for (a ledger's `apply` among them), and says so; where the placement reads what a tool runner serves is
left to the build of step 4 and tracked on issue 1966.
- §24 **A module moves when it is next touched** (ADR 0595 §11), keeping its local names and carrying its old settings
as options, so its bus objects and data stay and no drift is raised. It asks for each entry once: an old section
and the new form are never both used for one local name. During the move the grants part keeps creating the old
sections' entries, so each entry has one creator. The indexer counts the modules on each old section, a tool reads
the count, and the manifest validator refuses a section once its count is 0. The foundation's own kept data (the bus module's,
the grants part's and the indexer's) stays on the old sections for good and is not counted: those modules cannot
wait for a process that needs them first. ADR 0619, extending ADR 0595 §10's foundation, adds the parts the bus module's own composition, placement, build or delivery needs before it can answer:
the assigner, the tool runner, the builder, the checker, mesh-config and the vault; their new state and streams of
this milestone go on their old sections too (§ 12.2; design 51 *The move*; design 32).
- §25 **Readers move after their owners** (ADR 0596 §3). Today 195 `reads` entries in 37 modules read another module's
bus state (counted 2026-11-03 02:00). **A correction of fact**: the indexer's and the assigner's state, counted again
from every manifest on main later that day, is read by 23 modules, 10 buckets of the indexer (`verdicts`, read by the grants part, among them) and 6 of the assigner,
not by the 19 the first plan counted; and 151 state entries were counted, not 158 (the template table of § 12.1 keeps
the first count, which chose the templates). The owners first gain their events, first-view tools and read tools: the
indexer a read tool per bucket its readers read, the assigner the same (§ 12.2 lists them). Then all 23 readers move in the milestone of ADR 0612, the
four foundation readers included, by the first view of § 12.2 (ADR 0615 §1), the grants part's of the indexer's
`verdicts` by its first-view tool `verdicts` (ADR 0622) and then `manifest-accepted` and `manifest-refused`, and the
assigner's of the indexer's `graph` by its first-view tool `graph` (ADR 0631) and then `indexer.views`; their order is the module graph's, not
a review reading an owner delivered (ADR 0615 §2). The status view's move keeps every tool it serves, each answered by
asking the owners' read tools of § 12.2. **The node-engine's reads granted in code stay**: the grants part's code
grants each node's node-engine, with no manifest declaring them (`CandidateReads` and the composed reads beside it),
the indexer's `candidates` and `verdicts` and its own node's keys of the assigner's `wants`. They are not among the
23 readers of ADR 0615 §1 and move in the next milestone, queued on issue 1966. The manifest validator still refuses a moved module's subscription to an event
its owner does not declare. Every other reader moves when its module is next touched. These moves are tracked by
issue 1966.
## The lead's answers
- **L1** (issue 1966, met three times on 2026-11-12: a merged module's new build waited for its part grant): a candidate's
manifest that a merge makes a module's trunk manifest is newly accepted in §1, so the indexer's merge pass says
`manifest-accepted` from the verdict it holds even when that verdict was written for the candidate before the merge
and is unchanged; a reader that receives it below the revision it holds treats it as a gap (§12.2) (P3, P1).
+200 -8
View File
@@ -7,9 +7,28 @@ code:
- mesh-tools src/broker-amqp.ts (to be replaced)
- mesh-catalog modules/nats (to be written)
- mesh-sdk src (the protocol's NATS binding, step 3)
- mesh-tools node-tools/internal/bus (a module's state, ADR 0201)
updated: 2026-10-10
- mesh-tools node-tools/internal/bus (a module's state, ADR 0201; the new calls and apply, ADR 0595, ADR 0597)
- mesh-sdk (the typed bus-state entry, its binding and the new calls' package, ADR 0595)
- mesh-delivery (the manifest validator's rules and the grants part's binding, ADR 0595)
- mesh-contracts seats/mesh-broker (the seat's definition: `list` answers each entry's binding)
updated: 2026-11-11
decisions:
- "ADR 0595"
- "ADR 0626"
- "ADR 0631"
- "ADR 0620"
- "ADR 0259"
- "ADR 0627"
- "ADR 0436"
- "ADR 0619"
- "ADR 0615"
- "ADR 0621"
- "ADR 0622"
- "ADR 0614"
- "ADR 0613"
- "ADR 0612"
- "ADR 0596"
- "ADR 0597"
- "ADR 0289"
- "ADR 0264"
- "ADR 0235"
@@ -38,6 +57,17 @@ under which subject, with which guarantee, under whose account; how a node joins
reaches a tool; how the mesh moves from the bus it has to this one; and how each claim is checked.
Prose and diagrams only; no configuration is pasted.
**Amended, 2026-11-03** (ADR 0595, ADR 0596, ADR 0597): what a module keeps on the bus is its **bus state**, a
provision of the bus module asked for by template, created by the bus module, read by its owner alone; and the
**ledger** is a storage type of its own. § 12 is the design; the `KV_<module>_<name>` row of § 3 and the module's
state in § 4 hold for a module still on the old sections.
**Amended, 2026-11-11** (ADR 0595 §1, §7; ADR 0615 §3 to §6; the operator, 2026-11-11, in the terminal: "Ask the
bus module"): the grants part no longer learns a binding only from the bus module's live events; at every start and
when asked again by hand it takes a first view of every binding from the seat `mesh-broker`'s `list`, so a missed
`created` loses no binding for good (milestone bindings-first-view). The same change puts this design in the shape
of playbook 02: § 12.2 to § 12.6 moved, unchanged and numbered, into the part of milestone bus-state.
## 1. What the bus is for
**The bus is where the mesh happens.** Not a transport the mesh sends things over — the place a
@@ -129,7 +159,7 @@ mesh already has a shape for that — a seat's `accept` subjects, on a work queu
holders, which is what a build queue shared by several machines *is*. Keeping a second mechanism for it
meant two things to reason about and two places for a permission to be wrong. A build's outcome is the
seat's own event — which is why the control branch loses its copy too: one publish reaches whoever
asked, the controller that records it and the catalogue that places it — the fan-out a shared exchange gave for free, as a derived subject rather than a
asked, the controller that records it and the catalog that places it — the fan-out a shared exchange gave for free, as a derived subject rather than a
configured topology.
**Revised 2026-09-26** ([design 29](32-what-a-module-declares.md)): a module's events and tools
@@ -179,7 +209,7 @@ Core NATS is at-most-once. Everything the mesh must not lose lives in a JetStrea
| EVENTS | `mesh.mod.*.event.>` and `mesh.seat.*.event.>`; and `mesh.again.>` (ADR 0264) | limits (age, size), durable consumer per subscribing module | a subscriber that was down catches up; after `max-deliver` attempts the advisory feeds `mesh.events.dead` (its own small stream). *2026-10-08, ADR 0264:* that stream is DEAD_LETTERS, below; each consumer also filters `mesh.again.<consumer>.>`, where a message it gave up on is delivered again to it alone. *2026-10-01:* a build's whole log is here too, as the build seat's `log.<build id>` events (ADR 0157) — one subject per build, a week of retention, read back by `builds --log <id>` with a consumer that is gone when the reading is done |
| DEAD_LETTER_NOTICES | the server's maximum-deliveries advisory | work queue, a week; one consumer (the controller), no `max-deliver` | the server's word that a consumer gave up on a message, kept whether or not a controller is listening, until the controller has copied the message (ADR 0264) |
| DEAD_LETTERS | `mesh.events.dead.<stream>.<consumer>` | limits: no age, 256 MiB, refuses new when full; a day's de-duplication | every message a consumer gave up on, with its consumer, subject, deliveries and when, until a person delivers it again or drops it with why (ADR 0264); a `max-deliveries` condition is open per consumer while it holds any |
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | the newest value per key — as many past values as the owner declared — no age unless the owner declared one; a value at most 256 KiB, a bucket at most 64 MiB | a module's state (ADR 0201): one per name in a manifest's `state`, created from the catalogue on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data |
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | the newest value per key — as many past values as the owner declared — no age unless the owner declared one; a value at most 256 KiB, a bucket at most 64 MiB | a module's state (ADR 0201): one per name in a manifest's `state`, created from the catalog on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data. *2026-11-03, ADR 0595:* a module's kept data in the new form is created by the bus module from `requires`, by template (§ 12) |
Tool calls and heartbeats stay on core NATS: a lost heartbeat is the next heartbeat; a lost tool
call is a timeout the caller already handles.
@@ -193,6 +223,9 @@ holding `mesh-broker` — has an account of its own whose only grant is the snap
names, a stream's information, the snapshot request and its acknowledgements, and its own inbox. The
night's backup takes every stream through it ([to-be 43](43-backups-against-mistakes.md)). The
controller stays the only writer of stream definitions; the writers table holds that at composition.
*Amended 2026-11-04, ADR 0626 §2:* that account keeps exactly this grant; beside it, the bus module's process holds
a process account of its own for the bus state it creates (§ 12.2), so the module holding `mesh-broker` holds two
accounts, each doing only what its record grants.
### The store window, and what moving it into the server changes
@@ -266,7 +299,7 @@ expresses this exactly, per subject, and better than a vhost could:
permissions for each consumed event's subject, its tool subjects, and that same inbox prefix.
Nothing else. A module that tries to publish outside its emits is refused by the server, not by
convention.
- **A module's state** (ADR 0201), for whichever principal carries the module — today the machine's
- **A module's state** (ADR 0201; for the new form, § 12.2: the owner's account alone, no reader, ADR 0596), for whichever principal carries the module — today the machine's
runtime, whose grant is the union of its modules': binding to the bucket, reading a key directly,
and an ordered consumer for listing and watching, created and deleted on the bucket's own stream
and nothing else's; and, for the owner's instances only, publishing under the bucket's own
@@ -321,7 +354,7 @@ signing hierarchy for nothing.
## 5. The broker as a module
`nats` is a catalogue module claiming the seat `mesh-broker`
`nats` is a catalog module claiming the seat `mesh-broker`
(ADR 0079: the seat
is the server, and the server changes). It declares one container (a single binary; **JetStream on a host
directory bind, not a named volume** — revision, second review:
@@ -410,7 +443,7 @@ bridged. It is three things:
for anyone else — and nothing on control, nodes or builds. It is issued, sealed to the person's
own key, and revoked, like a module's.
2. **A client that speaks the bus**: a small program on the workstation that connects as that user
over TLS, lists tools by asking the catalogue (`mesh.tools.mesh-catalog.catalog_tools`), and
over TLS, lists tools by asking the catalog (`mesh.tools.mesh-catalog.catalog_tools`), and
turns each tool into a call — as an MCP server for an agent, and as a command line for a person.
It uses the sdk's `Broker` contract on the NATS runtime, so it is the same code path a module's
tools use, not a second protocol.
@@ -606,7 +639,7 @@ find what changed and why.
decides it.
- The heartbeat interval and the controller's "quiet" threshold on core NATS without persistence —
the same numbers as today are proposed.
- Whether the person's client is a catalogue module (runs on an enrolled workstation node) or a
- Whether the person's client is a catalog module (runs on an enrolled workstation node) or a
standalone program (runs anywhere with credentials). Both, in that order, is proposed.
- Leaf nodes: a NATS leaf per machine would make every module's connection local and survive the
hub's restart. Deliberately out of scope; noted so it is not forgotten.
@@ -617,3 +650,162 @@ find what changed and why.
that needs it? ADR 0039's test —
*does editing it recompile unrelated modules, and does it change often* — probably says no for
one instance; worth asking again if a second module needs the same shape.
## 12. Bus state: what a module keeps on the bus, and the bus module that provides it
*Added 2026-11-03* (ADR 0595, ADR 0596, ADR 0597, from research 082; the operator, 2026-11-02 20:45: "The names
simply represent named configurations or templates"). Until now a manifest declared what a module keeps on the bus
in four sections with four vocabularies (`state`, `logs`, `streams`, `objects`), the grants part created each
entry once and never changed it, and nothing compared what the bus holds with what was asked. This section is the
design of the one form that replaces them; [design 32](32-what-a-module-declares.md) § 4 says how a manifest asks
for it, and [design 55](55-the-knowledge-base.md) § 2.1 is its first ledger.
**Bus state is a provision of the bus module** (ADR 0595 §1). The **bus module** is the module holding the seat
`mesh-broker` (today `nats`). A module asks it for each piece of its **bus state** the way it asks postgres for a
database: an entry in `requires`, by its local name, its storage type, its template and the template's options.
The bus module creates it; the module receives the names on the bus in its membership; nothing else creates it.
### 12.1 Three layers
```
layer 3 templates state events work queue log objects ledger
│ │ │ │ │ │
layer 2 storage types key-value │ │ │ object store ledger
built on the bucket │ │ │ │ (a stream and
stream │ │ │ │ │ key-value buckets)
└──────────┴───────────┴──────────┴──────────┴────────────┘
layer 1 base type stream
```
A **storage type** is how the bus keeps the data: the **stream** is the base, and the **key-value bucket**, the
**object store** and the **ledger** are built on it, each with calls of its own (ADR 0595 §2). A **configuration**
is the settings a storage type is given. A **template** is a storage type with a configuration, predefined and
named: six exist, from the uses counted on main (158 state entries, 6 event streams, 4 logs, 3 work queues). An
**option** is one setting of a template a module may change, within its bound. A module gives a configuration of
its own only where no template fits: the bare stream, or a ledger whose history expires.
**What keeps the data safe is fixed, never an option** (ADR 0595 §3). Each template fixes what happens when it is
full, and the manifest validator refuses a manifest that gives it:
| Template | On | Fixed | Options, their bounds (default) |
|---|---|---|---|
| state | key-value | refuses when full; a value at most 256 KiB; the bucket at most 64 MiB; one copy, on disk | past values 1 to 64 (1), 0 refused; time to live (none); a lifetime per key; one bucket per machine |
| events | stream | drops the oldest when full; the module's own subjects | age 1 to 30 days (7); cap 1 to 8192 MiB (1024); a grant (ADR 0363 §4) |
| work queue | stream | removed once taken; refuses when full; one taker; no grant | deliveries 1 to 10; duplicate detection up to its age; cap; age |
| log | stream | refuses when full; never rewritten; an entry at most 256 KiB; one subject per key | cap 1 to 8192 MiB (1024); age 1 to 3650 days (none) |
| objects | object store | refuses when full; kept for good | cap 1 to 8192 MiB, required; a grant |
| ledger | ledger | as log, plus its type and a snapshot per instance; its state and index buckets as state | cap; keeping days only in a ledger's own configuration |
| (none: a bare stream) | stream | the module's own subjects; within the mesh's bounds | its own configuration, which must give "when full" (refuse or drop the oldest), a cap of 1 to 8192 MiB and an age of 1 to 3650 days or none, and may give a grant; the manifest validator refuses one missing any of the three |
The template log is the **module log** of ADR 0297. The mesh, never the module, names the bus objects and their
subjects (ADR 0201 §4): `<module>_<local name>`, and a local name holding the separator is refused so no two
modules' names meet.
### 12.2 to 12.6 Who creates it, the seat's verbs and events, the ledger, the calls and the move
```
manifest validator ─verdict: accepted with the module's bus-state entries whole─▶ bus module
│ creates each entry
▼
grants part ◀──────────────── created, recreated, dropped (module, local name, bus name, binding)
│ ◀───── at its start, and when asked again: mesh-broker.list (every entry, its binding)
│ writes the binding into the module's membership
▼
the tool runner reads the membership ──▶ the module's calls reach its own entries, and only those
```
These sections are the lines of milestone **bus-state**, in its part
[25-milestones/bus-state.md](25-milestones/bus-state.md), where they keep their numbers. The shape, in short: the
indexer's `manifest-accepted` carries a module's bus-state entries whole to the bus module, whose process creates
each entry under the module's names and says `created`, `refused`, `drifted` and `drift-cleared`, `full` and
`full-cleared`, `orphaned`, `recreated` and `dropped` on the seat `mesh-broker`. The seat's reading verbs `list`,
`show`, `check` and `orphans` are answered by the bus module's tools; its acts `create`, `apply`, `recreate` and
`drop` by its process alone, the last two on the operator's warrant and after a stream snapshot. The grants part
writes each entry's binding into the module's membership, kept in its own state `memberships`, from those events
and, at every start and when asked again by hand, from its first view of the seat's `list` (milestone
**bindings-first-view**, in its part [25-milestones/bindings-first-view.md](25-milestones/bindings-first-view.md)).
The ledger is a storage type whose state the tool runner derives from its history; the new calls are a mesh-sdk
package of their own; and the foundation parts take their first views of each other's state from the owner's own
tools, then follow its events.
### 12.7 How it is checked
| Rule | Checked by |
|---|---|
| A manifest's bus state is valid, once | the manifest validator's unit tests, each refusal naming the field: an unknown template, an option outside its bound, past values 0, "when full" given, a fixed setting given, a name used twice across storage types, two modules' bus names alike, a `reads` in the new form, an effect outside the fixed set (ADR 0595 §8, ADR 0596 §2, ADR 0597 §2) |
| The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused |
| The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; the process connects on its own process account, composed by the grants part with that account's grants and limits, never on an account without them; a `create` handed through `requests` is answered on the same key, which the tool watches; `recreate` and `drop` without the operator's warrant are refused, and a warranted `drop`, its warrant read from the operator channel's own record of the ask under the process's account and never from a stand-in, takes the entry's stream snapshot before it deletes, and acts within seconds of the bus module's `decided`, with no timer between; the same warrant handed in a second request is refused and deletes nothing; each event said is on `mesh.seat.mesh-broker.event.<event>` and appended to the bus module's history stream, and `history` reads it back; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at the raise mark and cleared only below the clear mark; `recreated` rewrites the binding and `dropped` removes it; at start the first view from `indexer_bus_state` creates what a verdict given while it was down asked; the older remove tools refuse a new-form entry, naming `drop`'s tool |
| A module asking bus state waits for the bus module | the module graph's table test: a manifest with a ledger entry has a requirement edge to the module providing `bus-state`, one without has none (ADR 0614 §5) |
| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`modules/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`modules/mesh-delivery-assigner`) and the grants part's (`modules/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an owner's event carries the changed value whole, and a reader applies a put and a delete from the events alone and never asks the verb; a revision lower than the one held makes the reader ask its first view again; an existing waiter on `manifest-accepted` or on the assigner's `put` does not wake on the new families; for the indexer's `verdicts` (ADR 0622), called by the grants part while the node's tool runner is stopped, it answers, and from any other account, the indexer's own included, it is refused; a missed revision on `manifest-refused` makes the grants part ask `verdicts` once more; the grants part and the indexer started at the same moment, `verdicts` answers within the bound with its revision; with the indexer down past the bound the grants part raises the condition naming the indexer and `verdicts` and never reads the indexer's state, and the answer clears it; for the indexer's `graph` (ADR 0631), called by the assigner it answers with its revision, and from any other account, the indexer's own included, it is refused; a missed revision of the bucket `graph` on `indexer.views` makes the assigner ask `graph` once more; started with the indexer down, the assigner raises past the bound the first-view condition naming the indexer and `graph` and reads nothing of the indexer's state, and the answer clears it; the read tool shows the revision each reader holds, and the re-read tool asks once and records the hand act; in the assigner's whole-module test, the assigner starts before any new-form bus state exists, with no binding of the bus module, and the indexer, the grants part and the tool runner each get their first view of it. Live, after the delivery: the indexer's `indexer_old_sections`, which the milestone extends to count the `reads` entries on each owner's state, reads 0 declared `reads` entries on the indexer's state and 0 on the assigner's, and says the node-engine's reads granted in the grants part's code remain (§ 12.6); and each owner's read tool of § 12.2 answers what its bucket holds, each status view tool answers a session's call as before the move, and `mesh_search` finds each owner's read tool |
| The owners' first views are their own tools, in a runtime of their own (ADR 0621) | cases of the indexer's and the assigner's whole-module integration tests: each first-view tool, called from a reader's account while the node's tool runner is stopped, answers (§1); each answers its granted readers and refuses every other account, the owner's own included (§2); the two runtimes started at the same moment, each asking the other's first view at start, both answer within the first-view bound with their revisions (§3); and the milestone's group passes its merge check with no change to the manifest validator's known fields for these verbs (§4) |
| The grants part's first view of the bindings (design 25 bindings-first-view) | the cases of design 25 bindings-first-view §8: a case in the grants part's existing whole-module integration test, where a binding whose `created` was never said is written after a start; unit rows of the bus module's tools and the grants part, §9's rows among them (a binding composed from an answer without `binding`); live, the next start after the delivery, and §9's live case after the grants part's delivery alone |
| A foundation part's first view comes within its bound | live, after the delivery: each reader's read tool (`grants_first_views`, the mesh MCP server's `mesh_first_views`, `indexer_first_views`, `assigner_first_views`) shows, for the indexer's and the assigner's verbs, the time from its ask to the owner's answer under the two-minute bound, and no first-view condition raised by the delivery |
| The bus module's first view | cases in the nats module's one integration test: a replayed verdict older than the first view is not applied; an `indexer_bus_state` that does not answer turns the health unhealthy naming it and is asked again on the backoff; a set-aside manifest missing from the first view keeps its `asked` key, an empty `manifest-accepted` removes it; a deleted module's entry, after its empty `manifest-accepted`, is listed by `orphans` and raises `orphaned`, and a set-aside module's entry is neither; `nats_first_view_again` asks at once and records the hand act |
| `manifest-accepted` is never lost silently | unit tests of the indexer: a failed publish is published again, and one still failing writes the finding; `indexer_say_accepted` says it again for one module from the verdict held, and refuses a module with no accepted verdict |
| A person's request goes through `requests`, and the process verifies the warrant itself and acts on it once (ADR 0626 §4, ADR 0259 §6) | the process's unit tests: a `recreate` and a `drop` written to `requests` with no warrant, with a warrant for another act or entry, with a warrant the operator channel's record does not hold, and with a warrant whose ask id the process already acted on are each refused, delete nothing and say why; a `create` or the seat's `apply` for an entry no accepted manifest asks for is refused; the stamp is the caller the tool runner proved, whatever the request's body names |
| The process's account, and the bus module's only bus declaration (ADR 0626 §1 to §3) | the manifest validator's unit test: the bus module's manifest with its process's `bus` and `keeps` is accepted; `keeps` on any other process or module, any other bus declaration of the bus module, and a subject of its process on which a tool is answered are refused; the composition test: the process is granted the create, update, delete and snapshot subjects under modules' names, nothing on which a tool or a verb is answered, and no subscription to a restricted stream's subjects beyond the snapshot request, its acknowledgements and its inbox; the snapshot account is granted the same subjects as before; no tool runner and no tool of the bus module holds the bus's create, update or delete subjects |
| The process's fixes of review round 1 hold | the nats module's whole-module integration test and unit rows: an event or condition the bus did not take is kept, named by the health tool and said again at the next fill pass; a second accepted manifest asking for an entry raises no overdue condition; the warrant's wake on `decided` acts within seconds without a call to the pass by the test; a second request naming a spent ask is refused naming the act it spent; an ask past 12 hours is not acted on |
| Drift and orphans, live | live, made only by a manifest change: a manifest that shrinks a setting raises `drifted`; the conditions clear as above. Nothing is filled on the live bus, and no orphan is made there |
| Overdue and stopped are loud | live, carried by the change of the first module that moves in step 5, never by a merge made for the test alone: with the bus module's process stopped, an accepted manifest asking for an entry raises the overdue condition within five minutes, from the watchman, and a later `created` clears it; the bus module's health turns unhealthy when its pass is more than three minutes old |
| A person's tools reach the process, and only by role | live: a role without the dangerous grant is refused `drop`'s tool; the tool runner's account holds no grant of the bus's create, update or delete subjects |
| The author is the proven caller | the ledger's integration test: an `apply` naming another module's tool call is refused; the author stamped is the caller the tool runner proved, whatever the module passed |
| Only the owner reaches its data | live: another module's account reading an entry in the new form, a ledger's history, snapshot or state among them, is refused; a raw publish on a ledger's history is refused |
| The ledger keeps its promises | one integration test against the real bus (ADR 0597's How it is checked): two writers race and one is refused; two interleaved applies leave the state at the higher sequence; a write cut between history and state is repaired; a full state bucket answers taken with its sequence and catches up; an idle instance whose operations expired rebuilds from its snapshot; `rebuild`, `reindex` and `lag` repair by hand; a forged author is refused; every `apply` leaves the snapshot equal to the state; an index behind raises its condition and `reindex` clears it |
| Only the owner applies | the ledger's integration test: another module's ledger `apply` on the knowledge base's ledger is refused |
| A bare stream is complete | the manifest validator's unit test: a bare stream missing "when full", a cap or an age is refused, naming the field |
| The calls rebuild only their users | a read of the delivery plan: only the importers of the calls package rebuild when it changes |
| The move keeps data | live: a module moved from `state` keeps its bucket and data and raises no drift; a new entry in an old section is still created by the grants part; the count falls by one; at 0 a manifest using the section is refused and the foundation's are still accepted; a moved module is held off a machine whose tool runner does not read the typed binding |
**The test budget** (50 integration and 500 unit tests per repository): the design adds one integration test in the
nats module's repository and one in mesh-tools (the ledger), and table cases, not new functions, to the manifest validator's unit
tests in mesh-delivery and to mesh-sdk's decoding tests. Each repository's count against the budget is read in the
prepare step of issue 1966, and a repository over it merges tests to stay within it.
**Acceptance.** The operator asks the bus module's `list` and sees every module's kept data with its template, its
settings and its fill; a module that moved keeps its data; a drift or an orphan reaches the operator as a condition
and is changed only on their word; and the knowledge base keeps its records as a ledger (design 55 § 10).
## Milestones
Each milestone not yet delivered has its part in [25-milestones/](25-milestones/), holding every line it builds
(playbook 02, *The shape of a design*); § 1 to § 11 and the five steps of § 9 stay in this file.
- **bus-state** — § 12.2 to § 12.6, built under issue 1966: the bus module creates every module's bus state, the
seat `mesh-broker` gains its verbs and events, the grants part writes the bindings, the ledger, the calls and the
move. Its part: [25-milestones/bus-state.md](25-milestones/bus-state.md).
- **bindings-first-view** — a recovery fix under issue 1966: the grants part takes a first view of every binding
from the seat `mesh-broker`'s `list` at every start and when asked again by hand, so a binding whose `created` it
never heard is written all the same. Its part:
[25-milestones/bindings-first-view.md](25-milestones/bindings-first-view.md). Its acceptance: a module whose
bus-state entry the bus holds, and whose `created` the grants part never heard, finds the entry's binding in its
membership after the grants part's next start, with nobody's hand.
## Reviewed by architectural domain
The amendment of 2026-11-03 to ADR 0595, ADR 0596 and ADR 0597 (designs 25 § 12, 32 § 4 and 55 together) was
reviewed by the four reviewers by architectural domain side by side (playbook 01), over three rounds, and the lead
ruled on every finding. What was found, and what answers it:
| Domain | Found | Answered by |
|---|---|---|
| security | the author could be words the module chose; a person's dangerous tools had no role, mark or path to the process; the caller travelled as a plain field | design 25 § 12.3 (marks, the process's own tool subject, the identity seat's signed token), § 12.4 (the tool runner stamps the caller it proved; the move's authors marked imported) |
| seams | the bus module asked for its own events in the new form, which it would have to create itself (ADR 0595 §10); a person's tool had no join to the process; a verdict was written twice with no order | design 25 § 12.3 (its events on its old `streams` section; the process's own tool subject); design 55 § 2.1 (the `review` operation first, `reviews` caught up from it) |
| running qualities | overdue creation was undefined and raised by nobody; the database's rollback write lived in memory; a forward delivery after a rollback could miss acceptances and verdicts; no size estimate of the heads | design 25 § 12.2 (five minutes, the watchman, durable consumers, `check` at start); design 55 § 8.1 step 6 (`database.sequence`, the check at every start before the drop), § 2.1 (the heads' estimate) |
| health | the bus module's health would have fired on a quiet mesh; conditions had no clearing; `database-behind` had no clearing | design 25 § 12.2 and § 12.3 (each condition with what clears it); design 55 § 8.1 step 6 |
| events and logs | an event lost when the module stopped after `apply`; the bus module's events had no reader | design 55 § 1 (each event sent with its operation's sequence as id, the state `sent`); design 25 § 12.3 (`history` of one entry) |
| testing | the bus module had no test that starts it whole; live tests would have filled the live bus; the budget was not counted | design 25 § 12.7 (the nats module's integration test, live drift only by a manifest change, the budget paragraph); design 55 § 10 |
| upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 |
| tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 |
**Answered since, 2026-11-04** (ADR 0626, and the registry milestone's ruling on where the seat's events go): the
process's own tool subject of the security and seams rows is gone, since a process account answers no tool call; a
person's request travels through the process's state `requests` and the process verifies the warrant itself
(§ 12.3); the process holds a process account of its own beside the snapshot account (§ 12.2); and the seat's
events are on the shared stream like every seat's, with the bus module's 30-day history its own stream (§ 12.3).
**Answered since**: the margin of `full` and the events of `drop` and `recreate` were the operator's answers of
2026-11-03 and are now ADR 0595 §6's progressive insight, with the correction of fact for ADR 0595 §4 (the bus
module deletes through `recreate` as well as `drop`); both wait for the operator's Approve and are written in § 12.2
and § 12.3. The milestone's amendment (ADR 0613 to ADR 0615: the first view, the bus-state edge, the deletion paths,
the caller until the identity seat, the reader count) awaits its own review by architectural domain, which rewrites
this section.
+417 -14
View File
@@ -15,8 +15,26 @@ code:
- mesh-catalog modules/gitea/module.json
- mesh-controller internal/inventory/migrations/0063-a-machines-names-are-one-seats.sql
- mesh-controller internal/inventory/migrations/0064-the-resolver-file-is-the-uplinks.sql
updated: 2026-10-10
updated: 2026-11-09
decisions:
- "ADR 0641"
- "ADR 0636"
- "ADR 0634"
- "ADR 0635"
- "ADR 0596"
- "ADR 0593"
- "ADR 0625"
- "ADR 0573"
- "ADR 0606"
- "ADR 0609"
- "ADR 0595"
- "ADR 0627"
- "ADR 0608"
- "ADR 0613"
- "ADR 0620"
- "ADR 0612"
- "ADR 0614"
- "ADR 0605"
- "ADR 0287"
- "ADR 0223"
- "ADR 0220"
@@ -98,7 +116,7 @@ nobody argued for is an entry nobody can explain.
**What deserves one** (ADR 0161). A provision the
mesh's own code dereferences by name is delivered by a mesh seat its provider claims — the store, the
bus, the vault, the artifact store, the catalogue. A provision a module merely offers may have several
bus, the vault, the artifact store, the catalog. A provision a module merely offers may have several
providers, and a consumer with several is a person's choice. A fact of the shape *exactly one machine
is X* — the hub — is not a seat, because a seat is held by a module assignment and points at it; it is
a placement with a capacity of one, kept by the store, refused by name when a second is placed, and
@@ -114,7 +132,7 @@ capability is.
ADR 0110): a module
declares its own seats with their protocols, so the seats a mesh has are the mesh's own **plus
every registered module's**. The set is still closed — a seat named nowhere is refused — but it is
computed from the catalogue rather than maintained by hand, which is the property 0110 actually
computed from the catalog rather than maintained by hand, which is the property 0110 actually
needed and the table could not keep.
**And the mesh's own half is data, named for its scope.** Revision, 2026-09-27, reconciling two
@@ -125,7 +143,7 @@ the set out of compiled code into a table the controller owns, so a rename is on
rebuild of everything that names one.
So the set has two halves and neither is written out here: the mesh's own, which the controller holds
as rows, and every registered module's, which is computed from the catalogue. What this document keeps
as rows, and every registered module's, which is computed from the catalog. What this document keeps
is what a seat *is* — the rest would be a third copy, stale the first time somebody renamed one, which
is the fault ADR 0122 exists about.
@@ -139,32 +157,391 @@ convention, which later seats departed from.
|---|---|---|---|
| `mesh-controller` | — | mesh | — | the controller |
| `mesh-store` | — | mesh | — | the store the mesh's own records live in |
| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus |
| `mesh-broker` | — | mesh | `mesh-bus`, and `bus-state` (ADR 0595 §1, ADR 0614 §5) | the broker carrying the mesh's own bus; its holder, the bus module, creates the bus state modules ask for (design 25 § 12) |
| `mesh-vault` | — | mesh | `secret` | the vault (in the seed since ADR 0161; the earlier *reserved* named an effect no rule produced) |
| `mesh-artifact-store` | `the-artifact-store` (renamed 2026-09-30, ADR 0156) | mesh | `artifact-store` | the artifact registry |
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue |
| `mesh-npm-package-registry` | `npm-package-registry` | mesh | `npm-package-registry` | the forge |
| `mesh-git` | `git` | mesh | `git` | the forge |
| `container-image-registry` | `mesh-artifact-store` for images (ADR 0605; the old name readable until every node-engine reads the new one) | mesh | `artifact-store` | the artifact registry (`distribution`) |
| `oci-artifact-registry` | `mesh-artifact-store` for bundles and archives (ADR 0605) | mesh | `artifact-store` | the artifact registry (`distribution`) |
| `mesh-catalog` | `the-catalogue` | mesh | — | the indexer |
| `npm-package-registry` | — (its rename to `mesh-*` stays deferred, ADR 0156 §8) | mesh | `npm-package-registry` | the forge |
| `go-package-registry` | — (ADR 0605) | mesh | `go-package-registry` | the forge |
| `git` | — | mesh | `git` | the forge |
| `mesh-build-machine` | `the-build-machine` | node | — | a builder |
| `mesh-resolver` | — | mesh | — | the mesh's resolvers, each holding every node's internal domain (ADR 0194); replicated, held on the anchor and the home server (ADR 0223) |
| `mesh-dns-resolver` | — | mesh | — | the mesh's resolvers, each holding every node's internal domain (ADR 0194); replicated, held on the anchor and the home server (ADR 0223) |
| ~~`mesh-dns-port`~~ | `the-dns-port` | node | — | retired by ADR 0194: the local resolver became the mesh's one; deleted from the set, and from the store's table, once nothing claimed it (ADR 0220) |
| `node-hostname` | `node-hosts-file` (renamed by ADR 0223; the former name an alias) | node | — | owns a machine's names: `/etc/hostname`, written from its holder's `hostname` setting with no default and taking effect at the next boot, and in `/etc/hosts` the machine's own lines and the operator's kept region, changed through its verbs `entries`, `add`, `remove` (ADR 0199). Held by the module `hostname`, formerly `hosts` |
| `node-resolver` | — | node | — | a machine's own resolver, held only where something requires `split-dns`: it writes `/etc/resolv.conf` there (the uplink's holder steps back from it) and routes a VPN's domains to the VPN's servers over its link through its verbs `routes`, `route`, `unroute` (ADR 0247). Held by the module `systemd-resolved` |
| `mesh-intrusion-prevention` | `the-intrusion-prevention` | node | — | an intrusion-prevention service |
| `mesh-packet-filter` | `the-packet-filter` | node | — | the packet filter |
| `mesh-private-network` | `the-private-network` | node | — | the private network the mesh runs over |
| `the-private-network` | — (it keeps its name, ADR 0636 §6) | node | — | `mesh-wireguard`, the private network the mesh runs over |
| ~~`mesh-resolver-configuration`~~ | `the-resolver-configuration` | node | — | retired by ADR 0223: `/etc/resolv.conf` is written by the holder of `node-uplink`, the program that would otherwise rewrite it, and the need of the uplink beside it (ADR 0220) goes with it; deleted from the set, and from the store's table, once nothing claims it |
| `mesh-showcase` | `the-showcase` | node | — | the showcase module |
The controller holds **the mesh's own** entries in code, and a test asserts their size and that
every one names the record that made it a seat. A module's seats are not here and never will be —
they are read from the catalogue. **This table and
they are read from the catalog. **This table and
ADR 0126 govern, and code that
disagrees is what is wrong.** The implementation in progress predates several
things here: seats held by assignments rather than claimed by definitions, the `mesh-vault` seat and
its reservation, and the foundation's seats delivering nothing. It is brought to this table before it
merges.
## A seat's definition (from 2026-11-03)
**A seat is defined once, in one language-neutral file, and every part learns it from the bus** (ADR 0608, from
research 081). Every seat has its definition file in its domain's contracts module, written by hand, whether it is
defined whole or at the seat list's grain (ADR 0635 §2). In the milestone of ADR 0612, 25 seats are defined whole
(ADR 0613 §1, ADR 0620, ADR 0627 §1, ADR 0634 §6, ADR 0635 §5); every other seat is defined at the seat list's grain.
- **The file** is a schema with conformance fixtures: the seat's verbs and their messages, its events and their
bodies, its states and the forms of its provisions, and names its glossary domain (ADR 0636 §5). It lives in the
contracts module of its domain (*Where a seat's
code goes*, below). Its Go and
TypeScript types are generated from it and committed beside it; nobody writes them by hand. The format is JSON, its
messages and bodies JSON Schema, as the seat list's verb inputs already are, with the format's own schema checked in;
the fixtures are data, never functions.
- **A definition names its states** (ADR 0608 §1; ADR 0634 §6 leaves the file's format to this design). A `states`
entry is what any holder of the seat keeps and what the seat's verbs answer from: its local name, its template of
ADR 0595 (state, events, work queue, log, objects or ledger), who writes it, each form of its keys with the schema
of the value under it, how long it keeps its history, and its size cap. The holder writes every state; only a state
of the events or log template may name each caller as the writer of its own keys, granted on keys under the
caller's own name and no other. No entry names a holder module, so another module can hold the seat by importing
the contract alone (ADR 0625 §7). The generated package carries, per state, its local name, a builder and a parser
per key form, and the value types, decoded strictly; the fixtures hold one value per key form.
`operator-channel`'s `records` becomes one `states` entry in the same change, so the format has one form for a
state. The format's own schema refuses an entry with an unknown template or a key form without a value, naming it.
**A state is the holder's, asked through the seat's verbs** (ADR 0596 §2): the module that reads another module's
bucket directly today keeps doing so only as a reader in transition, through its own manifest's `reads`, until its
module moves to the owner's verbs or events (ADR 0596 §3, issue 1966). The bucket's name stays
`<holder>_<state>`, the holder's own, so nothing moves under a seat's rename. **How it is checked**: the manifest
validator refuses a holder whose `requires` does not declare each of its seat's states with the same local name and
template, naming the state; the format's own schema refuses a caller-written state of another template and a state
with no size cap.
- **What is defined whole.** Ten seats came first (ADR 0613; ADR 0620, on the operator's answer of 2026-11-04 to move
them all; ADR 0627 §1): `npm-package-registry`, `go-package-registry`, `container-image-registry` and
`oci-artifact-registry` (one common part of their shared verbs, written once, so the four cannot drift),
`mesh-broker`, the four seats the milestone adds a verb or an event to, each with every verb its holder's manifest
on the trunk declares today, plus the new ones, and `node-engine`. Fifteen more are defined whole in the same
milestone because a name leaving mesh-sdk (*Where a seat's code goes*) is their type, state layout or message
(ADR 0634 §6, ADR 0635 §5), each with its verbs, events, messages and the layouts of its states:
`mesh-module-assigner`, `mesh-wants`, `mesh-node-enroller`, `mesh-catalog`, `node-build-go`, `node-build-image`,
`node-build-node`, `mesh-hand-acts`, `mesh-provisioner`, `mesh-conditions`, `mesh-self-check`, `mesh-healers`,
`mesh-watchdogs`, `the-private-network` and `mesh-delivery`. Five seats already whole gain the state layouts
mesh-sdk held for them: `node-engine`, `mesh-vault`, `mesh-delivery-checker`, `container-image-registry` and
`oci-artifact-registry` (ADR 0634 §6). **No event moves subject in this milestone** (ADR 0620), but those of the
two renamed seats (*Two seats renamed*, below): a seat's own events are only those published on its seat subject
today, and a holder's own events stay under the holder's own subjects, listed beside the seat they belong with,
never moved onto it. The four seats the milestone adds to:
- `mesh-delivery-checker` (held by the checker): the verbs `try`, `try-read` and `try-end`; no seat event. The
holder's own, beside it: its family `checker.asks`, new, with `tag-wanted` (design 51 *Tagged versions*) and
`verdict` (*Out of step is learned from events*, below); and its stream `checks`, which already declares
`preview.**` and `owed.**`, only its `emits` lacking `preview.written`, which the same change adds.
- `mesh-config` (held by mesh-config): the verbs `config` and, new, `renames`; no seat event. The holder's own,
beside it: `config-changed`, and its family `config.events`, new, with `renamed`, `retiring` and `retired`
(design 32).
- `mesh-vault` (held by the vault): the verbs `get`, `secret-ask`, `issued`, `rotate`, `move-in`, `node-key`,
`own`, `issue-account`, `sign`, `enrolment-token`, `node-account`, `rotate-node`, `node-account-confirm`,
`abandon` and, new, `retire`; no seat event. The holder's own, beside it: `provisioned`, `rotated`,
`secret-changed`, `account-hashed`, `own-replaced`, `account-issued`, `signed`, `rotation-begun`,
`node-account-ready`, `rotation-done` and, new, `own-retired` (design 32). The seat list mesh-sdk compiled
also names `accept` and `move-asks`, which no holder serves and nothing calls: the definition drops them.
- `operator-channel` (held by the router, the messenger module): the verbs `open`, `history`, `notify`, `tell`
and `asks`; the asks it accepts, `ask`, `cancel` and `condition`; its seat event `decided`; `by-caller` for
`ask`, `cancel`, `condition` and `decided`; and its state `asks`, as the holder's seat entry has them. The
holder's own, beside it: `refused`, and `offered` and `closed`, new, on its restricted stream (design 55 § 11).
Its definition is written from the holder's seat entry.
And `node-engine` (held by the node-engine on every node) has the verb `manifest-reads` and its seat event
`manifest-reads-changed`, which the milestone gives it (ADR 0618 §3, §4; design 51). The 25, by the contracts
module that holds each (ADR 0634 §7, ADR 0635 §5):
| contracts module | domain | seats | held by |
|---|---|---|---|
| `mesh-delivery-contracts` | Delivery | `mesh-catalog`, `mesh-delivery-checker`, `mesh-delivery`, `mesh-wants`, `git`, `node-build-go`, `node-build-image`, `node-build-node`, `npm-package-registry`, `go-package-registry`, `container-image-registry`, `oci-artifact-registry` | the indexer, the checker, the gate part, the assigner, the three build modules, the forge (three), `distribution` (two) |
| `mesh-health-and-repair-contracts` | Health and repair | `mesh-conditions`, `mesh-self-check`, `mesh-healers`, `mesh-watchdogs`, `mesh-hand-acts` | the condition keeper, mesh-self-check, mesh-healers, the watchman, mesh-hand-acts |
| `mesh-provisioning-contracts` | Provisioning | `mesh-provisioner` | mesh-provisioner |
| `mesh-identity-and-access-contracts` | Identity and access | `mesh-vault`, `mesh-node-enroller` | the vault, the module `mesh-enrolment` |
| `mesh-connectivity-contracts` (mesh-networking repository, ADR 0636 §6) | Connectivity | `the-private-network` | `mesh-wireguard` |
| `mesh-operator-and-conversation-contracts` | Operator and conversation | `operator-channel` | the router |
| `mesh-contracts` | Core, and Config | `node-engine`, `mesh-broker`, `mesh-config`, `mesh-module-assigner` | the node-engine, the bus module, mesh-config, the assigner |
ADR 0641 retired the Placement domain: module assignment is Core's, so `mesh-module-assigner` is defined in
`mesh-contracts`, and the wants are Delivery's, so `mesh-wants` is defined in `mesh-delivery-contracts`; the module
`mesh-placement-contracts` is not created (ADR 0641 §1, §2). From research 085's milestone (design 47) the seat
`mesh-grants` is defined whole in `mesh-identity-and-access-contracts` (ADR 0636 §5), and the seat `git` gains in
`mesh-delivery-contracts` the verbs by which every delivery part reaches the forge (*The git seat*, below). How it is
checked: the indexer refuses a definition of these seats from any other contracts module, naming both (ADR 0636 §5),
and its domain check refuses a definition naming the domain Placement (ADR 0641 §5).
**How it is checked**: each of these definitions' conformance fixtures names every event's subject, the seat's and
the holder's own, and a case of the holder's whole-module test takes a fixture from the running holder (an event
it publishes) and matches it, subject and body, against the definition; the indexer's generator tests cover each
of the 25 (ADR 0634 §10, ADR 0635 §6). The builder and the indexer's own events hold no seat:
the builder's `numbered` and `old-form-closed`, the indexer's `manifest-accepted` and its family `indexer.views`
are those modules' own, declared in their own manifests, which
ADR 0613 does not govern, and no seat definition carries them. Every other seat, the foundation's included, has its definition on the bus in the same milestone, all
at once (ADR 0608 §2), holding the seat list's grain: its name, whether it is held once or on every node, its verbs
with their inputs and its provision. Its file is written by hand in its domain's contracts module, or in
`mesh-contracts` where its domain has none; the files the registry branch generated once from mesh-sdk's seat list
are their starting text, and nothing generates them again (ADR 0635 §2). Its messages, events and state layouts are
added to a seat's definition when its domain's contract next changes, which moves that domain's seats whole into its
contracts module in that change (ADR 0613 §3); which domain moves when is tracked by issue 167 (ADR 0613 §4).
- **Two seats renamed** (ADR 0634 §9): `mesh-placement` becomes `mesh-module-assigner`, and `mesh-enrolment`
becomes `mesh-node-enroller`; the module `mesh-enrolment` keeps its name. Their definitions, the subjects of their
verbs and events, every holder's claim, every caller's `invokes` and grant, the claude-code instructions' text, the
mesh MCP server's description and the glossary take the new names in the same delivery group, and nothing answers
to the old ones: a call on an old subject fails loudly, naming the seat. **No state moves**: a state's bucket is
the holder's (`<holder>_<state>`), so placement keeps every revision. The old subjects' events keep only their
stream's own retention. **The order is the module graph's**: the grants of the new subjects are an ordering edge
that comes before the holders move, and the callers come after the holder that serves the renamed verbs; an older
node-engine takes its new build through `mesh-wants`, which is not renamed. **A machine enrolling with an installer
built before the rename** asks the old enrolment seat's subject, which no holder answers: its enrolment fails
loudly, naming the seat, and nothing joins; it enrols with an installer of the new build. **How it is checked**: the
manifest validator refuses a claim, an `invokes`, a `consumes` or a grant naming a seat no definition names, so an
old name fails
(ADR 0634 §10); the acceptance after the delivery is that `mesh-module-assigner.placement` answers and
`mesh-placement.placement` is not found, placement's revisions are those it had before, and no `seat-holders` key,
finding or condition stays under an old name.
- **Every part learns the seat list from the bus, through the seat `mesh-catalog`** (ADR 0635 §1, as ADR 0636 §4
changes it). The node-engine, the tool runner, claude-code, the manifest validator and every other reader of the
list of seats call, through mesh-sdk's runtime, the verb `seat-definitions` of the seat `mesh-catalog`: without a
seat it answers every trunk's seat with its digest, with a seat that seat's definition. They hear the seat's events
`seat-definition-changed` and `seat-definition-removed`, said for the trunks' definitions. A reader names no
module: it calls the seat, never its holder, the indexer, by name. A running part acts on what is merged, so a
reader takes no definition from a pull request's head; the manifest validator inside a delivery group's check is
the one reader that reads the members' heads. No package carries the seat list, and mesh-sdk imports no contracts
module.
**A reader without the list acts on nothing new, and keeps running what it runs** (ADR 0636 §1, changing ADR 0635
§3). A reader that starts without the list applies, composes, grants and judges nothing new, except for the
bootstrapped modules below; it keeps running what it already runs, says in its health what it waits for, and a
node-engine after a reboot starts again the compositions it last applied. Each node writes one finding when its
readers have waited five minutes (ADR 0564 §2); the condition keeper raises it as a condition, which reaches the
operator as every condition does, and clears it at the first full read. A reader away longer than the events'
retention reads the whole list again. A check that cannot read the definitions it needs fails, naming them, rather
than passing without them.
**The bootstrapped definitions** (ADR 0636 §2, changing ADR 0608 §2) are the foundation's seats (research 075) and
`mesh-catalog`, `mesh-conditions` and `operator-channel`: every reader carries them from its start and acts on their
holders without the list, so after a full restart the indexer comes back and serves the list, and the condition
keeper and the router, with the channels it sends through (ADR 0259), carry a wait's condition to the operator.
**A missing or refused definition blocks new claims only** (ADR 0636 §3): when a seat's definition is missing,
removed or refused, readers refuse a new claim, `invokes` or grant of that seat, and never stop or remove a module
that already runs holding or calling it.
**Each reader's list has two tools** in mesh-sdk's runtime bundle (ADR 0636 §7): one reads which definitions a
reader holds, their source and digests and what it waits for; the other, granted to the operator and the healers,
makes it read the list again now.
**How it is checked** (ADR 0635 §6, ADR 0636 §8; *Acceptance tests*, below): the node-engine's whole-module test,
against a stand-in of the seat, acts on nothing but the bootstrapped modules without the list and on all once it
answers, and starts its last compositions after a restart; the indexer's whole-module test starts with only the
bootstrapped seats and answers `seat-definitions`; with the indexer kept down, the node's finding is written after
five minutes and the condition keeper raises it to the operator; a refused definition stops no running module; a
reader refuses a pull request head's definition; mesh-sdk's unit test refuses a module's name in the runtime; both
tools answer.
- **The generator** is each language's build module's: the Go build module's package for Go, the Node and TypeScript
build module's for TypeScript (the logic is the module's whose work it is). The generator's version is a build input
of every contracts module, so a changed generator rebuilds each contracts module once (ADR 0603 §3). Each build
module's test step regenerates and compares, for every module carrying the contracts marker (below): a committed
file that differs, a file missing, or a file in the package tree the generator did not write fails the step, naming
each path.
- **Out of step is learned from events, and the indexer runs no generator.** For a pull request, the check's step
fails with the out-of-step paths; the checker puts them in its verdict and emits its declared event `verdict`
carrying them (its `preview.written` is declared in the same change). For a trunk, the builder's build-end event
carries a trunk build's out-of-step paths, which is how a generator's change is seen, since no check of the
contracts module runs then. The indexer consumes both and writes the trunk's finding.
- **On the bus**: the indexer reads every seat definition from every trunk and open pull request, from the forge
bridge's messages, and keeps them in its own state `seat-definitions`, keyed by seat and source (the trunk, or a
pull request's head), as their only writer; a definition is at most 256 KiB. As the holder of `mesh-catalog` it says
`seat-definition-changed` (seat, source, digest), kept seven days, only when a digest changed, and
`seat-definition-removed` when a trunk's definition goes, both as events of the seat (ADR 0636 §4, changing ADR 0608
§3). **Only the trunk's entry is the seat's contract**: the readers of the seat list read the trunks' through
`seat-definitions`; the contracts modules' builds and the review ask `indexer_seat_definition`, which answers the
trunk's unless a pull request's head is named, and says which it answered. **The manifest validator inside the
delivery group's one check reads the seat definitions at every member's head** (ADR 0636 §4), so a group that
changes a definition is judged by what it merges. A
pull request's entries are removed on the forge bridge's event that it closed or merged. **A seat of the mesh's own
contracts is defined only in its domain's contracts module, and a move there is a handover** (ADR 0636 §5, changing
ADR 0608 §2): a definition names its glossary domain; the indexer takes a seat of the mesh's
own contracts only from that domain's contracts module, `mesh-contracts` for a domain with none, and refuses a
definition of it from any other module, with a finding naming both. A definition moving from `mesh-contracts` to its
domain's contracts module is a handover: the indexer keeps the last trunk definition until the new one is merged,
in either order of the two trunks. A seat defined outside the mesh's own contracts is taken from its own contracts
module (ADR 0625 §7). A definition
that does not parse fails its pull request's check; the trunk's stays. The merge is the only approval. The
foundation's own definitions are bootstrapped before the bus exists, as the bootstrap of a fresh mesh has it
(research 075).
- **Findings**, written by the indexer, from which the condition keeper raises and clears the conditions (ADR 0564): a
trunk's seat definition that does not parse, `seat-definition-unparsed` (a pull request's is its check's verdict, not
a condition), cleared by a definition that parses; generated types on a trunk out of step with their definition,
`generated-types-out-of-step`, cleared only by a clean build end of the same contracts module, per module and not per repository, since several
contracts modules share one; a seat defined outside its domain's contracts module, naming both modules (ADR 0636 §5). **Tools** of the indexer: `indexer_seat_definition`
(read one), `indexer_seat_definitions_again` (read a trunk's or a pull request's again now) and `indexer_out_of_step`
(the generated types out of step).
- **Contracts modules.** A seat's contract is published by a contracts module, which holds the seats' definitions,
their contracts and their client packages and no seat (ADR 0625 §4, §5); the seat's holder implements the contract.
**The contracts marker**: a contracts
module's package artifact names the directory of its definitions in the field `generated-from`, and the manifest validator
refuses a module carrying it that claims a seat, naming the seat. **Each domain whose seats the milestone defines
whole has a contracts module of its own, created in the milestone** (ADR 0634 §7, ADR 0635 §5; the table above);
`mesh-contracts` keeps the Core domain's seats, `mesh-config`, and the definitions of every seat whose domain has
none yet. Each declares its packages in Go and TypeScript, cut per seat as *Where a seat's code goes* says, in
package series `0.1`, published through the two package seats (design 51) and taken by its users by version, or
inside one delivery group by the group's head.
- **A contracts module and a domain SDK are modules like any other, found by their manifests** (ADR 0634 §8).
`mesh-delivery-contracts` and the Delivery domain's SDK are folders of the mesh-delivery repository, and the other
domains' contracts modules folders of the mesh-contracts repository, but the Connectivity domain's:
`mesh-connectivity-contracts` is a folder of the mesh-networking repository, beside `mesh-wireguard`, which holds
`the-private-network` with its manifest and peer generator its own (ADR 0636 §6, changing ADR 0634 §8). Each is a package-only module, as mesh-sdk long
is: published from its trunk, rebuilding the modules that use it like any package (ADR 0573 §2, §3), and delivered
to no node. A step of the build or the delivery that treats a module in a folder unlike one at a repository's root
is a gap of the build and delivery system, fixed in the milestone, never worked around. **How it is checked**: the
delivery group's one check builds and publishes every new module from its folder, and the build modules'
regenerate-and-compare step runs on every contracts module the milestone creates (ADR 0634 §10).
- **mesh-sdk takes no seat-specific type** (ADR 0613 §5): a unit test of mesh-sdk refuses an exported identifier or
string constant naming a seat the definitions define. The two checks every change already runs, the
build modules' compare step and that test, are where a stray file and a stray type are refused; mesh-catalog's own
checks cannot see another repository.
- **A contracts cycle holds nothing.** `mesh-delivery-contracts` is published through the forge module's package seats
and built by the Go and Node build modules, and the forge module, `distribution` and the Go build module import it. A
package edge and a build module's edge are triggers, never waits, so that cycle starts and is not refused (ADR 0614
§6), and a package is numbered per source revision, so the triggers end (design 51 *The versions*).
- **Older readers keep working**: a moved contract keeps its subjects, keys and message shapes, and a reader ignores
a field it does not know. The two renamed seats are the exception, and every one of their users moves in the same
delivery group (ADR 0634 §9, as it changes ADR 0608 §6).
- **A vendor-specific** provision or contribution lives in its module's own extension package, published by version
like any package; a later holder of the seat may publish its own the same way. A seat's definition stays
vendor-neutral, which the seams review checks.
### Where a seat's code goes
*Added 2026-11-04, widened 2026-11-05* (ADR 0625, ADR 0627, ADR 0634, ADR 0635; the operator, 2026-11-04: "many small
sdk's instead of 1 large sdk", and shared logic lives with its owner). A module is rebuilt only for what it uses, so
shared code lives with its owner: a seat's code is cut per seat, in the contracts module of its domain; what one
module owns is that module's own package; what one domain's modules and seats share is that domain's SDK; mesh-sdk
keeps only what modules or seats of more than one domain use.
```
contracts module of a domain (mesh-<domain>-contracts; mesh-contracts for Core, mesh-config, and a domain with none)
├── definitions and fixtures written by hand, every seat's at its grain, the generation's source
└── one package per seat defined whole, per language generated: types, subjects, state layouts, a typed call per verb
└── its client package (where callers or holders share logic over the seat) written by hand
domain SDK (mesh-<domain>-sdk) written by hand: logic and forms one domain's modules and seats share, owned by no module
a module's own package written by hand: logic and state layouts one module owns, where it holds no seat for them
mesh-sdk the runtime every module uses, and what modules or seats of more than one domain share;
it imports no contracts module and reads the seat list from the bus
```
- **Shared code lives with its owner** (ADR 0634 §1), and the first home that fits is the one: a seat's types, the
layouts of its states and the messages of its verbs and events are its contract's, and the logic its callers or
holders share is its client's; logic and state layouts one module owns, where that module holds no seat for them,
are that module's own package, which other modules import from it; logic and forms no seat carries and no one
module owns, the seat-neutral parts seats' clients or holders share included, are their domain SDK's when only one
domain's modules or seats use them, and mesh-sdk's when more than one domain's do or every module does.
- **Each seat defined whole has a package of its own, per language** (ADR 0625 §1, ADR 0627 §1): its generated
contract, and a module imports only the packages of the seats it uses, so a change of that seat rebuilds only its
users. Where the fingerprint counts a language's module rather than its package, each such package is a module of
its own in the same folder. In the milestone of ADR 0612 these are the 25 seats above.
- **A seat at the seat list's grain has its definition and no package** (ADR 0635 §1, §2): its readers learn it
from the bus, and a change of it rebuilds nothing.
- **The imports run one way per package** (ADR 0625 §3, as ADR 0634 §2 and ADR 0635 §1 change it): a generated
package imports nothing of mesh-sdk; mesh-sdk imports no contracts module; a domain SDK imports only mesh-sdk and
contract packages; a client imports its own seat's contract, mesh-sdk's runtime and its own domain's SDK. Where a
language's module is the unit of import, the clients are a module of their own in the folder, so the two never
form a cycle.
- **A client package** (ADR 0625 §4, as ADR 0634 §2 changes it) is the one hand-written package a seat may carry
beside its contract, owned by the seat's contracts module, never by a holder. It holds the logic two or more
callers or holders would otherwise each write over that seat, and only where such logic exists; it uses only its
own seat's contract, mesh-sdk's runtime and its own domain's SDK, never another seat's client, and never names a
holder, a module's tool or a module's subject. A seat's types and typed calls are its contract's, never its client's.
- **A domain SDK** (ADR 0634 §2) is a package-only module, `mesh-<domain>-sdk`, named after the domain's heading in
the glossary, which may sit in a repository with that domain's modules. Its importers are its domain's modules and
the holders and callers of its domain's seats; it imports only mesh-sdk and contract packages, names no module's
tool or subject, and holds no seat's type or state layout and no state layout one module owns. The first is
`mesh-delivery-sdk`, in the milestone of ADR 0612: the moving of a file in parts (below), the build steps' and the
composer's shared logic, each made seat-neutral over its callers' types (ADR 0635 §4), and the rules the Delivery
domain's modules share.
- **A module's own package** (ADR 0634 §1) holds what that module owns and no seat carries: its state layouts that
other modules read and the logic over them. Its fingerprint counts only that package's files (ADR 0593 §2), so a
change elsewhere in the owner module rebuilds none of the package's importers. Another module of the same repository uses it by path, from the same
commit (ADR 0609 §2); a module of another repository uses it only by its name and a published version (ADR 0573
§1), so the owner's manifest declares it as a package artifact (ADR 0606 §4).
- **The clients of the milestone of ADR 0612** (ADR 0627 §1, ADR 0634 §4, ADR 0635 §4), each moved out of mesh-sdk
with every importer in the same delivery group, mesh-sdk keeping no copy and no alias: `operator-channel` (an ask
and its awaited answer, mesh-sdk's `asks` and `asker`); `mesh-vault` (the calls its callers share; mesh-sdk's
hand-written vault types are replaced by the vault contract's, not moved into the client); `node-engine` (the
checks of its compose-candidate ask and answer); `npm-package-registry`, `go-package-registry`,
`container-image-registry` and `oci-artifact-registry` (publishing a file in parts, one client per seat, each
binding the Delivery domain's SDK's transfer to its own seat's verbs and types); `mesh-hand-acts` (recording a
hand act); and `mesh-provisioner` (mesh-sdk's provider runtime and its harness, which only providers use).
`the-private-network` has none: the layout of the value it provides, which holds no key material, is the seat's
contract in `mesh-connectivity-contracts` (ADR 0634 §1), the private network's manifest and peer generator are
`mesh-wireguard`'s own, and the seat has a client only where its callers share logic (ADR 0636 §6, changing ADR 0635
§4).
- **Moving a file in parts is seat-neutral, in the Delivery domain's SDK, on both sides** (ADR 0627 §2, as ADR 0634
§3 changes it): it serves only the four registry seats, all of the Delivery domain. The sending side and the going
on from what a holder already holds, and the holder's side (taking the parts into the holder's own objects,
checking their hashes, dropping an upload left idle, saying when its objects fill), name no seat, verb or seat's
type and take subjects, shapes and checks from their caller. Each registry seat's client binds the sending side,
and each holder (the distribution module, the forge's two package seats) binds the receiving side to the seat it
holds, so no holder copies another's transfer.
- **In a contracts module's published packages every file outside a client is generated** (ADR 0625 §5): the build
modules' regenerate-and-compare step fails any other hand-written file there, naming it. The definitions and their
fixtures sit outside the published packages and are not judged by that step.
- **The contracts modules follow the glossary's domains** (ADR 0625 §6, ADR 0634 §7): a domain's seats move into
`mesh-<domain>-contracts`, named after the domain's heading in the glossary, when that domain's seats move; a seat
of the Core domain, or of a domain with no contracts module yet, is in `mesh-contracts`. The seven the milestone of
ADR 0612 creates are in *A seat's definition*, above; the order of the other domains stays issue 167's (ADR 0613 §4).
- **A seat defined outside the mesh's own contracts has a contracts module of its own** (ADR 0625 §7), which may
share a repository with its first holder; its definition, contract and client never live in the holder module, so
another module can hold the seat by importing only the contract, and the holder can be retired while the contract
stays.
- **A role only one module could ever play is no seat** (ADR 0625 §8): it is that module's own tools and events,
reached by the module's name (ADR 0596 §2). The test is whether another module could take the role over, not how
many hold it today, so every seat in force stays a seat, `mesh-broker`, `node-engine` and `route-proxy` included.
- **mesh-sdk takes only what modules of more than one domain use** (ADR 0625 §9, as ADR 0627 §2, ADR 0634 §1, §4, §5
and ADR 0635 §4 change it): the bus connection, the tools bundle's runtime, state and its helpers, the answer of a
module's health tool, the `Membership` type with the names its runtime reads to learn its own membership, the
package `views` (which takes its hand-act recorder and its finding writer from its caller), `outward`, the parts of
`forgekey` that modules of more than one domain use, and a seat-neutral part seats of more than one domain share,
as long as it names no seat, verb or seat's type. In the milestone of ADR 0612 its `go/delivery` is moved whole and
deleted, each of its names to the home this section gives it, and every other part naming a seat or a holder's
state leaves for its owner, every importer moving in the same delivery group; after it, mesh-sdk holds no package,
type or function over a seat defined whole, and no seat list (ADR 0627 §4, ADR 0634 §4, ADR 0635 §1).
**How it is checked** (ADR 0625 §11, ADR 0627 §3, ADR 0634 §10 and ADR 0635 §6, all built in the milestone of ADR 0612):
| Rule | Checked by |
|---|---|
| One package per seat defined whole | a unit test of the generator (one program in the Go build module, emitting both languages and run by the Node build module too) emitting one package per seat defined whole, covering each of the 25; the manifest validator refuses a contracts module whose package holds the generated files of two seats defined whole, naming them |
| The imports run one way | a mesh-sdk unit test that no package of mesh-sdk imports a contracts module; the generator tests check no generated package imports mesh-sdk; a domain SDK's importers and imports by the review below |
| mesh-sdk holds no logic over seats, and no part that left it | mesh-sdk's package-list test refuses by name `go/delivery`, `asks`, `asker`, the vault's types and its shared calls, the compose-candidate checks, the provider runtime, and every other package or part ADR 0634 §4 and ADR 0635 §4 move; its seat-naming test refuses the defined seats' names in mesh-sdk, so a copy kept without importing a contract fails too |
| A client uses only its own seat's contract and its domain's SDK | the catalog check of a client: its own test refuses a client importing another seat's client or another domain's SDK, and one naming a module's tool or subject, and passes every client of the milestone |
| The parts transfer is seat-neutral | its unit test, in the Delivery domain's SDK, runs both sides against two different subjects and shapes the caller gives, and the seat-name check holds it seat-neutral; the review refuses a holder that takes parts in without it |
| Only a client is written by hand in a published package | each build module's regenerate-and-compare test fails on a hand-written file outside a client package and passes a contracts module holding its definitions and fixtures; the step runs on every contracts module |
| A module in a folder is built and published as one at a root | the delivery group's one check builds and publishes every new contracts module and the domain SDK from its folder, and `mesh-wireguard` and `mesh-connectivity-contracts` from the mesh-networking repository |
| A seat is defined only in its domain's contracts module | the indexer refuses a second module's definition of a seat, with its finding naming both, and a move from `mesh-contracts` holds in either merge order (ADR 0636 §5) |
| A holder keeps its seat's states | the manifest validator refuses a holder whose `requires` does not declare each of its seat's states with the same local name and template |
| A contracts module holds no seat; a role only one module could ever play has none | the manifest validator's refusal of a contracts module that claims a seat; review both ways: a new seat definition's pull request is asked whether another module could take the role over, and a module's pull request is asked the same of each new tool or event family other modules call |
| Each new exported name sits where this section puts it | the review of every pull request (playbooks 07 and 08, the code reviewer's definition) asks the home of each new exported type, field, constant, package or module against this section, a domain SDK's importers included |
**Acceptance tests**, each at its level (unit, or a case of the module's one whole-module integration test against the
real bus and store) and in its repository. The move raises no repository's test count, since mesh-delivery is already
over its budget: each case below replaces or joins an existing one.
| Test | Level | Repository |
|---|---|---|
| research 081's 14, 21, 25 and 26 | as research 081 sets them | as research 081 sets them |
| mesh-sdk's seat-naming, import and package-list tests; no module's name in the runtime | unit | mesh-sdk |
| a reader of the seat list, against a bus the test gives: a reader away longer than the events' retention reads the whole list again; a reader refuses a pull request head's definition; both list tools answer, the re-read one only for the operator and the healers | unit | mesh-sdk |
| the node-engine, against a stand-in of the seat `mesh-catalog`: without the list it acts on nothing but the bootstrapped modules, then on all once the stand-in answers; after a restart it starts its last applied compositions again; a refused definition stops no running module | a case of the node-engine's whole-module test | `mesh-host` (the node-engine's repository) |
| the indexer starts with only the bootstrapped seats and answers `seat-definitions`, without a seat (names and digests) and with one; with the indexer kept down, the node's finding is written after five minutes and the condition keeper raises it to the operator | cases of the indexer's whole-module test | mesh-delivery |
| a trunk definition that does not parse writes its finding and a fixed one clears it; a trunk build end naming out-of-step types writes the finding, a clean build end of another contracts module of the same repository leaves it, and a clean one of the same module clears it | a case of the indexer's whole-module test | mesh-delivery |
| `indexer_seat_definition` answers the trunk's entry while a pull request holds another; a pull request's entries go when it closes; a definition over 256 KiB is refused; a seat-list-grain definition is read from a contracts module like a whole one; a removed definition says `seat-definition-removed` and its seat takes no new claim afterwards; a second module defining a seat is refused with its finding, and a definition moving from `mesh-contracts` to its domain's contracts module holds in either merge order; each of the indexer's tools answers | cases of the indexer's whole-module test | mesh-delivery |
| the manifest validator refuses a claim, an `invokes`, a `consumes` or a grant naming `mesh-placement` or the seat `mesh-enrolment`, and a holder missing one of its seat's states in `requires` | unit | mesh-delivery |
| the checker's `verdict` names out-of-step files; the group's check reads seat definitions at every member's head | a case of the checker's whole-module test | mesh-delivery |
| each build module's compare step fails on a differing, a missing and an unwritten file, naming each, per contracts module | unit | mesh-catalog |
| a change in one folder module rebuilds and publishes only it and its importers; a module with both a deployed and a package artifact (the grants part or disk-load) is built, delivered and published | a case of the Go build module's whole-module test | mesh-catalog |
| after the delivery, `mesh-module-assigner.placement` answers and `mesh-placement.placement` is not found; placement keeps its revisions; no `seat-holders` key, finding or condition stays under an old name | live, read through the mesh's tools | the mesh |
| `mesh-wireguard` is built and delivered from the mesh-networking repository | the group's one check, then live | mesh-networking |
## The foundation's seats
`mesh-controller`, `mesh-store` and `mesh-broker` name which assignment the mesh *itself* uses: the
@@ -312,6 +689,13 @@ difference: it receives a URL either way.
With the seat unheld, a build from the seat is refused and says why. External builds carry on.
**The seat's verbs, from research 085's milestone.** The delivery parts reach the forge through the seat `git`'s
verbs alone, never by the forge module's own tools: reads for the parts that declare them in their `invokes`, and
acts (a status, a comment, the delivery's view, a line on the commit's note, a merge) for the forge bridge alone, as
the holder's trusted value `callers` names; the forge's events (a pull request updated, merged or closed, the forge's
own webhooks, a repository writable again) are the seat's events. Design 47, *The verbs*, lists them with their
callers and how they are checked.
**Not yet designed:** a credential for cloning a private repository. The mesh's own repositories are
public. The natural place for a clone credential is a `secret` from the vault, and that is a decision
still to take.
@@ -326,7 +710,7 @@ checked as their tables say:
| Rule | Checked by |
|---|---|
| The set is closed, and every mesh entry names its decision | 0118: a unit test on the mesh's own entries. **The refusal of an unknown seat is at registration, not in the parser** — correction of fact, 2026-09-27: a module may hold a seat *another* module declares, which is the point of naming the seat and not its provider, so whether a claimed name exists is a fact about the whole catalogue and a manifest in isolation cannot be judged on it. Registration tests cover an invented name and a name another module declares; the parser still refuses a claim on the mesh's own `mesh-*`/`node-*` namespace and a scope that disagrees with a declaration in the same manifest. |
| The set is closed, and every mesh entry names its decision | 0118: a unit test on the mesh's own entries. **The refusal of an unknown seat is at registration, not in the parser** — correction of fact, 2026-09-27: a module may hold a seat *another* module declares, which is the point of naming the seat and not its provider, so whether a claimed name exists is a fact about the whole catalog and a manifest in isolation cannot be judged on it. Registration tests cover an invented name and a name another module declares; the parser still refuses a claim on the mesh's own `mesh-*`/`node-*` namespace and a scope that disagrees with a declaration in the same manifest. |
| The set is derived, and enumerating it is a query | 0118: the overview lists the mesh's own plus every registered module's, asserted against a fixture mesh. |
| `mesh-*` is the mesh's, and a module may not declare one | 0118: a registration test refusing a manifest that declares any `mesh-*` seat, naming the prefix. |
| Two modules cannot declare the same seat | 0118: a registration test; the second is refused and the first untouched. |
@@ -339,7 +723,7 @@ checked as their tables say:
| `secret` has one provider, the holder of `mesh-vault` | 0161: a second claimant of the seat is refused by name (`CanHold`); *correction of fact, 2026-10-01: no parser rule ever reserved the word, the seat does the work*. |
| A singular fact about machines is a placement of capacity one, refused by name | 0161: the private network command's test for a second hub; the store's unique index. |
| A holder of `node-uplink` is the dialect the machine runs | 0161: the node-engine reports `uplink-<manager>` in its profile with every report; a resolution test refuses the other holder naming the capability. |
| A seat's holder has the seats it needs beside it: the resolver configuration is refused at `assign` without the uplink held on its node, and the uplink's last holder cannot be taken from beneath it | 0220: seat-dependency tests on the definition, on a synthetic claimant, at assign, at unassign and at composition, and one reading the catalogue for the uplink's possible holders. |
| A seat's holder has the seats it needs beside it: the resolver configuration is refused at `assign` without the uplink held on its node, and the uplink's last holder cannot be taken from beneath it | 0220: seat-dependency tests on the definition, on a synthetic claimant, at assign, at unassign and at composition, and one reading the catalog for the uplink's possible holders. |
| A replicated seat has every holder on record and composes on each; a seat held once still refuses a second holder, on record or not; `--add` is refused for it | 0223: resolution and composition tests with two holders on record and a third machine, with two claimants and nothing on record, and on `mesh-store`; a unit test that only `mesh-dns-resolver` is replicated, surviving the store's rows; store tests keeping several holders once each, a handover leaving one, and unassigning one taking only its row. |
| A retired seat leaves the set once nothing claims it, from the compiled set and the store's table both | 0220: the closed-set test's count, and the store migration that deletes the row. |
| A node seat names only literal absolute paths; every path has one seat naming it outright, and stepping back does not cycle | 0287: a unit test over the compiled set; registration tests refusing a module's seat naming a path at mesh scope, with `${`, or already named by another seat. |
@@ -347,3 +731,22 @@ checked as their tables say:
| The uplink's step-back comes from the seat | 0287: the composition tests for the uplink and the resolver on one machine pass with the case in the collision check removed. |
| Holdings are derived, and the overview lists every seat | 0118: the `seats` command test, including an unheld seat. |
| A build source on the seat records no address; an unheld seat refuses only self-hosted builds | 0111's tests. |
## Reviewed by architectural domain
The amendment of 2026-11-03 for research 081 (ADR 0605 to 0611; *A seat's definition*) was reviewed with designs 51
and 32 by the four reviewers by architectural domain; the findings and what answers them are listed in design 51's
*Reviewed by architectural domain*. For this design: only the trunk's definition is the contract (security), the
indexer learns out-of-step types from the check's verdict event and runs no generator (seams), findings go to the
condition keeper (health), a pull request's entries are removed when it closes (running qualities).
The milestone's amendment (ADR 0612, ADR 0613, ADR 0614, ADR 0620: the seats defined whole, the rest at the seat
list's grain, the contracts marker, out of step learned from the checker's `verdict` and the builder's build end, the
seat list's one source), with the amendment of 2026-11-04 (ADR 0625, ADR 0627: ten seats defined whole, a package per
seat, a package for the seats at the seat list's grain in `mesh-contracts`, the seven clients and the seat-neutral parts transfer, *Where a
seat's code goes*), and the amendment of 2026-11-05 (ADR 0634, ADR 0635: shared code with its owner, the domain
SDK, a module's own package, a contracts module per domain the milestone moves, 25 seats defined whole with their
states, the seat list read from the bus, two seats renamed), with ADR 0636 (the seat list through the seat
`mesh-catalog`, a reader without it acting on nothing new, a seat defined only in its domain's contracts module, the
Connectivity domain in the mesh-networking repository; accepted at its version 15) and the lead's rulings on the first review by architectural
domain written in, await their final review, which rewrites this section.
@@ -47,7 +47,7 @@ A requirement has three parts:
**A contract is shared, not per module.** A database's contract is the database's, whoever requires
it. **The controller holds every contract**, one per provision name, declared where the provision is
defined in the catalogue. Today contracts are implicit in each provider's served fields; the first
defined in the catalog. Today contracts are implicit in each provider's served fields; the first
phase below makes them explicit, because nothing can be checked against a contract that is not
written down. A provider is checked against the contract it claims to answer. A module's own
specification may narrow a contract (a password of at least this length, a directory owned by this
@@ -213,13 +213,13 @@ a placed directory under the node's root. Each is this design's provider in the
placeholders have, not yet the one requirement form below; they are phase 1's first cases.
*Phase 3, in part (2026-09-30):* every definition's **own** data directory is placed; the conversion
moved no data, proven by resolving both catalogues with the controller's rule and comparing
moved no data, proven by resolving both catalogs with the controller's rule and comparing
([issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md)).
What the mesh writes *for* a module was still placed by the definition
([issue 174](../../04-ISSUES/174-the-meshs-own-files-for-a-module-are-placed-by-the-definition/00-report.md)),
the gap this design answered on 2026-09-26; built later the same day: a directory saying
`place: "mesh"` is `<root>/mesh/<module>`, a directory beneath a placed one states its path as
`${dir:<id>}/<rest>` and moves with it, and the same proof — both catalogues resolved and compared —
`${dir:<id>}/<rest>` and moves with it, and the same proof — both catalogs resolved and compared —
shows forty-eight definitions naming the paths they named before.
*A preference has a default; the operator's own value has none (2026-10-08,
@@ -323,7 +323,7 @@ value in a container's environment is refused when the definition is parsed, wit
containers, login, broker account and settings are keyed by it, as today, and a login still fits the
backend that keeps it (ADR 0049):
the bound is the one the offer of each provision it requires states, a keyless provision states none,
and an overflow is refused by the catalogue check before merge and left out of the provider's grants,
and an overflow is refused by the catalog check before merge and left out of the provider's grants,
reported, at composition — never a refusal of the provider's machine
(ADR 0225).
@@ -371,7 +371,7 @@ not rotated by the vault, which cannot make its replacement: an operator deliver
**Each requirement says how its recipient takes a new value.** It either *applies* it, through a
provisioner (a provider setting a login's password, the broker's provisioner updating an account, a
store's provisioner changing its own superuser), or *reads it at start*. Every module in the catalogue
store's provisioner changing its own superuser), or *reads it at start*. Every module in the catalog
reads its secrets at start, and none watches them. The node-engine already recreates a container when a file
it read at creation changes, its env-files and files mounted into it directly
([issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)).
@@ -433,7 +433,7 @@ as waiting, and nothing is delivered until the answer arrives.
| paths in resources, mounts, bindings, secrets and received files | host directory requirements, placed by the assignment |
| literals carried in a definition | operator requirements with defaults |
Each is retired only once nothing uses it. Until then both are accepted, and a catalogue test lists
Each is retired only once nothing uses it. Until then both are accepted, and a catalog test lists
the definitions still using the old form. That list shrinks to empty, and then the old form is
removed from the parser.
@@ -453,7 +453,7 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
is fixed in the node-engine already. *Ends when* nothing outside the vault generates a shared secret after
genesis, a lab consumer of analytics receives its site id, and a database credential rotates over
its two credentials, the consumer recreated by derivation, never without a working login, and its data intact.
3. **Definitions move, and seats move to assignments.** Every catalogue definition is rewritten, adopted
3. **Definitions move, and seats move to assignments.** Every catalog definition is rewritten, adopted
and running assignments placed where their data already is, and each claim becomes a seat the
module can hold, held by the assignment that holds it today. *Ends when* the list of definitions
using an old form is empty, the old forms are removed, and the store module runs on two lab
@@ -464,7 +464,7 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
| Rule | Checked by |
|---|---|
| Every requirement has one of the four provider kinds | The parser refuses any other. |
| A definition names no host path, node or mesh | The catalogue tests of ADR 0112. |
| A definition names no host path, node or mesh | The catalog tests of ADR 0112. |
| A module provider is chosen by named seat, pin, co-location, only one, a person's choice | Resolution tests for each step: a requirement naming a seat served by its holder even with another provider on the consumer's node, and refused when the seat is unheld; several candidates and none local, where assignment lists them with the seat's holder first and records the choice as a pin, and refuses without one. |
| A provider answers within its contract | A controller test: an answer carrying a field its contract does not name, or missing one it does, is refused and not delivered. |
| A module narrows a contract and never widens it | The parser refuses a module specification that loosens a contract's field. |
@@ -484,9 +484,9 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
| The controller and a node's host take the same path | 0113's tests: the controller's definition declares requirements and no own secret; a node's bus account is made by the vault and delivered sealed to that node. |
| Moving the vault or the broker is break-glass | A resolution test: an ordinary assignment moving `mesh-vault` or `mesh-broker` is refused, naming the procedure. |
| A secret that cannot be rotated says so | A vault test: rotating a secret marked not rotatable by the mesh is refused, naming why. |
| Only the controller reads the seat placeholder | A catalogue test, from phase 3: no definition uses the seat placeholder. |
| Only the controller reads the seat placeholder | A catalog test, from phase 3: no definition uses the seat placeholder. |
| Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. |
| The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. |
| The old forms retire | The catalog test listing definitions still using one. It must be empty before a form is removed. |
## Not settled here
+14 -14
View File
@@ -169,11 +169,11 @@ paper is wrong until there is a second mesh to find out.
The derivation refuses two things where they can still be named: two users with one name — the
server reads the file as one of them and which one depends on the order — and a module
assigned but absent from the catalogue, which would compose a user with no authority and fail
assigned but absent from the catalog, which would compose a user with no authority and fail
on its first publish with an authorisation error that says nothing about a missing manifest.
A user the mesh has minted no password for is *named* rather than dropped or written as a user
anybody is: an ordinary situation with an obvious remedy, and the caller decides whether a
partial file is worth writing. A seat's protocol is gathered across the whole catalogue, not
partial file is worth writing. A seat's protocol is gathered across the whole catalog, not
from one manifest, because a seat is declared by one module and held by another.
**Out, and what each needs.**
@@ -447,7 +447,7 @@ pays for itself furthest away.
derived to subjects, the three namespaces, permissions computed from a declaration, and a
manifest that contains no subject. Done in the controller's composer (permissions, streams,
consumers), in the runtime's client (subjects derived from the credential, never named by a
module), and as a catalogue test asserting all 72 manifests hold no subject — because the
module), and as a catalog test asserting all 72 manifests hold no subject — because the
rule held by construction, and a rule held by construction is one a later field breaks
quietly.
- [x] 3.9 **seats declared by modules** — the manifest now carries `seats` (name, scope,
@@ -470,7 +470,7 @@ pays for itself furthest away.
all: a holding is derived at resolution, never stored, so nothing recorded points at an old
name (recorded as a progressive insight on ADR 0126). A **kept** rename table tells a
manifest written against an old name what it became, because a module lives in its own
repository and may be registered long after the catalogue stopped using one.
repository and may be registered long after the catalog stopped using one.
**A seat and the interface it delivers are different names.** The `git` seat became
`mesh-git` while the `git` *provision* it delivers did not change, and the same for the
@@ -522,7 +522,7 @@ it, and the beds that need a mesh living on NATS can finally run.
(ADR 0129). Both sides
are behind a seam with an implementation per bus, and on the bus being built one publish does
what two did: the outcome is the role's own event, so the asker matches it by the id its
request carried, the controller records it and the catalogue places it in the graph. A builder needs a reply queue for nothing and a grant over nobody's inbox.
request carried, the controller records it and the catalog places it in the graph. A builder needs a reply queue for nothing and a grant over nobody's inbox.
Checked against a running server: the round trip; a third party on the role's event hearing
the same outcome the asker did, which is what the decision rests on; work leaving the queue
@@ -532,7 +532,7 @@ it, and the beds that need a mesh living on NATS can finally run.
The outcome carries the module name, because only the manifest says what was built and one
message now has three readers. A failed build names none: it produced no module version, and
the catalogue would otherwise place something that was never made.
the catalog would otherwise place something that was never made.
- [x] 4.3 an installation completes over the bus, with the same outcome as the path it replaces —
**the installer can raise it**: a foundation template that stands up the server, writes the
server's own settings and the mesh's first user list beside them, and starts a controller
@@ -573,7 +573,7 @@ it, and the beds that need a mesh living on NATS can finally run.
correct. It uses the client a module's runtime uses, so what a person may do is answered by the
same permission list that answers it for a module and an audit has nothing separate to read.
Three decisions in it worth keeping. It lists what the **catalogue** has rather than what this
Three decisions in it worth keeping. It lists what the **catalog** has rather than what this
credential may call: somebody seeing only their own tools cannot tell "not installed" from "not
yours", and those need different people to fix them. A failed call says which of three things
happened — nobody serves it, this credential may not, or the tool was slow — because the
@@ -597,10 +597,10 @@ it, and the beds that need a mesh living on NATS can finally run.
**The catch-up half needed nothing built, and that was the answer.** It existed because a queue on
the bus the mesh runs on today receives only what is published after it is bound, so everything
built before the catalogue existed was announced to nobody — and on a fresh mesh that is always
the foundation, because those are the things the catalogue needed in order to exist
built before the catalog existed was announced to nobody — and on a fresh mesh that is always
the foundation, because those are the things the catalog needed in order to exist
([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)). A
whole mechanism followed: the catalogue asks, the controller re-publishes.
whole mechanism followed: the catalog asks, the controller re-publishes.
A stream is a log and a consumer is a position in it. A consumer created later starts at the
beginning, so the builds are simply there — asked of a running server rather than assumed, since
@@ -636,8 +636,8 @@ left to be re-derived under time pressure:
| 1 | this one | prose; nothing deploys |
| 2 | the sdk | comments only, and no module rebuilds for it |
| 3 | the client library | it is what a module calls to emit, and it is where the subject is derived. Until it lands, a locally-named event is published under the local name itself |
| 4 | the catalogue | every manifest and every module's code, renamed together. Safe only once the runtime derives |
| 5 | the controller | **it refuses an old-style event name outright**, so landing it before the catalogue makes every unconverted module unregisterable |
| 4 | the catalog | every manifest and every module's code, renamed together. Safe only once the runtime derives |
| 5 | the controller | **it refuses an old-style event name outright**, so landing it before the catalog makes every unconverted module unregisterable |
| 6 | the node-engine | last, because nothing else waits on them |
Two properties make the sequence safe rather than merely ordered, and both are pinned by tests. A
@@ -682,11 +682,11 @@ healthy while reacting to nothing.
- [x] 5.4 **the old broker and everything that named AMQP leave the mesh** (ADR 0131,
superseding ADR 0127): the two modules that
required `amqp` are removed, the broker's module is unassigned and removed (**done 2026-09-28**; the predecessor's own tooling, which rode the same adopted broker, went dark with it, as ADR 0130 accepted), registration refuses
a manifest that provides or requires `amqp`, and a whole-catalogue check asserts none does. Not
a manifest that provides or requires `amqp`, and a whole-catalog check asserts none does. Not
a retirement condition — a decision, taken, with the operator's "I don't care if the predecessor
breaks" on record (ADR 0130).
Retiring with it: the build outcome's second announcement under the module's own name, which
existed only so a catalogue deployed before the rename and one after both heard it.
existed only so a catalog deployed before the rename and one after both heard it.
> **The remote tooling goes with it too.** The predecessor's own mesh talks over that broker, so
> shutting it down ends the path that reaches this installation's machines from a workstation.
@@ -5,7 +5,7 @@ code:
- mesh-controller internal/inventory
- mesh-controller internal/catalogue
- mesh-controller cmd/mesh-controller
updated: 2026-10-17
updated: 2026-11-06
decisions:
- "ADR 0181"
- "ADR 0182"
@@ -174,7 +174,7 @@ the client/identity side and the CA are the open pieces.
`ssh-client` template can emit a `Host` block per node with the right login name. Composed
end-to-end in the controller's tests.
**Written but not shipped:** the `ssh-client` catalogue module itself exists on a branch of the
**Written but not shipped:** the `ssh-client` catalog module itself exists on a branch of the
module repository; its pull request was closed with a hold until this design is deployed, and
nothing has deployed it since. The predecessor's generator still writes every workstation's ssh
client blocks today — which is where [issue 172](../../04-ISSUES/172-the-ssh-client-block-matches-one-spelling-of-a-machine/00-report.md)
@@ -209,7 +209,7 @@ alias and its trust, and a fresh machine has no operator dotfiles at all — the
service and leave the human unable to work on the box.
**Why not build it reflexively:** it is a real addition to the node model, the resource model, and
the seat set, and must be gotten right. The mechanism half is now settled — ADR 0120 is what lets
the seat list, and must be gotten right. The mechanism half is now settled — ADR 0120 is what lets
the ssh files be templates with no controller format — so what remains to decide here is the
model:
@@ -2,7 +2,7 @@
layer: to-be
status: in-progress
code: [mesh-controller]
updated: 2026-10-17
updated: 2026-11-06
decisions:
- "ADR 0238"
- "ADR 0221"
@@ -30,7 +30,7 @@ webhook".
seat or schema:
1. `module moved <module> <commit>` — tell the mesh its source advanced (the controller repo has no
trigger, so this is manual; the catalogue's webhook does it automatically — see below).
trigger, so this is manual; the catalog's webhook does it automatically — see below).
2. `build --behind` (or `build <repo> [--ref] [--path <subdir>]`) — the builder rebuilds and
records the new image.
3. The mesh **reconciles on its own**: the module's declaration now names the new image, the next
@@ -41,7 +41,7 @@ seat or schema:
**A breaking change** — a manifest schema the controller parses differently (a fact's shape, a
seat's name), where the new controller cannot read the manifests the old one stored:
4. Land the code (controller + catalogue together — they are one change).
4. Land the code (controller + catalog together — they are one change).
5. Rebuild + deploy the new controller (steps 1–3). **The moment it is live it refuses the
still-old-shape stored manifests, and composition freezes for every node that runs an affected
module.** Running services are untouched; only new declarations stop.
@@ -63,7 +63,7 @@ automating this must solve.
### 1. The trigger today is HAL's, not the mesh's
Build-on-push works for the catalogue because its repository has a Gitea webhook pointing at
Build-on-push works for the catalog because its repository has a Gitea webhook pointing at
`http://host.docker.internal:9877/webhook/gitea` — and **that receiver is `hal-gitea-tools.service`**
(`~/.hal/modules/hal/gitea/tools/server.js`), a *predecessor* component. The nox builder consumes
build work; it does not receive Git events. So the mesh's own build pipeline currently rides on a
@@ -78,12 +78,12 @@ HAL service, and:
seat — `mesh-forge-trigger` or folded into the git seat's holder) that receives Git events and turns
them into build work over the broker, for **every** repository including `mesh-controller`. Replacing
`hal-gitea-tools` is the concrete first build. Its logic already exists to copy: match the pushed
repository (and changed paths, for a monorepo like the catalogue) against the build-context
repository (and changed paths, for a monorepo like the catalog) against the build-context
repository of every registered module, and rebuild the matches.
### 2. The builder validates too — and a breaking change deadlocks it
The builder embeds the same catalogue package the controller does, so **it validates a
The builder embeds the same catalog package the controller does, so **it validates a
manifest against its own compiled-in seat/schema set**. A breaking change therefore couples *four*
things, not two: the controller, the **builder**, every affected manifest, and every node's host.
This session's seat rename rebuilt the controller but not the builder, and the stale builder then
@@ -104,7 +104,7 @@ does not know. The old builder cannot build the new builder. Escapes:
Either way, self-update for breaking changes needs a **transition discipline** so a push does not
auto-freeze: the new controller (and builder) should accept the *old and new* shape together for
one release — deprecated aliases in the seat set, a schema that reads both — then a later release
one release — deprecated aliases in the seat list, a schema that reads both — then a later release
drops the old. With that, a breaking change rolls out on a push like any other: everything reads
both, the manifests migrate, the compatibility is removed. Without it, self-update would simply
automate the freeze.
@@ -126,7 +126,7 @@ automate the freeze.
Revision, ADR 0162. The
trigger exists: the forge announces a merge on the bus and the controller acts on it (ADR 0157 made
the build narrate; this makes the merge a plan). A module's dependencies are one relation in the
catalogue — `depends-on` edges of four kinds: stands-on, packages, built-by, declared. A merge takes
catalog — `depends-on` edges of four kinds: stands-on, packages, built-by, declared. A merge takes
what changed and everything reachable from it along those edges, sorts the set into tiers, writes the
plan to the store, asks the first tier and returns. Each outcome advances the plan; a tier whose
rolled-out modules a later tier is built by waits until the machines report them applied; a
+225 -12
View File
@@ -12,8 +12,16 @@ code:
- mesh-tools src/main.ts
- mesh-catalog modules/mesh-catalog
- mesh-tools node-tools/internal/runtime (a module's state, ADR 0201)
updated: 2026-10-12
- mesh-sdk (the typed bus-state entry in `requires` and its binding, ADR 0595)
updated: 2026-11-04
decisions:
- "ADR 0609"
- "ADR 0612"
- "ADR 0613"
- "ADR 0618"
- "ADR 0606"
- "ADR 0595"
- "ADR 0596"
- "ADR 0307"
- "ADR 0240"
- "ADR 0233"
@@ -62,7 +70,7 @@ onto this in step 3 of ADR 0116.
This is the load-bearing rule.
ADR 0112 says a manifest names no node, mesh or path. A transport address is the same class of thing: if
manifests held literal subjects, reorganising the subject space would mean editing every module in
the catalogue, and the mesh would have hundreds of copies of a decision it made once.
the catalog, and the mesh would have hundreds of copies of a decision it made once.
| declared | derived |
|---|---|
@@ -94,7 +102,7 @@ lives under the emitting module's own name and the consumer carries that couplin
**How the rule is checked, because it was not.** *Added 2026-09-27, same source.* Two checks, because
the mistake happens at two scales. Per manifest, at registration: an event is a local name, and the
old bus's form is refused with the name to write instead. Across the whole catalogue, as a test:
old bus's form is refused with the name to write instead. Across the whole catalog, as a test:
where a consumed event's emitter is present, it must emit that event. The second cannot demand a live
emitter for everything — a module lives in its own repository and may be installed long before the
one whose events it wants — so it says nothing about an absent emitter and everything about a present
@@ -117,7 +125,7 @@ caller already handles). The kind token is what makes `mesh.mod.*.event.>` a saf
first draft of this table had no token, which reads better and cannot be implemented.
**The test this must pass: the manifest survives the wire changing.** Reorganise the subject space
and every manifest in the catalogue is still correct. That is the property
and every manifest in the catalog is still correct. That is the property
ADR 0039 gave the sdk, applied to
declarations.
@@ -172,7 +180,7 @@ can be wrong.
**And one publish reaches three audiences without anybody's inbox being opened.** A build's outcome is
the seat's own event: whoever asked matches it by the id their request carried, the controller records
it, the catalogue places it in the graph. That is the fan-out a shared exchange gave for free, written
it, the catalog places it in the graph. That is the fan-out a shared exchange gave for free, written
as a subject the mesh derived instead of a topology somebody configured — and it is why a holder needs
no permission to publish into an asker's inbox, which is the one grant design 25 §4 refuses by name.
@@ -227,7 +235,7 @@ service" versus "one worker per machine".
| credential | sealed, per consumer | none | none | none | none |
| reply | — | none | none, or an event later | a report | awaited |
| retention | — | age and size | work queue, explicit ack | **last per subject** | none |
| declared | `provides`/`requires` | `emits`/`consumes` | seat `accepts` / `uses` | the mesh's own; a module's `state` / `reads` | `serves` |
| declared | `provides`/`requires` | `emits`/`consumes` | seat `accepts` / `uses` | the mesh's own; a module's bus state in `requires` (ADR 0595), the old `state` / `reads` until it moves | `serves` |
**Job** is the one ADR 0041 had no room
for. Its table has events at 1:many and provisions at 1:1; a module submitting work to a service
@@ -249,7 +257,7 @@ registered for every machine, sent as an event, never reached a machine assigned
consumer did not exist yet when the event passed — and a licence binding sent as events replays a
week of rotations where only the latest matters. So a module names the state it **owns** with
`state`, and another module's it **reads** with `reads: <module>.<name>`. Each is a key-value bucket
the controller creates from the catalogue, mesh-wide, existing from registration so a reader can
the controller creates from the catalog, mesh-wide, existing from registration so a reader can
watch before the owner runs anywhere ([design 25](25-the-bus-on-nats.md) §3). Every instance of the
owner writes; a reader reads and watches. A key may name a machine by the module's own convention;
the mesh keeps one bucket per name, not one per machine, because "every server, for every machine"
@@ -278,6 +286,42 @@ module's assignment** — what a module stored is data
(ADR 0030) — and one whose
declaration is gone is reported, never removed by the mesh.
**A module asks for its bus state in `requires`, and reads no other module's.** *Amended 2026-11-03, ADR 0595
and ADR 0596, from research 082.* The `state` and `reads` of the two paragraphs above, and the sections `logs`,
`streams` and `objects` beside them, are four vocabularies for one need: what a module keeps on the bus. They are
replaced by one form, a **provision** of the bus module, as a database is postgres's:
- **An entry of `requires`, in its typed long form** (ADR 0595 §1): the local name, the storage type, the template
(state, events, work queue, log, objects, ledger) and the template's options, within their bounds; a ledger also
names its type (its schema, its operations each with its ledger effect, its indexes). The templates, their fixed
settings and their options are [design 25](25-the-bus-on-nats.md) § 12.1. mesh-sdk's catalog package carries the
entry's type and its strict decoding; every rule that judges it lives in the manifest validator (ADR 0595 §8).
- **The binding is what the module receives**: the names on the bus for each local name, written into its
membership by the grants part once the bus module says `created` (design 25 § 12.2). The module still names only
locally (§ 1). A module whose binding is missing at start fails loudly, naming the entry.
- **One name, one form.** A module asks for each entry once: an old section and the new form are never both used for
one local name, and one name serves one storage type only.
- **No readers.** The new form has no `reads`, and the manifest validator refuses one (ADR 0596 §2). Only the owner's account
reaches its bus state. **A module's events are subscribed to; its data is asked for**: what another module needs
of a module's kept data it asks through that module's tools or seat verbs (§ 4's tool relationship, `serves`),
and what a module lets others hear is its declared events, heard through `consumes` (ADR 0363, unchanged; ADR 0596
§1). A bucket's layout is never a contract another module depends on; a tool or a declared event is.
- **A state may be read-only to the module's tools.** A state's declaration may carry `read-only: true`: only the
processes of the module whose `bus.state` names that state write it, and the module's tools read it, holding no
publish on its subjects. The manifest validator refuses a read-only state that no process of the module names. How it is checked:
the manifest validator's unit tests for the refusal, and the composition test that a tools bundle's account holds a read on the
state and no write. The bus module's state `spent` (design 25 § 12.3) is the first user. (A design limit of the
milestone's review; no record decides it yet.)
- **The move is a module at a time** (ADR 0595 §11, ADR 0596 §3), each when it is next touched, keeping its local
names and its old settings as options so its data stays. A reader moves after its owner: the owner declares the
event or gains the tool its readers need first, and the manifest validator refuses a moved module's subscription to an event
its owner does not declare. An old section is refused once no module but the foundation's uses it.
What follows for the paragraphs above: for a module that has moved, ADR 0201 §2 (another module's state read by
naming it in `reads`) no longer holds, and the bucket "every instance of the owner writes; a reader reads and
watches" becomes "every instance of the owner writes and reads". The runtime's `get`, `put`, `delete`, `keys`,
`watch` and `history` stay the calls of the template state.
**A module declares the data it holds.** *Added 2026-10-06,
ADR 0233.*
State on the bus is one kind of data; the rest lives on a machine's disks or with a provider, and the
@@ -326,7 +370,7 @@ Every shape above appears once, in order, and no step knows where the next one r
**A change lands.** The module holding `mesh-git` emits `pushed` — repository, ref, commit. An
event, because it is a fact about git and git's identity is the meaning.
**The change becomes work.** The controller consumes `pushed`, asks the catalogue which modules
**The change becomes work.** The controller consumes `pushed`, asks the catalog which modules
are built from that repository and path
(ADR 0069), and submits one
**job** per affected module to the `mesh-build-machine` seat. The builder stays simple: it builds
@@ -366,7 +410,7 @@ not to an address it was given at genesis. Held and retried while the store rest
chain above went dark at the moment it touched a machine: nothing said which version a machine now runs,
or that it refused to. The controller states those as facts under its own seat's namespace, when what
a machine runs changes rather than on every convergence pass, and anything that cares subscribes the way
the catalogue subscribes to `built` (ADR 0134).
the catalog subscribes to `built` (ADR 0134).
The facts are second-hand by design — one emitter, one ordering — and a machine that cannot reach the bus
produces none, so absence is not health.
@@ -437,6 +481,136 @@ meaning into shape wherever it can go — a required `channel` field is caught,
interpretation of an existing one is not. Saying so is better than implying the fingerprint is
complete, because a team that believes it is complete stops reviewing for the case it misses.
### A package, its series, and the series its users accept (from 2026-11-03)
(ADR 0606 §4, ADR 0609, from research 081.) This is the manifest's half of design 51's versions.
- **A package is declared as an artifact**: its name, the kind `package`, its language (`go` or `typescript`), its
paths and its **package series** (`0.1`). An image, a bundle or an archive may declare a package series the same
way; one that declares none is numbered in `0.1`. Nobody types a version: the builder counts it (design 51).
- **A package is named by its name in its own language** (a design limit of the registry milestone, 2026-11-04): a
Go package by the module path of its `go.mod`, a TypeScript package by the name in its `package.json`. The
indexer reads that name at each merge, the builder numbers the package under it, the package's registry seat
publishes it under it, and a user's `packages` names it so. One name per package across the mesh, because the
language's own tools fetch by that name and no other. How it is checked: the manifest validator refuses a name in `packages`
that no package of the shelf is named, or that a member of the change's delivery group does not name at its head.
- **Raising a package series** is a reviewed change of the package's manifest, for a change that breaks its users.
The checker's verdict names every change of a package series it sees, so the review of the change sees it.
- **A user names the mesh packages it takes in a section of its own, `packages`**, each with the package series it
accepts. It is not `uses`, which grants publishing on a seat's `accepts` subjects (§ 3); a package is not a seat. A
user builds, and is checked, against the newest version of that series; a publish in another series does not move
it, and moving is a reviewed change of the user's manifest. A user that names none accepts the series of the
package's first numbered version, so nothing is edited in today's users. The indexer's tool `indexer_package_users`
lists, per package, its users and the series each accepts, so a user left on an older series is seen.
- **Inside one repository** a module uses its sibling package by path, from the same commit, and waits for no publish.
- **A user outside the package's delivery group is not built before the merge**: it is built after it, when the new
version is in its series. A user that breaks fails its own build, is not wanted, keeps its previous build, and a
condition tells the operator. A group's members are built in the group's check, against the group's tagged version.
- **A contracts module's package** names the directory of the seat definitions it is generated from in the field
`generated-from`, the contracts marker (design 26).
- **The manifest validator reads the new words** (ADR 0578 §1): each package series well formed and only on the four kinds that may
carry it, a package naming `go` or `typescript`, and each name in `packages` a package some manifest declares. A
running manifest validator that does not know a word sets the manifest aside to wait, never refuses it, while a newer indexer
build is merged and not yet delivered; and a node-engine is handed a manifest using these words only once it
states the capability to read them (ADR 0614 §4).
- **Cost**: the new manifest fields are read through mesh-sdk's manifest types, so their change rebuilds every module
once; it is the one rebuild the milestone of ADR 0612 already takes for the contracts move (ADR 0612 §1, ADR 0613 §1).
### A config value renamed (from 2026-11-03)
(ADR 0609 §4.) A config value's declaration may name its old name, `renamed-from`. On the merge, mesh-config moves the
value it holds under the old name to the new name in its own state, without the operator giving it again, emits
`renamed` (module, old name, new name, when; its family `config.events`, kept seven days) and records it. This design decides what ADR 0609 leaves open:
- **Which values**: a module's own config values only, never a secret, never another module's value. A rename keeps
the value's trust: a value set without the operator's Approve stays untrusted under its new name (ADR 0348 §8), and
the checker refuses a rename that would change a value's trust.
- **How long the old name is read** (the **rename window**): until every node-engine whose node has a want of the
module reports applied a build of it that reads the new name. mesh-config hears the status view's `delivered`
events and, at its start and on each, asks its `node_haves` for every node, away ones included, since `delivered`
counts only the nodes present (design 47); until then mesh-config answers both names. A node away when the last
`delivered` was said should close its window when it returns, with no other delivery: mesh-config also wakes on
the node-engine seat's event `mesh.seat.node-engine.event.manifest-reads-changed`, said when a node's stated
capabilities change and when it returns after being away (ADR 0618 §4), as for the old seat name (design 51), and
on it reads that node's stated capabilities through the seat's verb `manifest-reads` (ADR 0618 §3) and its applied
builds through `node_haves`, never a watch of the node-engine's have state (ADR 0596). mesh-config declares
`seat:node-engine.manifest-reads` in its `invokes`: that is the grant the grants part composes, and with it the
grants part grants mesh-config the subscription to the event's subject (its unit row is in design 51). A
node-engine that does not yet serve `manifest-reads` counts as stating nothing: the window stays open and
the seat's verb `renames` names the node. ADR 0618 does not cover a
returned node that applies the module's new build only after its return event, since that build changes what the
module reads, not what the node-engine states: such a node closes the window on the next `delivered`, whichever
delivery says it. A node holding a window open
for seven days is mesh-config's finding naming the node, cleared when the window closes; `config_rename_close`
drops a node from a window by hand, recorded as a hand act.
- **The checker asks the seat, never the module.** The seat `mesh-config` gains the optional verb `renames` (the
open renames, and the nodes holding each window open), in its seat definition (design 26). The checker asks it for
every change carrying `renamed-from` or dropping one: a holder that does not serve it is refused by the tool runner
naming the verb, and the checker then refuses `renamed-from` naming that; while a window is open, the same answer
refuses dropping `renamed-from`, naming the nodes. A check that cannot ask fails, and says so. So no manifest uses
`renamed-from` in the milestone that brings it, whose running mesh-config serves no `renames`; the first that does
is its live case. The checker declares `seat:mesh-config.renames` in its `invokes`, so by the served-verb edge
(ADR 0614 §2) its new build waits until a build of mesh-config that serves `renames` runs healthy; no cycle closes,
since mesh-config waits only on the indexer's `indexer_config_readers`. The "not served" answer is then met only by
the running checker in the milestone's own check, and after a revert. `config_renames` names, per rename, the nodes
still holding the old name open.
- **A refused move** (the old name holds nothing, or a secret) is mesh-config's finding, from which the condition keeper
raises the condition, cleared once the value moved or `renamed-from` left the trunk's manifest. **Tools of
mesh-config**: `config_renames` (what moved, from which name, when), `config_rename_again` (move one value by hand
when a move was refused) and `config_rename_close` (above).
### A value no longer read (from 2026-11-03)
A config value or secret no manifest on any trunk reads any more is a **retiring value**. mesh-config learns it from
the indexer's tool `indexer_config_readers`, which it asks on each of the indexer's `manifest-accepted` events; the
indexer says `manifest-accepted` for every newly accepted manifest, its bus-state entries empty when it asks none (the
bus module reads an empty one as "this module asks nothing now", design 25 § 12.2). A tool the indexer does not serve
yet answers "not known", and nothing retires on it. It is marked retiring only once no node-engine reports applied a
build that reads it, as the rename window is (§ above). mesh-config then emits `retiring` (family `config.events`,
kept seven days) and asks the operator once, through the operator channel, whether to remove it, naming any node that
still read it until lately; it is removed only on the operator's Approve. **mesh-config holds no secret**, so the
retiring flow and its one ask stay mesh-config's, and on the Approve it removes a value itself and retires a secret
through the seat `mesh-vault`'s new optional verb `retire` (one module's own secret, by node, module and name), carrying
the operator's warrant, which the vault checks (ADR 0259 §6) before it removes anything. The vault's caller list for
`retire`, a trusted config value in ADR 0363's form, names the holder of the seat `mesh-config` alone, checked against
the caller the tool runner proved; the warrant is bound to the node, module and name it was asked for, so it retires
nothing else. mesh-config then emits `retired` on `config.events` (module, name, node, the ask), and the vault emits
`own-retired` (node, module, name, the ask's id, the caller it proved) beside its `rotated` and `own-replaced`, on the
module events stream (ADR 0363) under the vault's own subjects, kept as that stream keeps every module's events. An ask unanswered for seven
days is asked once more, and then written as a finding, cleared by an answer, a cancel, or the value read again; a
sleeping node holding a retirement open for seven days is a finding naming it. `config_retiring` lists what is
retiring, since when, and its ask, and asks it again or cancels it.
**Where mesh-config keeps it.** Its new state (`renames`, `retiring`, `findings`) and its stream `config.events` are
declared in its old `state` and `streams` sections in this milestone, as the builder's and the checker's new state is
(design 51 *The move*), so that neither mesh-config nor the deciding parts that ask it wait on the bus module's
process, which is delivered in the same milestone. They move to bus state when each module next moves (ADR 0595 §11).
`config-changed` stays on the shared events stream, unchanged, so its consumers do not move.
**How the rename and the retiring are checked**, as cases of mesh-config's first whole-module integration test (C20,
C24 below), of the checker's and of the vault's existing whole-module tests: the checker's ask of `renames` meeting a
holder that does not serve it refuses `renamed-from` naming the verb, one meeting an open window refuses dropping
`renamed-from` naming the nodes, and a check that cannot ask fails saying so; `retire` with the warrant removes the
secret and emits `retired` and `own-retired`, without it, with a warrant for another name, or from a caller not on the list it is
refused; a waiter on the vault's `rotated` or `own-replaced` does not wake on `own-retired`; a node that was away,
returning with a build that reads the new name, closes the rename window on its `manifest-reads-changed` event when
it applied that build before the event (ADR 0618 §4), and on the next `delivered` when it applies it after; `config_rename_close` drops a node from a window and records the hand act; a window held seven days and a
retirement held seven days each write their finding naming the node, cleared when it closes.
### The mesh delivery cases this design carries
Research 081 listed the mesh delivery cases; these are the ones it handed to a design, each with its acceptance test,
each a case of its module's one whole-module integration test unless it says live.
| case | what holds | acceptance test |
|---|---|---|
| C6, a package series raised | users on `0.1` keep the newest `0.1`; a reviewed change moves a user | builder: a `0.2.0` publish moves no `0.1` user, its check takes the newest `0.1` too, a reviewed change naming `0.2` moves it, a user naming no series builds against the newest `0.1` |
| C14, a provision's form gains a field | the provider serves it in a new version; the consumer reads it once rebuilt; an older consumer ignores it | mesh-sdk: a consumer built against the older form reads a provision with the new field |
| C18, a new required config value with no default | the check names the nodes that lack it; the want waits; the operator is asked once | live: a module gaining such a value waits on each node lacking it, the operator is asked once naming the nodes, and once given the module is applied |
| C20, a config value renamed | the value moves, keeps its trust, both names read until every node runs the new build | mesh-config: a renamed value is present under the new name with no operator input and its trust; the old name answers until the node-engines report the new build; dropping `renamed-from` early, or changing trust, is refused; a refused move writes its finding |
| C22, a secret only a person can give | the want waits; the operator is asked once what to give and where | live: a module requiring such a secret waits, one ask names it and where, and once given the module starts |
| C24, a config value or secret no longer read | it is retiring, the operator is asked, removed only on the Approve | mesh-config: with one node-engine still reporting a build that reads the value it is not marked retiring and no ask is sent; once that node reports the new build it is marked retiring, `retiring` is emitted and one ask reaches the operator; an ask unanswered seven days is asked once more, then written as a finding; the value stays until an Approve removes it |
## 9. Provisioning over the bus
Provisioning rides the bus, and the provider stops having an address.
@@ -482,7 +656,7 @@ addresses will believe a class of bug is fixed when it is untouched.
controller's address — recorded somewhere, at some moment, and wrong as soon as anything moved.
Now it publishes to `mesh.seat.mesh-controller.report` and the bus routes it to whoever holds the
seat. Nothing anywhere records where the controller is, so nothing can record it *wrongly*. The
same is true of the builder, the catalogue, the telegram sender. This class is not mitigated; it
same is true of the builder, the catalog, the telegram sender. This class is not mitigated; it
is gone, because the information is no longer stored.
**Something that is not on the bus: the address stays, exactly as before.** A module that requires
@@ -580,11 +754,11 @@ billing existing under that name.
## 12. How it is checked
- **A manifest holds no subject.** A catalogue test: no manifest contains a string matching the
- **A manifest holds no subject.** A catalog test: no manifest contains a string matching the
subject grammar. The rule is worthless if it is followed by convention.
- **A preparation is given what the module is given.** A composition test: what the preparation
entrypoint receives equals what the module's own code receives, asserted rather than written twice —
which is the drift a hand-written step invites, three times over in the catalogue today.
which is the drift a hand-written step invites, three times over in the catalog today.
- **A convergence that changed nothing says nothing.** Two identical reports, one emitted fact: what is
guarded against is a fact per minute per machine, which is a stream nobody reads.
- **Permissions are exactly the three namespaces.** A composition test per module: the derived
@@ -602,6 +776,45 @@ billing existing under that name.
- **A module reaches only the state it declared.** The composer's test: an owner's runtime may write
its buckets, a reader's may only read, and nothing else is granted; the runtime's test over a real
bus: a name not issued and a reader's write are refused with the reason.
- **Bus state is asked for once, in one form, and read by its owner alone** (ADR 0595 §8, ADR 0596). The manifest validator's
unit tests: a `reads` in the new form, a local name used twice across storage types or in an old section and the
new form, an unknown template and an option outside its bound are each refused, naming the field. Live: a moved
module receives its binding after `created`; another module's account reading its entries is refused; a moved
reader hears its owner's declared event and asks its owner's tool. The rest is [design 25](25-the-bus-on-nats.md)
§ 12.7.
- **State is current at once.** The runtime's test over a real bus: a watch hands the current values
without deletions, then an end-of-current marker, then changes. Live: a machine assigned after a put
reads it at start.
## Reviewed by architectural domain
The amendment of 2026-11-03 to ADR 0595, ADR 0596 and ADR 0597 (designs 25 § 12, 32 § 4 and 55 together) was
reviewed by the four reviewers by architectural domain side by side (playbook 01), over three rounds, and the lead
ruled on every finding. What was found, and what answers it:
| Domain | Found | Answered by |
|---|---|---|
| security | the author could be words the module chose; a person's dangerous tools had no role, mark or path to the process; the caller travelled as a plain field | design 25 § 12.3 (marks, the process's own tool subject, the identity seat's signed token), § 12.4 (the tool runner stamps the caller it proved; the move's authors marked imported) |
| seams | the bus module asked for its own events in the new form, which it would have to create itself (ADR 0595 §10); a person's tool had no join to the process; a verdict was written twice with no order | design 25 § 12.3 (its events on its old `streams` section; the process's own tool subject); design 55 § 2.1 (the `review` operation first, `reviews` caught up from it) |
| running qualities | overdue creation was undefined and raised by nobody; the database's rollback write lived in memory; a forward delivery after a rollback could miss acceptances and verdicts; no size estimate of the heads | design 25 § 12.2 (five minutes, the watchman, durable consumers, `check` at start); design 55 § 8.1 step 6 (`database.sequence`, the check at every start before the drop), § 2.1 (the heads' estimate) |
| health | the bus module's health would have fired on a quiet mesh; conditions had no clearing; `database-behind` had no clearing | design 25 § 12.2 and § 12.3 (each condition with what clears it); design 55 § 8.1 step 6 |
| events and logs | an event lost when the module stopped after `apply`; the bus module's events had no reader | design 55 § 1 (each event sent with its operation's sequence as id, the state `sent`); design 25 § 12.3 (`history` of one entry) |
| testing | the bus module had no test that starts it whole; live tests would have filled the live bus; the budget was not counted | design 25 § 12.7 (the nats module's integration test, live drift only by a manifest change, the budget paragraph); design 55 § 10 |
| upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 |
| tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 |
**Left to the operator**, because each changes an accepted record: a margin between raising and clearing the
condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of
its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate`
as well as `drop`. Each is noted on issue 1966.
The amendment of 2026-11-03 for research 081 (ADR 0606, ADR 0609; the package, rename, retiring and delivery-case
sections of § 8) was reviewed with designs 51 and 26 by the four reviewers by architectural domain; the findings and
what answers them are listed in design 51's *Reviewed by architectural domain*. For this design: a rename keeps a
value's trust (security), `packages` is a section of its own, not `uses`, and mesh-config hears the status view's
`delivered` events (seams), a value retires only once no node-engine runs a build reading it (security), and `renamed`
and `retiring` have a family and an ask that is asked again (events).
The milestone's amendment (ADR 0612, ADR 0613: the seat verb `renames` the checker asks, the vault's `retire`,
`manifest-accepted` for every manifest, mesh-config's new state in its old sections) awaits its own review by
architectural domain, which rewrites this section.
@@ -135,7 +135,7 @@ by side until nothing is bound to the old one.
*Amended 2026-10-07 (ADR 0246,
[issue 298](../../04-ISSUES/298-a-new-seat-verb-deadlocks-across-two-repositories/00-report.md)):*
**additive takes two steps when the seat and its holder are in different repositories.** That is the
case for every mesh seat whose holder is a catalogue module. §3 makes every promised verb a condition of
case for every mesh seat whose holder is a catalog module. §3 makes every promised verb a condition of
holding, and a claim may name no verb the seat does not promise. So a verb added to the seat in the
controller refuses today's holder, and a holder that serves it is refused by today's controller. Neither
change can land first. An added verb therefore lands in three steps, each its own pull request:
@@ -163,7 +163,7 @@ still a new version.
of the subject table.
- **An added verb lands in steps, and each step is checked (§7).** The controller's claim check refuses
a claim that serves a verb the seat does not promise, and a holder that misses a required verb. It runs
at registration, at handover, and in `module check`, which the catalogue's merge check runs over every
at registration, at handover, and in `module check`, which the catalog's merge check runs over every
manifest with the controller the mesh runs. So a step out of order fails the pull request that took
it. That an optional verb stays optional when read back from the store is held by mesh-controller's
`TestAnOptionalVerbStaysOptionalInASetReadFromTheStore` (#114), and that a seat's row gains a verb's
@@ -0,0 +1,387 @@
---
layer: to-be
design: 34
milestone: every-event
updated: 2026-11-11
code: [mesh-sdk, mesh-delivery, mesh-tools, mesh-catalog modules/claude-code]
decisions:
- "ADR 0670"
- "ADR 0671"
- "ADR 0286"
- "ADR 0367"
- "ADR 0043"
- "ADR 0259"
- "ADR 0332"
- "ADR 0363"
- "ADR 0376"
- "ADR 0489"
- "ADR 0571"
- "ADR 0578"
- "ADR 0614"
---
# Design 34, milestone every-event — the tool runner hears every event
**What it builds.** A session's `mesh_wait` waits on any event of any seat or module, by its name, with no list to
keep: the tool runner's bus account is granted the event subjects of the whole mesh, every module's own stream
included (ADR 0671 §1), and the list of families in the tool runner's manifest, the manifest validator's rule about it
and the grants part's computation of subjects from it go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); this
part holds every line the milestone builds, cited as "design 34 every-event §n".
**Where.** Four repositories and hq, one delivery group: mesh-sdk (the shared event subjects; the field, type and
membership entry that go), mesh-delivery (the grants part, seat `mesh-grants`; the manifest validator in the indexer),
mesh-tools (the tool runner, module `node-tools`) and mesh-catalog (the claude-code module's Stop hook, §20), with hq
for the playbook and agent lines (§24). Only these reference the list today: no other manifest carries the key
`waits`. The milestone is built on each repository's main as it stands. Design 47's milestone G edits the grants
part's account composition and the tool runner's manifest and manifest test too; its lead rebases it onto this
milestone's heads when G's develop restarts.
**The approval mark on events.** *A marked event waits for the operator's warrant* (§32 to §37) writes the operator's
answers of 2026-11-11: a wait respects the approval mark, and the gate that asks and checks it (design 46's milestone
7, [`7-approval-mark.md`](../46-milestones/7-approval-mark.md)) is built in design 47's milestone G and delivered in one
group with G and this milestone ("Build the gate in G"). So §25's grant of a restricted stream's subjects and the gate
that holds its mark reach the machines together.
## The event subjects, in mesh-sdk
1. **One place names the mesh's event subjects.** mesh-sdk's package `buscompose` gains a file `events.go` with three
exports, used by the grants part (§6) and the tool runner (§13), so neither spells the event namespace itself:
- `EveryEvent`, the subjects the tool runner's account hears: `mesh.seat.*.event.>` and `mesh.mod.*.event.>`, in
that order (ADR 0670 §1);
- `EventSubjects(name)`, the subjects one event name lands on. A name is `<emitter>.<event>`, optionally followed
by further tokens (`operator-channel.decided.claude-code`); each token matches `[A-Za-z0-9_-]+`. It answers four
subjects: `mesh.seat.<emitter>.event.<rest>`, the same followed by `.>`, `mesh.mod.<emitter>.event.<rest>`, and
the same followed by `.>`, where `<rest>` is every token after the first; given a namespace (`seat` or `module`,
§13) it answers only that namespace's two. It refuses, with an error naming the rule, a name of fewer than two
tokens, an empty token, or a token outside that form (so `*` and `>` are refused);
- `EventName(subject)`, the inverse for a heard subject: for `mesh.seat.<emitter>.event.<rest>` or
`mesh.mod.<emitter>.event.<rest>` it answers the name `<emitter>.<rest>` and its namespace; any other subject is
answered as not an event.
Why four subjects: a name does not say whether its emitter is a seat or a module, and both namespaces are heard
(§6); and a seat's event that names its asker or kind after the event (ADR 0259 §3), or a module's that names its
module and branch, is heard by the event's name alone, the `filter` narrowing it by body. How it is checked: one
table-driven unit test of the three, in `events_test.go`, one of whose rows holds that every subject
`EventSubjects` answers for its cases is covered by a subject of `EveryEvent`.
2. **The manifest loses `waits`.** `catalogue.Manifest.Waits` and its comment go from `go/catalogue/manifest.go`;
the comment on `ToolRunner` loses "and the only module that may declare `waits`". `go/catalogue/waits_test.go` is
deleted, and one row is added to the nearest table over raw manifests, `go/catalogue/invokes_test.go`'s
`TestAManifestMaySayWhatItInvokes` (Q-T): a manifest
`{"module":"node-tools","version":"1","tool-runner":true,"waits":["a.b"]}` decodes with `UnknownField()` naming
`waits` (ADR 0670 §2). The decoder itself is unchanged: a stored manifest that still carries `waits` is read
without it (ADR 0578 §3).
3. **The baseline of manifest reads stays as generated.** `go/catalogue/manifest_reads_baseline.txt` keeps
`word:waits`: it is the contract of the engines built before any stated what it reads (ADR 0614 §4), which did read
`waits`. In `own_manifest_test.go` the list of baseline entries this contract no longer reads (today the two
renamed seats) gains `word:waits`, its comment naming ADR 0670 §2 as the record that retired the word.
4. **The wait types go.** `go/buscompose/waits.go` (the type `Wait`) is deleted and `Users.Waits` goes from
`go/buscompose/users.go`; `Membership.Waits` and the type `WaitIssued` go from `go/membership/membership.go`
(ADR 0670 §2). Nothing replaces them: no membership names what its account hears.
## The grant, in the grants part (seat `mesh-grants`)
5. **What the grants part computed from the list goes.** `internal/buscompose/waits.go` is deleted whole (`Wait`,
`WaitSubject`, `waitGrants`, `waitsIssuedFor`); `Declared.Waits` goes from `internal/buscompose/users.go`, and with
it the term `len(d.Waits) > 0` of `runnerServes`; `m.Waits = waitsIssuedFor(d)` goes from `membership.go`, and the
alias `WaitIssued` from `sdk_membership.go`; `waitsOf` and the field `Waits:` of `declaredFor` go from
`internal/grants/records.go` (ADR 0670 §2). `safeSubject` in `nats.go` stays: the bus-state grants use it.
6. **The tool runner's runtime hears every event subject.** In `internal/buscompose/nats.go`, where a runtime's
permissions are composed from the modules it carries, the call of `waitGrants` is replaced by: when one of the
carried modules says `tool-runner` (`Declared.ToolRunner`), `buscompose.EveryEvent` (§1) is appended to the
runtime account's subscribe list. Nothing else is added for it: no publish, no durable consumer, no queue, no other
subject; a runtime carrying no tool runner gets neither subject (ADR 0670 §1, §3; ADR 0043 §2, §3, §5, §8 as ADR
0670 §3 now reads them). The grants part writes them into the bus's user list as every grant (ADR 0571 §9); they
are read with `mesh-grants.grants_user_list`, and composed at once with `mesh-grants.grants_compose_now`.
7. **The tool runner's membership names no events.** The grants part's first composition after its delivery rewrites
every tool runner's membership in its state `memberships` (key `<node>.node-tools`, ADR 0571 §10) without the key
`waits`, since the field is gone (§4); every other part of it is unchanged. It is read with the grants part's
reading of memberships and rewritten at once with `mesh-grants.grants_compose_now`.
8. **A composition test holds the grant.** In `internal/buscompose/buscompose_test.go`, one table-driven test composes
a runtime carrying the tool runner and one other module, and a runtime carrying the other module alone, and holds:
the first's subscribe list contains `mesh.seat.*.event.>` and `mesh.mod.*.event.>`; no subscribe subject of either
is wider than those (none of `>`, `mesh.>`, `mesh.*.>`, `mesh.seat.>`, `mesh.mod.>`, nor any `$JS.` subject beyond
what the carried modules' own consumes give); the first's publish list holds no subject of another module's or
seat's events; the first derives no durable consumer beyond its carried modules' `consumes`; and the second has
neither subject (ADR 0670 §1, §3).
9. **The real bus holds it too.** The grants part's existing real-bus test (`cmd/mesh-grants/composed_integration_test.go`)
gains cases, no new test: the tool runner's account, as composed, subscribes
`mesh.mod.mesh-delivery-view.event.delivered` and receives an event published there; it is refused a publish on
that subject; it is refused a subscription on `mesh.seat.channel.accept.show.<kind>` and on
`mesh.seat.intake.proof.code.<kind>` (an accept and a proof are never events, ADR 0670 §1); and another module's
account is refused a subscription on an event it does not consume (ADR 0670 §1, §3).
## The manifest validator, in the indexer
10. **The rule about the list goes.** `waitProblems` and its call in `judge/rules.go` are deleted, and with them the
regular expression `waitToken` where nothing else uses it; the table rows that judged `waits`
(`judge/rules_test.go`, the `waits` cases) are deleted, and one row is added: a manifest that carries `waits` is
refused by the manifest validator as the checker runs it (`Shelf`), naming the unknown field `waits`, and set aside
to wait as the indexer runs it (`ShelfWaiting`), as any field it does not know (ADR 0578 §1); the tool runner's
manifest without it is accepted (ADR 0670 §2). Only revisions taken in after the indexer's delivery are judged so:
a manifest the validator accepted before keeps its verdict (ADR 0578 §1).
## The tool runner (module `node-tools`)
11. **Its manifest's changes, every one.** `node-tools/module.json` drops the key `waits` and its five entries (ADR
0670 §2); gains `operator-channel` in `uses`, for the asks of marked waits (§35; design 46 7-approval-mark §7); gains
in its old `state` section the per-machine state `approvals` of design 46 7-approval-mark §13 (the tool runner is a
foundation part, ADR 0619 §2, §3); and gains the stream `node-tools-findings` and
the `consumes` of `mesh-conditions.findings-asked` that design 47 G §387 writes. Its `version` is `0.1`, as design 47 G §368
and §624 have every manifest's. Nothing else in it changes.
12. **The publication time is kept.** `bus.Heard` gains `Published`, read from the event's header `x-time`, which
mesh-sdk's emit sets (`go/process/emit.go`) and the status view's own emit too; `HearFirst` reads it from the
message's headers, and leaves it empty for an event that carries none.
13. **`mesh_wait` takes any event name.** `console.Surface.waitAnswer` (`internal/console/wait.go`) no longer reads the
membership. Each name of `subjects` becomes its subjects through `buscompose.EventSubjects` (§1); a refused name is
answered as a failure quoting that error. An optional argument `from`, `seat` or `module`, narrows every name to
that namespace, for an emitter whose name is both a seat's and a module's. A call naming more than 16 names is
refused, saying the bound (each name is four subscriptions for the call; 16 names is 64, far above what a watcher
names). The same subject named twice is subscribed once. The map from subject to name goes: the heard event is
named by `buscompose.EventName` of the subject it arrived on. The timeout, the filter and the timed-out answer are
unchanged (ADR 0367 §1). The refusal of names outside a list, and its words naming the manifest, go (ADR 0670 §2).
14. **A misspelt name is refused, not waited on for ever.** Before it subscribes, `waitAnswer` refuses a name whose
emitter is neither a seat nor a module of the catalog half the tool runner holds from G (its first view by
`mesh-catalog.revision`, followed on `catalog-revision`; design 47 G §196), naming that source and its revision.
A name whose emitter is known but whose event no seat definition and no module's entry in the catalog half
declares (its `emits` and its streams' events, design 46 7-approval-mark §5) is waited on,
and its timed-out answer says that no declaration names that event. While the catalog half is not yet read (the
first views' state, `mesh_first_views`), names are not checked, and the answer says `unchecked` with why; a
marked wait is then held as §34 says.
15. **The answer names the heard event.** A heard event is answered as today, `heard` with `subject`, `body` and
`at`, where `subject` is now the heard event's whole name (`mesh-delivery-view.delivered`,
`operator-channel.decided.claude-code`), with two new fields: `from`, `seat` or `module`, the namespace it
arrived on, and `published`, the event's own time (§12), absent when it carries none. The `read` line stays (an
event only wakes you, ADR 0367 §4); for a name beginning `operator-channel.decided` it says instead that the
operator's answer is read only with `claude_code_answer`, never from the event's body (ADR 0670 §5; ADR 0332 §5,
§6).
16. **A refused subscription is said, and a wait is never a hot loop.** `HearFirst` subscribes every subject of the
call; a subscription the bus refuses is noted, and the others go on waiting. When every subscription of the call is
refused, it waits out the call's timeout (or the caller's going) and then answers the refusal, so a watcher that
loops on it asks once a timeout, never at once again. Its refusal words become: "the bus refused this account a
subscription on <subjects>: the grants part grants this machine's tool runner every event subject (novox/hq ADR
0670 §1), so its grant is not composed yet; read the state from its owner meanwhile"; no words name a wait grant or
ADR 0367 §2. A heard event's answer and a timed-out answer list under `refused` any subject the bus refused while
others stood.
17. **The tool's description says what it hears.** `waitTool`'s description of `subjects` says: one or more event
names, `<seat or module>.<event>`, such as `mesh-delivery-view.delivered`; an event whose subject goes on after
the name (an asker, a kind, a module) is heard by the name too; the filter narrows by the body's fields; `from`
narrows to a seat's or a module's. Its own description keeps "an event only wakes you" and adds that an operator's
answer is read only with `claude_code_answer`. The words "only those the mesh granted this machine's tool runner"
go.
18. **The hearing serves `mesh_wait` alone.** `HearFirst` has one caller, `waitAnswer`. The modules the runtime carries
share its account, so the bus no longer bounds which events their code could hear; the runtime still gives a
carried module's code only what its membership names, and this is accepted under ADR 0670 §1, every session
already hearing every event. How it is checked: the code review of every change to the tool runner reads
`HearFirst`'s callers.
19. **What goes from the tool runner's code, and its tests.** The type `bus.WaitIssued` (`internal/bus/wait.go`) and
`Membership.Waits` (`internal/bus/bus.go`) go. In `console/manifest_waits_test.go` the test of the five families
becomes a test that the manifest has no key `waits`, its comment citing ADR 0670 §2; its tests of what the tool
runner consumes and of the runner's own secrets stay. In `console/wait_test.go`: `waitingRuntime` issues no waits,
and the answering test waits on `mesh-delivery-view.delivered` and on `operator-channel.decided` with no membership
naming either, its rows holding `subject`, `from`, `published` and `read` (a `decided.<asker>` event heard by
`operator-channel.decided`); `TestMeshWaitRefusesAnEventOutsideTheGrantedFamilies` becomes the table of refusals:
a name with `*`, with `>`, of one token, 17 names, an emitter no source knows (§14), and a bus-refused subscription
answered after the timeout with §16's words; `TestMeshWaitWithNoFamiliesIssuedWaitsOnNothing` is deleted. In
`bus/wait_test.go`, `TestTheBusRefusesAWaitOutsideTheGrant` stays as §16's refusal test, with a timeout of a
fraction of a second, its comment citing ADR 0670 §1. The manifest test also holds each change of §11: no `waits`,
`operator-channel` in `uses`, the state `approvals` in the old `state` section, per machine with its time to live,
and none in `requires`, and the finding stream. The
tool runner ends milestone G with no more tests than it starts with (design 47 G §419, §626): these tests replace
existing ones, and every test deleted counts.
## The claude-code module's Stop hook
20. **The hook no longer doubts the grant.** In mesh-catalog `modules/claude-code/cmd/claude-code/stop.go`,
`watcherAdvice` says for a wait with an event: "mesh_wait on <event> and reread <read> after every answer, or start
a watcher that does", with no condition on the tool runner's grant; its comment cites ADR 0670 §2 beside ADR 0489
§3. Its row in `stop_test.go` holds the new words.
## Events carry no secret
21. **The prepare lists every event.** The milestone file's prepare (playbook 07, step 0) gains a section *Events and
their fields*: every event each module declares (its `emits` and its streams' events) and each seat's definition
declares, read from the module catalog: the catalog revision of the change's branch (`mesh-catalog.revision`),
which carries the seat definitions too, and nothing else, no manifest and no bus read, so the events G and design
46's milestone 7 add are listed (the finding streams of design 47 G §383 among them), each a holder's event with
its subject form; and every event mesh-sdk publishes on a module's behalf (the announcement's `states` on
`mesh.mod.<module>.event.states`, found by the callers of its subject builders); each with the fields of its body
as the emitting code builds it, by file and line (ADR 0670 §4). *Why:* the operator, 2026-11-11: "only the indexer
uses the manifest to build the module catalog".
22. **The code review confirms none carries a secret, before the grant is delivered.** The milestone's code review
(playbook 07, step 2) reads that list again at the group's heads, and confirms that no event's body carries a
credential, a token that grants an act, code, or a message marked secret; an intake `choice`'s one-use token is not
such a token (ADR 0259 §3, §5). The list is the module catalog's (§21); the review reads no bus. An event found
carrying a secret holds the milestone's delivery and goes to the operator as a design question
(ADR 0670 §4).
23. **The stream events are read too.** §21's list also names every event kept in a module's own stream
(`mesh.stream.<module>.event.<event>`, ADR 0363 §2), the seat holders' events published there among them, and
whether its stream is restricted (its declaration carries a `grant`, ADR 0363 §4). The mail stream's events are
named in it as heard with their content: a mail's own text, a sign-in link or a code someone sent by mail among
it, is the operator's mail and not a secret in §22's sense, which is a credential, token or code the mesh itself
issues or keeps (ADR 0671 §3, which narrows ADR 0670 §4).
24. **From then on every change is asked the same.** In this milestone's hq pull request: playbook 07's review step
gains the question, for any change that adds an event or a field to one, "does it carry a secret?"; and the watcher
lines that describe the list (`.claude/agents/mesh-watcher.md`, playbook 07's *Following and reporting*: "the
families a watcher may wait on are those the mesh granted the tool runner") say instead that `mesh_wait` hears any
event by name (ADR 0670 §2, §4).
## Stream events
*By ADR 0671: the operator chose every module stream, mail too, on 2026-11-11.*
25. **The tool runner also hears every module stream, a restricted one included.** `buscompose.EveryEvent` (§1)
gains a third subject, `mesh.stream.*.event.>`, after the two of §1, and the grants part's composition (§6) appends
it with them. Nothing else is added for it: no durable consumer, no grant to acknowledge, no membership word, no
publish, and no subscribe deny of a restricted stream (ADR 0671 §1). A restricted stream's grant value (ADR 0363
§4) decides who consumes it, not who hears it, so changing or withdrawing that value changes nothing in the tool
runner's grant (ADR 0671 §2); a module that consumes a restricted stream is composed as today (ADR 0376 §2 as ADR
0671 §2 reads it). The bus does not hold the approval mark on a restricted stream; the tool runner's own gate does,
before it subscribes for a wait (§32 to §37; the operator, 2026-11-11), and that gate is delivered in the same
group as this grant (design 46 7-approval-mark §23). *Why:* ADR 0286 §8 has the bus hold a mark, and the bus
cannot grant a subscription for one approved wait alone, so the operator chose the tool runner's gate.
26. **A name hears its stream form.** `EventSubjects` (§1) answers six subjects, adding
`mesh.stream.<emitter>.event.<rest>` and the same followed by `.>`; `from` (§13) takes `stream` too; `EventName`
answers a stream subject with namespace `stream`. A seat's holder event kept in its holder's stream is named by its
holder module (`mesh-delivery-checker.verdict`). The bound of §13 stays 16 names (96 subscriptions).
27. **The tests hold it.** §8's test holds exactly the three subjects for the runtime carrying the tool runner, beside
a fixture module that declares a restricted stream, and that no durable consumer and no subject to acknowledge on
that stream are derived for the tool runner's runtime (ADR 0671 §1). §9's real-bus test gains cases: the tool
runner's account, as composed, receives an event published on a fixture module's stream, and one published on a
fixture restricted stream whose grant value has no entry for the tool runner, and still receives one after that
value is changed and the accounts are composed again; it is refused a durable consumer and an acknowledgement on
that restricted stream; and a module whose `consumes` names the restricted stream with no entry in its grant value
gets no durable consumer and no read grant on it (ADR 0671 §1, §2).
## Delivery
28. **In G's delivery group, no order named beyond the module graph's.** The members are pushed and checked in design
47 milestone G's group, with design 46's milestone 7, whose edges (design 46 7-approval-mark §23) order the gate
before the seat definition that marks `execute`; this milestone adds no order of its own between its members, and no
transitional field is kept (the operator, 2026-11-03: a change is designed as
it should be, its delivery is the delivery system's): a part built with the new mesh-sdk reads a stored manifest of
the tool runner that still carries `waits` without it (ADR 0578 §3), and a node-engine is given a want only for a
manifest whose words it reads (ADR 0614 §4).
29. **What `mesh_wait` answers between the deliveries.** On a node whose tool runner is new and whose grant is not yet
composed, the subjects the old grant names are still heard and the rest are listed under `refused` (§16); on a
node whose tool runner is older and whose membership the new grants part rewrote, the old runner answers at once
that it was issued no event families. Both are loud. A watcher reads its owner after every answer (ADR 0367 §4) and
ends at a call failed twice (its definition), so neither becomes a loop; a laptop asleep through the window gets
its new tool runner when it wakes, and its watchers read their owners meanwhile in the same way.
30. **Going back is going forward** (design 46 7-approval-mark §24 for the gate's part). A fault met after the delivery is fixed by a new change delivered the same way.
Reverting the group instead reverts every member, the indexer first, since the manifest validator delivered here
sets aside the old tool runner's manifest carrying `waits` (§10); until the revert reaches a node, `mesh_wait`
there answers as §29 says.
## Nothing built ahead of the group
31. **No tool is built ahead of the group.** The extension of the nats module's `nats_streams` this line held, for
§22's bus read, is not built: the secrets review reads the module catalog alone (§21, §22). *Why:* the operator,
2026-11-11: "only the indexer uses the manifest to build the module catalog". *How it is checked:* the milestone's
prepare names no member outside the group.
## A marked event waits for the operator's warrant
*By the operator's answers of 2026-11-11: "can it also respect any 'requires approval' config on the topics?", and
"Build the gate in G".* These lines use the gate design 46 7-approval-mark builds for a marked call (its §8 to §19),
delivered in the same group. *Why they depart from the records, said once for the section:* ADR 0286 §1 marks tools
and verbs, and the operator extended the mark to a module's events and streams; ADR 0671 §1 and §3 have every session
hear every module stream, mail included, with no exception, and the operator narrowed that to a wait the operator
approves, for a marked event or a restricted stream.
32. **A module marks an event, and a restricted stream is marked whole.** A module's manifest `needs-approval` (ADR
0286 §1) may also name one of its own events, by the name its `emits` or one of its `streams`' `events` gives it,
with `*` and `**` as there. Every stream a module declares with a `grant` (ADR 0363 §4; `ModuleStream.Restricted`)
is marked whole, every event it carries, with no entry in `needs-approval`; a stream without a `grant` is not, a
work-queue one included. A seat's definition marks no event. The indexer's manifest validator refuses, as the
checker runs it, a manifest whose `needs-approval` names an event that neither its `emits` nor its streams'
`events` carry, naming the event, as ADR 0286 §4 refuses a mark on a tool the module does not serve (design 46
7-approval-mark §4's table gains the row). A module that marks only events needs neither `operator-channel` in
`uses` nor `approval-mark/1`: the tool runner asks for the wait, not the module.
33. **The tool runner reads the marks from the catalog half on the bus.** The tool runner derives each module's marked
subjects from the catalog half it holds (§14; the catalog graph entry carries `needs-approval` and each stream's
`grant`, design 46 7-approval-mark §5): for a marked event `<event>` of module `<module>`,
`mesh.mod.<module>.event.<event>` and `mesh.stream.<module>.event.<event>`, each event pattern as the bus spells it
(`WirePattern`); for a stream with a `grant`, every subject of `ModuleStream.Subjects()`; and each of these also
followed by `.>`, since an event may name its node or asker after its name (design 47 G §383) and
`ModuleStream.Subjects()` answers only the patterns the stream stores. It derives them again on every revision it
hears, and no module's name is written in its code; it is design 46 7-approval-mark §5's derivation, extended to
events, following the running version as that line says. A mark added while a wait waits applies from the next
call, which that call's answer says. *Why:*
ADR 0286 §5 had the mark issued in a membership; no controller issues one now, a membership names no event (ADR
0670 §2), and the catalog half, which the indexer alone builds from the manifests, keeps the mark out of any bundle
as §5 required; from G the catalog half is where the tool runner reads which modules exist (design 47 G §196), and
no part but the indexer reads a manifest (the operator, 2026-11-11).
34. **Which wait is marked.** A wait is marked when any subject it subscribes (§13, §26, after `from`) overlaps a
marked subject of §33, that is, some subject matches both, a wildcard on either side. mesh-sdk's `events.go` (§1)
gains `Overlaps(a, b)` for it. Before the catalog half is read (`mesh_first_views`), a wait with any subject
outside `mesh.seat.` waits, within its own timeout, for the first view and is then judged; nothing is asked while
the marks are unread, and a wait still unjudged when its timeout ends is refused then, saying the marks are not
yet read, since a mark that cannot be read runs nothing (ADR 0286 §40; design 46 7-approval-mark §19). A wait
`from: seat` is never marked.
35. **A marked wait runs only on the operator's warrant for that one wait.** The call is an act as design 46
7-approval-mark §8 makes one: the address `mesh_wait`, the node, the module `node-tools` and the tool `mesh_wait`,
its build (the tool runner's own fingerprint, read from its `MESH_FINGERPRINT` at its start, design 47 G §205 and
§625; while it is unknown, a marked wait is refused, saying so), and every
argument given (`subjects`, `from`, `filter`, `timeout`), so the warrant binds the exact names, the filter and the
bound. The tool runner refuses, asks, judges and claims it as that part's §9, §10, §11 and §13 to §17 do for a
marked tool, asking on the seat `operator-channel` under its own module's name, `node-tools`, whose manifest gains
`operator-channel` in `uses` for it (that part's §7). The ask's headline is "Wait on <names> on <node>?"
(shortened as that part's §10 says), and its explanation names every event name one per line, the filter, and the
bound as the timeout in minutes, then the act's fingerprint. On a warrant it accepts, it subscribes, and the wait's
timeout runs from then. One warrant is one wait (that part's §15): a watcher looping on a marked event is approved
call by call. Its answer carries the line naming the approval and the warrant is recorded through
`mesh-hand-acts.warranted` (that part's §16), and `mesh_approvals` (that part's §18) lists the waits asked beside
the calls. *Why:* the operator, 2026-11-11: a marked wait runs only on the operator's warrant for that one wait,
shown with the exact names it waits on and its bound.
36. **A refused marked wait is said, and never a hot loop.** A marked wait with no warrant the tool runner accepts
subscribes to nothing. Its first call publishes the ask at once; every such call answers, as an error, design 46
7-approval-mark §12's or §17's words with "this wait" for "the call", when its timeout ends or its caller goes, as
§16 answers a refused subscription, or as soon as the router's record of its ask says it was chosen: the wait hears
the warrant event `operator-channel.decided.node-tools` for its ask's id and then reads the record, an event only
waking it, and answers at once, so the same call made again runs within the warrant's 30 minutes. *Why:* ADR 0286
§14 answers within 5 seconds, which a watcher's loop would turn into a call every few seconds for the ask's hour;
ending at the choice keeps a long wait from spending the warrant's window. An unmarked wait is heard as §13 to §16
say, with no ask. Every machine's marked waits ask under the one asker `node-tools`, so they share its open asks
and the router's holding back of the oldest beyond three (ADR 0666 §12, as G builds it), and the wait reads the
router's record through `Client.Where` under `node-tools`.
37. **The tests hold it.** Unit, as the budget counts them (`meshtest` runs its bus in the test's process): §19's table
of refusals in `console/wait_test.go` gains rows, no test function: a wait on a fixture module's marked event, and
one on a fixture stream with a `grant`, each with no warrant, subscribes nothing, asks once and answers design 46
7-approval-mark §12's words after its timeout; a wait with the most timeout a wait takes (ten minutes, ADR 0367
§1), its ask chosen at its second minute, answers at the choice, and the same call made then runs; the same wait
with a warrant bound to its exact names, filter and timeout subscribes and answers the event published after it,
so the restricted stream's event is heard only after the warrant; with a warrant for other names, another filter
or another timeout it is refused; the same names `from: seat` ask nothing; a wait named `<module>.<event>.<node>`
under a marked `<module>.<event>` is marked; and before the catalog half is read, a marked wait asks nothing, is
judged when the first view comes within its timeout, and is refused at its timeout when it does not. A marked wait while
the tool runner's own fingerprint is unknown is refused, saying so. The warrant is
written on the test bus as the router's record. `Overlaps` gains rows in §1's table (`mesh.stream.mail.event.mail.>` overlaps
`mesh.stream.mail.event.mail.*.message`; `mesh.mod.mail.event.x` does not overlap `mesh.stream.mail.event.x`), and
§32's refusal a row in the indexer's `judge/rules_test.go`.
## Acceptance
- **A watcher wakes on an event no manifest names** (the operator's acceptance; live, read by tool call): at the first
delivery after every node holds the new tool runner, a watcher's `mesh_wait` on `mesh-delivery-view.delivered`
answers the status view's `delivered`, its `at` less than a second after its `published`, with no rereading on a
timer; the tool runner (`node-tools`) is delivered to every node, and the grants part and the indexer to theirs,
read with `mesh-delivery.show` and each node's `node-engine.composition` (§6, §13, §15).
- **The grant is exactly what the record names** (unit, and integration on a real bus): the composition test of §8
and the real-bus cases of §9 pass.
- **The list is gone** (unit): the manifest validator as the checker runs it refuses a manifest carrying `waits`, the
indexer sets it aside, and the tool runner's manifest is accepted without it (§2, §10, §11).
- **`mesh_wait` refuses loudly and never loops** (unit): §19's table of refusals passes.
- **No event carries a secret** (the code review): the milestone file's *Events and their fields* is complete and the
code review's confirmation at the group's heads is written in it before the grants part is delivered (§21, §22).
- **An answer is never taken from an event** (the code review, and unit): in the members nothing reads an operator's
answer from a `decided` event's body, and `mesh_wait`'s answer for one says to read it with `claude_code_answer`
(§15).
- **A stream event wakes a watcher** (live, read by tool call): a watcher's `mesh_wait` on
`mesh-delivery-checker.verdict` wakes on the checker's next verdict (§25, §26; ADR 0671 §1).
- **The grant does not follow a restricted stream's grant value** (integration on a real bus): §27's cases pass
(ADR 0671 §2).
- **A marked wait runs only on the operator's warrant** (unit): §37's rows pass.
- **A wait on mail, as the operator sees it** (live, read by tool call and on the phone): a session's `mesh_wait` on
`mesh-mail.mail`, the mail module's restricted stream, asks "Wait on mesh-mail.mail on <node>?" on the phone with
its bound, and hears nothing before; it ends refused when its timeout ends or as soon as the operator chooses; the
same call made after the Approve hears the next mail, and the next call asks again (§32 to §36).
+95 -8
View File
@@ -1,9 +1,21 @@
---
layer: to-be
status: implemented
code: [mesh-tools, mesh-host, mesh-controller]
updated: 2026-10-17
status: in-progress
code: [mesh-tools, mesh-host, mesh-controller, mesh-sdk, mesh-delivery, mesh-catalog modules/claude-code]
updated: 2026-11-11
decisions:
- "ADR 0670"
- "ADR 0671"
- "ADR 0286"
- "ADR 0043"
- "ADR 0259"
- "ADR 0332"
- "ADR 0571"
- "ADR 0363"
- "ADR 0376"
- "ADR 0489"
- "ADR 0578"
- "ADR 0614"
- "ADR 0197"
- "ADR 0195"
- "ADR 0175"
@@ -28,7 +40,7 @@ it put it there (ADR 0152).
## 1. What it is
A module, `mesh-console`, in the catalogue. Its image is the tool runner's own — the client that
A module, `mesh-console`, in the catalog. Its image is the tool runner's own — the client that
already speaks the bus as a command line and as an MCP server — started in a mode that reads the
module's credential and listens on loopback. It has no state, no provision, no seat. What it needs is
the bus, which it gets the way every module does: a credential the mesh minted for `<node>.mesh-console`,
@@ -42,7 +54,7 @@ consumer is the operator's agent, [36 — The operator's agent on a machine](36-
a machine without the mesh MCP server refuses such a module by name. Nothing else above changes: no seat, no
state, no tools of its own.
Its manifest says three things nothing else in the catalogue says together:
Its manifest says three things nothing else in the catalog says together:
- `invokes: ["*"]` — it calls every tool on the mesh, and the bus grants exactly that publish side;
- `listens` on a port `from: machine` — the filter opens nothing for it, because loopback is not outside;
@@ -74,7 +86,7 @@ its own under that module's name, `mesh.mod.<module>.tool.tools`, answering the
descriptions and argument schemas — the definitions from the code that answers them, and from nowhere
else. A module may not name a tool of its own `tools`; the runtime refuses the collision at load.
**The mesh MCP server asks the catalogue which modules the mesh holds, then asks each.** `catalog_modules`
**The mesh MCP server asks the catalog which modules the mesh holds, then asks each.** `catalog_modules`
answers the roster; one `tools` request per module, in parallel, answers the list. The bus refuses at
once a request nothing serves, so a module that is not running costs nothing and is named in the answer
as not answering, rather than silently absent — *silence and success must never look alike*. The list is
@@ -106,7 +118,7 @@ answers is reached through them by its address: `<seat>.<verb>` for a seat held
`<node>/<seat>.<verb>` for one held per machine, `<node>/<module>.<tool>` for an assignment, and
`<module>.<tool>` as well for a module whose instances are interchangeable. A module that is not
interchangeable is called with its machine or refused with the machines it runs on. Each discovery
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalogue
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalog
stays reachable through the `mesh` client and a setting, unannounced.
**What exists is what announced itself** (ADR 0197):
@@ -119,6 +131,64 @@ listed once per machine does not hold on the live mesh MCP server — postgres a
optional, answered by whichever instance replies. The address replaces that statement rather than
repairing it.
## 3b. Waiting on the bus
*By ADR 0367, as ADR 0670 and ADR 0671 change it.* Beside the five discovery tools of §3a and `mesh_runtimes`, the mesh MCP
server offers a seventh, `mesh_wait`: a session names one or more events, an optional `filter` of body fields and a
`timeout` (four minutes when absent, ten at most), and is answered the first matching event or told it timed out
(ADR 0367 §1). A session calls it in a loop from a background command (ADR 0367 §3), and after every answer or time-out
reads the state once from the verb that owns it: an event only wakes it (ADR 0367 §4).
```mermaid
flowchart LR
E[a seat's, a module's or a module stream's event<br/>mesh.seat.&lt;seat&gt;.event.… / mesh.mod.&lt;module&gt;.event.… / mesh.stream.&lt;module&gt;.event.…] -->|heard for the call| TR[the tool runner's account]
G[seat mesh-grants] -->|grants the three event subjects| TR
M[the catalog half, seat mesh-catalog] -->|the marked events and restricted streams| TR
TR -->|a marked wait only on the operator's warrant| OC[seat operator-channel]
TR -->|mesh_wait answers it by name| S[a session]
S -->|then reads the state| O[the verb that owns it]
```
**The tool runner hears every event of the mesh, and no list says which** (ADR 0670 §1, §2). The grants part (seat
`mesh-grants`) grants the account of the runtime carrying the tool runner a plain subscription on the event subjects
of every seat and every module, and on nothing else of theirs: no accept, proof, call, work queue or bucket, and no
publish. `mesh_wait` subscribes when called and closes the subscription when it answers, so the account keeps no
durable consumer and no queue of its own (kept from ADR 0367 §2), and any event name is accepted: a new event is
heard from its first publication, with no change to a manifest. The tool runner's manifest names no events, the
manifest validator has no rule about them, and no membership lists them. A name whose emitter no seat and no module
bears is refused, so a misspelt wait does not run for ever.
**Who hears.** Whoever may call the mesh MCP server: any process on the machine, through its loopback endpoint (§2),
which is the session there. The tool runner's standing grant of every event is accepted under ADR 0670 §1: what a
session hears through it, it may hear.
**Module streams too, mail included** (ADR 0671 §1, §2). The tool runner also hears the events kept in a module's own
stream (ADR 0363 §2), the seat holders' events published there among them, a restricted stream such as mail's
included. A restricted stream's grant value decides who consumes it, never who hears it, so the tool runner's hearing
needs no `consumes` and no entry in that value, and keeps no consumer of it.
**A marked event is heard only on the operator's warrant** (the operator, 2026-11-11). The approval mark (ADR 0286) covers a module's events and streams as well as its tools and
verbs: a module may mark one of its events in `needs-approval`, and a restricted stream is marked whole. The tool
runner reads the marks from the catalog half on the bus (seat `mesh-catalog`), and a `mesh_wait` on a marked event, or on any subject
of a marked stream, runs only on the operator's warrant for that one wait, asked on the seat `operator-channel` with
the exact names it waits on and its bound, and checked before the tool runner subscribes; without one it is refused
and says so. The bus grants the subjects, and the tool runner's own gate holds the mark, as the operator chose; the
gate is design 46's milestone 7, built in design 47's milestone G and delivered in one group with G and milestone
every-event, so the subjects and the gate reach the machines together. This departs from ADR 0286 §1 and §8 (tools and
verbs only, held by the bus) and from ADR 0671 §1 and §3 (every module stream heard with no exception) by the
operator's answer. An unmarked event is heard with no ask.
**What that makes audible, and what still bounds it.** Every session now hears the operator channel's `decided` of
every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, token or
code the mesh itself issues or keeps, and no message marked secret, which the code review asks of every change that
adds an event or a field (ADR 0670 §4 as ADR 0671 §3 narrows it). A mail's own content is the operator's mail and is
heard as it is, a sign-in link or a code someone sent by mail among it (ADR 0671 §3). And a heard event never answers anything: the operator's answer to an ask is read only
with `claude_code_answer`, from the router's record and held to the question's digest (ADR 0670 §5, ADR 0332 §5,
§6), and an asker acts only on a warrant from its own `decided` subject through its own consumer (ADR 0259 §6).
What is built for this, line by line, is milestone every-event's part,
[`34-milestones/every-event.md`](34-milestones/every-event.md).
## 4. Where it runs
On whichever machines an operator sits at, by assignment. It is not on the control node by default and
@@ -145,6 +215,14 @@ address gets a refused connection, which is the truthful answer.
| a call through the mesh MCP server's endpoint reaches a module over the bus and the answer is the module's own, unshaped | ADR 0035, a surface decides nothing |
| on the live mesh: the mesh MCP server assigned to a workstation answers `tools/list` on loopback and a call to the forge returns repositories | the exit of work-order step 3 |
| the composed filter for a machine carrying the mesh MCP server opens no port for it | ADR 0144 |
| on a real bus, `mesh_wait` answers the first matching event within a second of its publication, and after every answer a session reads the owner's state | ADR 0367 §1, §4 |
| live: a watcher's `mesh_wait` on the status view's `delivered`, an event no manifest names, wakes on it with no rereading on a timer | ADR 0670 §2, milestone every-event |
| a composition test: the tool runner's runtime account is granted exactly `mesh.seat.*.event.>`, `mesh.mod.*.event.>` and `mesh.stream.*.event.>` beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume | ADR 0670 §1, §3; ADR 0671 §1 |
| on a real bus, the tool runner's account hears a restricted stream's event with no entry in its grant value, still after the value changes, and is refused a durable consumer and an acknowledgement on it | ADR 0671 §1, §2 |
| the tool runner's gate refuses a marked wait with no warrant and accepts one with a warrant bound to its exact names, filter and timeout; on the test bus a restricted stream's event is heard only after the warrant | the operator, 2026-11-11; ADR 0286 |
| the manifest validator, as the checker runs it, refuses a manifest that carries `waits` as an unknown field, and accepts the tool runner's without it | ADR 0670 §2 |
| the milestone's prepare lists every event with its body's fields, and its code review confirms none carries a secret before the grant is delivered; every later change adding an event or a field is asked the same in its review | ADR 0670 §4 |
| nothing takes an operator's answer from a `decided` event's body; `claude_code_answer` is its one reader | ADR 0670 §5 |
## What shipped, 2026-09-30
@@ -159,10 +237,19 @@ machine with nothing else on it does; the mesh MCP server binds whatever it is g
Two things shipped bent, both stated in [`00-as-is/13-the-console.md`](../00-as-is/13-the-console.md):
the person's client through the mesh MCP server (`--console`) exists and was exercised in the test suite, not
on the live mesh; and a module registered from the catalogue by hand recorded its source as a URL rather
on the live mesh; and a module registered from the catalog by hand recorded its source as a URL rather
than as a path on the git seat, because `--self` takes the forge path form — the rebuild-on-merge still
matched it by URL.
## Milestones
| Milestone | What it builds | State |
|---|---|---|
| the mesh MCP server | §1 to §5: the module, its loopback endpoint, its discovery (ADR 0152) | delivered 2026-09-30 (above) |
| found by address | §3a: five announced tools and addresses (ADR 0195, ADR 0197) | delivered; [as-is 13](../00-as-is/13-the-console.md) |
| waiting on the bus | §3b's `mesh_wait` on a list of five families in the tool runner's manifest (ADR 0367) | delivered (issue 745); [as-is 13](../00-as-is/13-the-console.md) |
| every-event | §3b as ADR 0670 and ADR 0671 change it: the tool runner hears every event, module streams included, a marked one on the operator's warrant, and the list goes; one delivery group of mesh-sdk, mesh-delivery, mesh-tools and mesh-catalog, with hq | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) |
## What this does not settle
- **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through
@@ -132,7 +132,7 @@ a hypothesis (ADR 0153);
the mesh is asked and changed through the controller seat's verbs; the forge through the forge module's
tools; a licence through the `anthropic-licence-manager` seat's verbs, never by editing a file. The hard
rules in new words: a file the mesh manages is changed through the verb that owns it or through the
catalogue, never on disk; a store's database is never written by hand; main is never pushed; the mesh
catalog, never on disk; a store's database is never written by hand; main is never pushed; the mesh
creates no symlinks and nobody else does; a package is declared, not installed by hand. The glossary's
words, none of the predecessor's.
@@ -231,7 +231,7 @@ All four nodes carry one since 2026-10-03. **Per node:** the role. **Per mesh or
extra tool servers, and the operator's managed-settings keys (§2). The controller's verb replaces a
setting layer whole, so a layer set for one of these keeps the others it already held. **Prerequisite:** the manager holds its seat and has adopted the licences.
**Order:** the manager assigned and a refresh observed; the mesh MCP server's provision in the catalogue; this
**Order:** the manager assigned and a refresh observed; the mesh MCP server's provision in the catalog; this
module on one workstation; the six predecessor files and the hand-made mesh MCP server entry removed there; a
new session read to confirm it sees the mesh's instruction file, the mesh MCP server's five tools under `mesh`, and
its licence; then the rest.
@@ -19,7 +19,7 @@ decisions:
# 37 — The operator's machine
**Every configurable thing on a node is a module, the home included, and the same catalogue serves
**Every configurable thing on a node is a module, the home included, and the same catalog serves
a server and a laptop.** One default configuration per module, varied per node by a setting or a
kept region; roles a machine has once as node-scoped seats with tool contracts; one tool runner
per node serving every module's tools outside any container
@@ -117,7 +117,7 @@ resolver is asked by the second graphical module; the display server itself is g
all four today. Nothing home-scoped composes before it.
2. **The tool runner** — `mesh-host` supervises it; mesh-tools serves bundles from memberships
and reloads; mesh-controller composes the bundle into the declaration and the memberships to one
runtime per node; the catalogue renames the mesh MCP server. Proven when the packet-filter verbs answer
runtime per node; the catalog renames the mesh MCP server. Proven when the packet-filter verbs answer
from it and its container is gone.
3. **`zsh`**, the first environment module: seat, `user` shape, home files, `execute`. Proven on a
server first, then every node.
@@ -52,7 +52,7 @@ as design 28's: nothing here is new ground; every package reshapes something sta
| the controller's bus principals | Go: one principal per module per node, grants from what it declares | 132 lines | gains one principal per node for the runtime |
| the controller's memberships | Go: one per assignment, the subjects a runtime serves | 143 lines | **unchanged** in shape; the runtime reads several |
| the controller's declaration composer | Go, one file | 2 053 lines | gains the runtime's process, the bundles' archives, two env words |
| the catalogue | 35 manifests build a per-module tool container on the runtime's base image | — | none do; the runtime is a module of its own |
| the catalog | 35 manifests build a per-module tool container on the runtime's base image | — | none do; the runtime is a module of its own |
**Two measurements decide the shape.** The node-engine needs no change: a `process` and an `archive` are
what the runtime and a bundle are, and both are applied today. And the runtime already does
@@ -140,11 +140,11 @@ with the shortcut off answers the same.
a module whose own code is an image artifact is refused, whatever image it is built on.
*Amended 2026-10-02, at WP3.* The gate refuses the pattern **spreading**, not standing: a module
new to the catalogue in that shape, or one that had already moved to a bundle and returns to it,
is refused; a module the catalogue already holds in that shape — judged from the manifest it
new to the catalog in that shape, or one that had already moved to a bundle and returns to it,
is refused; a module the catalog already holds in that shape — judged from the manifest it
holds and what that module's newest build stood on — is rebuilt without complaint. The day the
runtime arrives some thirty such modules stand, each moves in its own change from WP4 on, and a
gate refusing every rebuild in the meantime would stop the catalogue's pipeline to make a point
gate refusing every rebuild in the meantime would stop the catalog's pipeline to make a point
this record already makes.
**Proof.** Composition tests: a node with three assigned modules, one holding a seat, yields one
@@ -159,7 +159,7 @@ the runtime is registered.
**What changes.** mesh-tools gains a `bundle` artifact of itself beside its images, and its manifest
becomes the `node-tools` module: a package for the interpreter, the loopback listener the mesh MCP server
declared, `invokes: *`, and nothing else — the process is the controller's to compose (WP2). In the
catalogue, `mesh-console` is retired as a module and `node-tools` assigned where it was. The
catalog, `mesh-console` is retired as a module and `node-tools` assigned where it was. The
runtime's `serve` keeps answering MCP on loopback; the person's end of it keeps the name "console"
([glossary](../../00-META/glossary.md)).
@@ -178,7 +178,7 @@ runtime accepts. *Built 2026-10-02* (mesh-tools `c46f950`, mesh-controller `ca7e
`729a5f9`). *Proven live 2026-10-02/03, on all four machines*: the mesh MCP server's container is gone,
`node-tools` runs as a unit the node-engine wrote, as the operator's account, `tools/list` on each loopback
answers with the same 219 tools as before, and the controller's verbs answer through it; `mesh-console`
retired from the catalogue. Three things the step found are issues
retired from the catalog. Three things the step found are issues
[203](../../04-ISSUES/203-a-fresh-assignment-is-pushed-before-its-credential-exists/00-report.md),
[204](../../04-ISSUES/204-a-controller-handover-re-sent-every-node-a-stale-declaration/00-report.md) and
[205](../../04-ISSUES/205-a-package-resource-fails-against-a-stale-package-database/00-report.md).
@@ -224,7 +224,7 @@ the node-engine re-creates the runtime's process on every reconcile (resolved th
credential and state directory gone, the client through `sudo` since the daemon's socket is root's;
proven on all four machines — `status`, `banned` and the module's own `fail2ban_settings` answer from
the runtime, no `mesh-fail2ban` container, the runtime serving both bundles. Two holders moved; of the
thirty-three tool containers the catalogue held, thirty-one remain, and all but these two carried their
thirty-three tool containers the catalog held, thirty-one remain, and all but these two carried their
module's configuration and secrets in the container's environment, which a bundle does not have — the
question research research 020 opened
and ADR 0192
@@ -235,7 +235,7 @@ container's, the runtime hands each bundle its own. That is WP4b below.
*mesh-controller, mesh-tools, mesh-catalog. One day. The rest of ADR 0175, under ADR 0192.*
**What changes**, in order: the manifest's tools artifact gains `env` and the catalogue check
**What changes**, in order: the manifest's tools artifact gains `env` and the catalog check
refuses a secret's content in it; the composer resolves a bundle's `env` per machine and carries it
beside the bundle's archive, `restart-on` included; the runtime hands each bundle its own
environment — the contributor's argument for an imported bundle, the child's environment for a
@@ -0,0 +1,559 @@
---
layer: to-be
design: 39
milestone: bus-and-switch
updated: 2026-11-11
code: [mesh-catalog modules/claude-licence-manager, mesh-catalog modules/claude-code, mesh-catalog modules/mesh-vault]
decisions:
- "ADR 0183"
- "ADR 0206"
- "ADR 0209"
- "ADR 0054"
- "ADR 0550"
- "ADR 0595"
- "ADR 0596"
- "ADR 0437"
- "ADR 0564"
- "ADR 0570"
- "ADR 0230"
- "ADR 0380"
- "ADR 0253"
- "ADR 0289"
- "ADR 0349"
---
# Design 39, milestone bus-and-switch — the licence manager on the bus, switching on its own usage
**What it builds.** The seat `anthropic-licence-manager` keeps everything it holds in the bus's own storage, by the
kept-data templates, and its store database goes; every grant and token moves into the vault, as ADR 0550 §3 has it;
and the manager itself moves every consumer off a licence whose session or weekly usage reaches a threshold, to the
licence with the most room, says each switch as an event and to the operator, and raises a condition when no licence
has room. Its tools read all it keeps and start by hand all it does. The whole shape is in the design's main file
([39](../39-the-anthropic-licence-manager.md)); this part holds every line the milestone builds, cited as "design 39
bus-and-switch §n".
**Why now.** The operator, 2026-11-11 22:10 to 22:20 CET: "build it in the licence manager, but make it configurable
of course, also remove the postgres database in favor of a bus store, implement useful tools, as much relevant state
as possible"; earlier that evening: "when the current licence usage is at 99%, switch to a licence that still has
room" (first said at 98, then 99), "on all nodes". That night every node was bound to one licence that reached 99 % of
its weekly window, and a session meant to switch them missed the readings: so the manager switches on its own
readings, and never a session. Issue 1409 (no warning before a licence's limit) is fixed in the same change (§30).
**Where.** One repository, mesh-catalog, and three of its modules: `claude-licence-manager` (the seat's holder),
`claude-code` (the agent module on every node, the consumer) and `mesh-vault` (the seat `mesh-vault`), with hq for this
part, the main file and the glossary. The order between them is the module graph's, each an edge the delivery plan
derives (§50).
**The lead's answers, by the principles.** Four questions no accepted record settled were answered on 2026-11-11 by
the lead of issue 1409, against hq's principles, as the operator's way of working since 21:15 that evening has it (the
lead answers develop and design questions against the principles; no decision record is written for a detail). Each
line they reach names its answer:
- **A1, the vault's own-account rule** (§15 to §17). ADR 0550 §3 waits until the licence manager and each agent
module run under a bus account of their own (ADR 0349 §11, issue 569); both run in their machine's tool runner,
under its account. Settled by principle 6 (inside the mesh every agent and module is trusted; who may do what is not
a question) and the operator's instruction that who may do what is not the operator's question (approved
2026-11-11 21:15 CET): the vault serves licence secrets to the tool runner's account. Its check that the asking
machine is the node the binding names stays, as a guard against mistakes, not as a permission.
- **A2, the switch without a record** (§24 to §33). ADR 0183 §21 (moving a consumer is a person's act), ADR 0206 §16
(no `licence.switched` event) and ADR 0206 §18 (every later change is a person's act) are in force. The operator
decided, 2026-11-11 about 21:10 to 22:20 CET: switch automatically at 99 %, on all nodes, configurable; and said that
night not to write records for what they decided. So the operator's answer of 2026-11-11 replaces those three
clauses for this module, and no record is written.
- **A3, how the first start reads today's rows** (§1, §11, §14). A build whose manifest no longer requires
`postgres-database` is composed with no credential for it, and `mesh-store.query` refuses the grant column. Settled
by principle 7 (the simplest thing that works): this milestone keeps `requires postgres-database`, and the `secret`
`grant-key` that opens the grants at rest, as the carry-over's read-only source; dropping both, and the database's
retirement on the operator's Approve, is the module's next change once `licences_carried` (§44) shows the
carry-over done. The delivery gap this names: no path hands a build the credential of a provision its manifest
dropped, so a module cannot read its retiring database at the first start of its new form.
- **A4, no wait for design 47's milestone G** (§9, §50). Settled by principle 7: this milestone ships soon, built only
on what main holds today (mesh-sdk, the vault, the operator channel, and today's way a module says a condition,
ADR 0437 §9), so it uses neither G's writer nor its stream.
## The kept data on the bus
1. **The manifest asks for its kept data by template, and keeps its database only as the carry-over's source.**
It gains one `bus-state` entry per piece of kept data, each in the typed long form (ADR 0595 §1): the local name,
the storage type, the template and its options, as §2 to §9 list them. The old section `state` (today
`bindings`) and the `reads` entry `claude-code.holdings` go: the module moves to the new form whole (ADR 0595 §11),
keeping the local name `bindings` so its data stays. `requires` keeps `postgres-database` and the `secret`
`grant-key`, with their `contributes`, `binds`, `secrets` and the resource `database-url`, read only by §11 and
never written (A3); the run-once step `prepare`, which makes the tables, goes, since nothing writes them again.
`uses` gains `operator-channel` (§9, §28). Why: the operator, 2026-11-11, and principle 1; the templates are
ADR 0595 §2; A3. How it is checked: the manifest validator accepts the manifest at the merge check; a catalog test
finds no `reads`, old `state` or `prepare` in it; a unit test finds no write to the database in the code.
2. **State `licences`: one key per licence, no secret in it.** Template `state`, option `history` 1. The key is the
licence's key form (§10). The value: `name`, `kind` (`subscription` or `api-key`), `account` (the account id),
`address`, `organisation`, `refreshFingerprint`, `accessExpiresAt`, `refreshExpiresAt`, `failures` (refreshes
failed in a row), `notifiedAt`, `adoptedAt`, `adoptedFrom` (the node), `rotatedAt`, `generation` (the vault
secret's, §15), and `usage`, the latest reading of §24 (`sessionPct`, `sessionResetsAt`, `weeklyPct`,
`weeklyResetsAt`, `sonnetPct`, `sonnetResetsAt`, `readAt`, `readError`). Written by the manager alone, at each
adoption, rotation, failure and reading; deleted by no act of the milestone (a licence is never removed today, and
none is added here). It replaces the table `licence`, less its column `sealed`, which goes to the vault (§12).
Why: ADR 0550 §3 keeps the licence records without a secret in the manager; principle 4 keeps the latest reading
beside its licence so `licences` answers from one key. How it is checked: unit, a rotation and a reading each write
the key and no field holds a token; the after-move scan of §20.
3. **State `bindings`: one key per consumer.** Template `state`, option `history` 8, so the last moves of a consumer
are read with the state's `history` call. The key is the consumer's: the node's name for a node's agent, `<node>.<worker>`
for a worker (ADR 0183 §19). The value: `licence` (its name), `kind`, `generation` (one more at every rotation and
every switch of that consumer, ADR 0206 §13), `since`, `why` and `by` (who or what moved it last: a person's
name, `login` for ADR 0209's move, `usage` for §26). Written by the manager alone; deleted by `release`. It replaces
the table `binding` and today's old-section state of the same name. Why: ADR 0206 §13; the eight past values are
the design's choice, for `licences_switches` to show a consumer's last moves. How it is checked: unit, `bind`,
`switch`, `release` and §26 each write or delete the key with its `why` and `by`.
4. **State `leases`: one key per lease, ended by its holder or its lifetime.** Template `state`, option `history` 1,
with a lifetime per key of 2 minutes (the time one exchange with the vendor is given today). The key is
`licence.<licence key>` or `account.<account id>`; the value names the holder (the process) and `until`. A lease
is taken by creating the key, which fails while another holds it, before the licence is read (ADR 0183 §6); the
holder deletes it at the end; a holder that dies leaves it to its lifetime. It replaces the table `lease`. Why:
ADR 0183 §6 keeps one rotation source; a create that fails is the bus's own compare-and-set. How it is checked:
unit, two refreshes started together rotate once and the second answers "another run holds its lease"
(the main file's first acceptance test).
5. **State `offered`: one key per grant a node offered.** Template `state`, option `history` 1, a lifetime per key
of 90 days. The key is the refresh token's fingerprint; the value: `node`, `account`, `outcome` (`adopted`,
`dead`, `refused`), `why`, `at`. Written at each candidate's outcome (ADR 0206 §9, §10); deleted by its lifetime,
after which a fingerprint offered again is tried once more and recorded again. It replaces the table `offered`.
Why: the manager never exchanges a dead candidate twice within its lifetime; 90 days is longer than a refresh
token lives. How it is checked: unit, a dead candidate is not exchanged at its second report.
6. **Log `usage`: every reading, kept 35 days.** Template `log`, options `max-age-days` 35 and `max-mib` 256, one
subject per licence key. Each entry: `at`, the flattened reading of §24 and `raw`, the vendor's answer whole
(ADR 0054). Written at every reading, never rewritten. It replaces the table `usage`. Why: ADR 0054 keeps the raw
reading beside the normalised one; 35 days hold five weekly windows, and at five minutes for four licences
the log stays under a quarter of its cap. How it is checked: unit, a reading appends one entry under its licence's
subject; the bus module's fill condition (ADR 0595 §6) warns before the log is full.
7. **Log `audit`: every act, kept for good.** Template `log`, option `max-mib` 64, no age, one subject per kind:
`adopted`, `refused`, `failed`, `rotated`, `bound`, `switched`, `released`, `drift`, `carried`, `settings`. Each
entry: `at`, `by`, `why` and the act's own fields (for `switched`: §27's). Written after each act, never rewritten.
It replaces the table `audit`. Why: ADR 0183 §5 keeps the audit of every switch; a log keeps it in order and for
good. How it is checked: unit, each act appends one entry under its kind.
8. **State `carried`: the carry-over's record.** Template `state`, option `history` 1, one key `postgres`: `at`, and
for each table of today's database the rows read and the entries written. Written once, at the end of §11; never
deleted, since its presence is what keeps a second start from carrying over again. Why: ADR 0437 §7's way of
carrying a module's state over once. How it is checked: §13.
9. **Its conditions are said on the operator channel, and kept in its own state `conditions`.** The manager says
each condition of §11, §29 to §31 and §33 as any module does on main today (ADR 0437 §9): published on
`mesh.seat.operator-channel.accept.condition.claude-licence-manager` (the subject the operator channel's contract
names, through its client in mesh-contracts), carrying the condition's `key`, `severity` (`urgent` or `warning`),
`scope` and `id`, `raised` or `cleared`, its `summary` and ADR 0253's words (`headline`, `needs`, `explanation`
opened by its verdict, `resolved` and `actions`); the router alone decides whether and where the operator is told.
It keeps each condition it said raised in its state `conditions` (template `state`, option `history` 1, one key per
condition key, its body and `since`), written before it is said and deleted when it is said cleared. At its start
it judges each kind anew from its own state and readings, says raised again each it still holds and cleared each
key of `conditions` it no longer holds. A raise or clear is said again only when what it says changes (ADR 0289
§1). Why: A4 (today's way a module raises a condition, without G's writer); the state lets a restart clear what
it said. How it is checked: unit, a condition is written in `conditions` and published once with these fields;
a start with a key it no longer holds says it cleared; whole (manager and the router on a test bus), a raised
condition reaches the desk channel and its clearing clears it.
10. **A licence's key form.** A licence's name is its account's address, or its id where it has none, or the name
given at `adopt` for an API key (today's `NameFor`); a name holds characters a key may not. Its key is the name
with each `@` written as `_at_` and every other character outside letters, digits, `-` and `.` written as `_`. An
adoption whose key another licence already has is refused, said in the audit and as `licence.refused`. Every
verb and tool still takes and answers the name; the key is the store's alone. Why: a key of the bus takes only
those characters; the form is the design's choice, readable in `licences_kept`. How it is checked: unit, a table
of names and their keys, and the refused clash.
## Carrying over today's rows
11. **At its first start the manager carries over every row before it does anything else.** When the state
`carried` has no key `postgres`, the manager, before it hears an event, reads a holding report, refreshes or reads
usage, reads every row of the six tables of its database `claude_licences` and writes them as §12 says, then the
key `carried.postgres`. Until that key exists it serves its read verbs and tools only, answering every other
with "the carry-over has not ended", and says the condition `carry-over-failed` (§9; urgent; "Needs you: the licence
manager could not carry over its licences; no consumer is moved and no token is refreshed until it does", naming
the table and the error) when a read or a write fails, cleared when the key is written. Why: a manager that ran on
an empty state would take every holding report for a candidate (ADR 0206 §5) and exchange refresh tokens it
already holds; failure is loud (principle 3). Its source is the database read with the module's own credential, which this milestone keeps for it alone (A3). How it is checked: whole (manager, a store database filled from a copy of today's rows), every row
is carried and the key written; with the database unreachable the condition is raised and no vendor call is made. A table that does not
exist (a fresh install) is read as no rows.
12. **What each table becomes.**
- `licence` → a key of `licences` (§2) per row; each row's `sealed` grant is opened with today's `grant.key` and
handed to the vault as §15 says, its generation 1; the row's other columns become the key's fields.
- `binding` → a key of `bindings` (§3) per row, its `generation` kept, `why` "carried over", `by` the manager.
- `lease` → nothing: a lease lives two minutes, and none taken before the start still holds.
- `offered` → a key of `offered` (§5) per row, its lifetime counted from the row's `at`.
- `usage` → one entry of the log `usage` (§6) per row of the last 35 days, oldest first, its `at` kept.
- `audit` → one entry of the log `audit` (§7) per row, oldest first, its `at` kept, under the subject its `what`
names (`drift` for any other).
Why: no licence, binding, grant or history is lost (the operator, 2026-11-11); usage older than the log's age
would be dropped by the log at once. How it is checked: whole, the counts in `carried.postgres` equal the rows read
for every table but `lease`, and `mesh-vault.get` of each licence's grant (§16) answers it.
13. **A second start carries over nothing.** With `carried.postgres` present the manager never reads the database,
and a carry-over that failed halfway is started again from the beginning, every write of §12 being idempotent
(a key put again with the same value, a log entry not appended twice: before appending, the manager reads its
log's last entry for that subject and skips rows at or before its `at`). Why: ADR 0437 §7. How it is checked:
whole, a second start writes nothing; a start stopped after `bindings` and started again leaves every count equal
to the rows read.
14. **The database's fate, and that this delivery moves data.** In this milestone the database `claude_licences`
stays, read once by §11 and never written. Once `licences_carried` (§44) shows the carry-over done and §20's scan
finds no grant left outside the vault, the module's next change drops `requires postgres-database` and
`grant-key`; the provision is then retiring, its account removed only on the operator's Approve (ADR 0570 §5),
and the database is renamed aside by the postgres module's `postgres_retire_database` and deleted only by a
person (ADR 0230 §14). The delivery of this milestone moves data, so it is said to the operator before it starts
(which module, its machine, that its licences, bindings and grants move to the bus and the vault), and the
manager's machine is backed up before the module moves, as for every module that holds data. Why: A3,
ADR 0570 §5, ADR 0230; the operator's instruction that a delivery moving data is said first. How it is checked:
the delivery's plan names the backup; after the delivery `mesh-store.databases` still lists `claude_licences`.
## Grants and tokens in the vault
*Lines §15 to §20 build ADR 0550 §3, on the tool runner's account (A1).*
15. **The vault holds two secrets per licence, handed to it by the manager alone.** `licence.<licence key>.grant`
holds, for a subscription, the refresh token, the access token and both expiries, and for an API key the key;
`licence.<licence key>.access` holds a subscription's access token and its expiry, or the API key. The vault
serves a new verb on its seat, `licence-put`: `licence`, `part` (`grant` or `access`), `generation` and the value
sealed to the vault's key; answered for a call from the machine that holds the seat `anthropic-licence-manager`
(as the seat's placement names it), and refused from any other machine naming the rule, a guard against a
mistaken caller rather than a permission (A1); it keeps the newest generation and refuses an older one; and it says its event
`secret-changed` with the secret's name and generation, never the value. The manager calls it after every
adoption, rotation and carry-over (§12), the `grant` first. Why: ADR 0550 §3 (the manager hands what it gets back
to the vault, keeping nothing at rest); the names, the verb and its fields are the design's choice. How it is
checked: unit (vault), `licence-put` from another machine is refused and an older generation is refused; unit
(manager), a rotation calls it twice and keeps no token after the call returns.
16. **The manager reads a grant from the vault when it needs one, and keeps it only for that act.** To refresh,
adopt again or read usage, the manager calls `mesh-vault.get` for `licence.<licence key>.grant`, sealed to its own
public key, under the licence's lease (§4), and drops the value when the act ends. Why: ADR 0550 §3, ADR 0183 §6.
How it is checked: unit, the manager's state and log hold no token after a rotation.
17. **A consumer's agent module gets its token from the vault, which serves only the licence a binding names.** The
vault answers `mesh-vault.get` for `licence.<licence key>.access` to the tool runner's account of a machine,
and only when that machine is the node the asking consumer's binding names and the licence is the one that
binding names: a guard against a node applying another's licence by mistake, not a permission (A1). Its rule that a
value goes only to a module under a bus account of its own (ADR 0349 §11) does not apply to the licence
secrets. It learns the bindings from the
manager's `binding.changed` (§21), which it consumes, and at its start from `anthropic-licence-manager.bindings`,
its first view, keeping them in its own state `licence-bindings` (template `state`, one key per consumer, the
licence and generation, deleted on a release). The agent module never receives a refresh token (ADR 0206 §11).
Why: ADR 0550 §3; A1; ADR 0596 §2 (the vault asks the manager, never reads its state); the state is the
design's choice. How it is checked: unit (vault), a token is refused to a machine no binding names and for a licence its
binding does not name (the main file's acceptance test), and given after a `binding.changed` naming it.
18. **`current` answers which licence and its health, never the token.** The seat verb `current` takes `consumer`
and answers `licence`, `kind`, `generation`, `health` (`ok`, `failing`, `expiring`) and `accessExpiresAt`; its
argument `public_key` goes. Why: ADR 0550 §3. How it is checked: unit, no answer of `current` holds a token.
19. **The agent module applies a new token when the vault says it changed or its binding moved.** The agent module
on a node asks `mesh-vault.get` for its licence's `access` sealed to its own key, when it hears a
`binding.changed` for its consumer with a newer generation than it applied, when it hears the vault's
`secret-changed` for the licence it holds, and at its start; it then writes the credentials file or the key-helper
as design 36 §5 has it, unchanged. Why: ADR 0550 §3, ADR 0206 §14 as ADR 0596 §2 changes it (an event and a first
view in place of a watched key). How it is checked: whole, `switch` changes a workstation's token end to end (the
main file's acceptance test).
20. **What ends with the move.** The manager's `grant.key`, its `secret` requirement and the code that seals a grant
at rest go; the `public-key` verb and the manager's keypair stay, since a login offered by a node is sealed to it
(ADR 0183 §23, ADR 0550 §3). After the delivery the vault's `secret_holders` lists both secrets of every licence
and the manager's own secret `grant-key` is retired with `mesh-vault.retire` on the operator's word. Why:
ADR 0550 §3 (a scan after the move that the manager holds no grant). How it is checked: `licences_kept` (§41)
shows no field holding a token in any entry, and `secret_holders` the two secrets per licence.
## Bindings and holdings said as events
21. **Every change of a binding is said as `binding.changed`.** The manager declares in `emits` the event
`binding.changed`, said after the key of `bindings` is written or deleted: `consumer`, `licence` (empty on a
release), `kind`, `generation`, `from` (the licence before), `why`, `by` and `at`; never a token. Why: ADR 0596 §2,
§3 (the owner gains the event its readers need before they move); the name and fields are the design's choice.
How it is checked: unit, `bind`, `switch`, `release`, a login's move and §26 each say one event per consumer
moved.
22. **The agent module follows its binding by that event, and asks once at its start.** `claude-code` gains
`consumes` `claude-licence-manager.binding.changed`, acting only on the event naming its node's consumer, and at
its start asks `anthropic-licence-manager.bindings` with `consumer` its node for its first view; its `reads`
entry `claude-licence-manager.bindings` goes. A node that was away asks once when it is back (ADR 0206 §15), by its
start or its reconnect. Why: ADR 0596 §2, §4. How it is checked: unit (claude-code), an event for another
consumer is ignored; whole, a node started after a switch applies the new licence from its first view.
23. **A node's holdings are said as events, and asked for by the manager.** `claude-code` declares in `emits`
`holdings-changed`, said at its start and whenever its holdings change (ADR 0206 §2), carrying its holdings
report whole as ADR 0206 §1 lists it, never a token, and gains `consumes`
`claude-licence-manager.holdings.asked`, on which it says `holdings-changed` again. The manager declares
`holdings.asked` (`at`, `by`) and says it after its carry-over (§11) and on `licences_resync` (§42), and consumes
`claude-code.holdings-changed` in place of today's `reads`. Its state `holdings` stays `claude-code`'s own. Why:
ADR 0596 §2, §3; a first view by events, since the holdings are one key per node; the names are the design's
choice. How it is checked: whole (manager and a fixture agent module), a `holdings.asked` is answered by one
`holdings-changed` per node, and a report carrying an unknown refresh token's fingerprint makes a candidate
(ADR 0206 §4).
## Switching on usage
*Lines §24 to §33 are the operator's answer of 2026-11-11, which replaces ADR 0183 §21 and ADR 0206 §16 and §18 for
this module (A2): the manager moves consumers by itself on its own readings, and says every switch as an event.*
24. **The manager reads each subscription's usage on a cadence, and keeps the reset times.** Every
`usage_minutes` (§33), for each licence of kind `subscription`, the manager reads the vendor's usage with the
licence's access token (§16) and flattens it: `sessionPct` and `sessionResetsAt` (the five-hour window),
`weeklyPct` and `weeklyResetsAt` (the seven-day window), `sonnetPct` and `sonnetResetsAt`. It writes the entry of
§6, the key's `usage` of §2, and says `usage.read` with those fields, never a token. A failed reading writes
`readError` (the vendor's status and reason) and keeps the last percentages. This is the manager's one timer: the
vendor says nothing as usage grows. Why: ADR 0054; the operator's "reset times"; the reset times of the weekly and
sonnet windows are read beside the session's, which alone is read today. How it is checked: unit, a vendor
answer is flattened with its three reset times; a failed answer keeps the percentages and writes the reason.
25. **A licence's room, and when it is known.** A licence's room is 100 less the higher of its `sessionPct` and
`weeklyPct` in its latest reading. It is known only for a licence of kind `subscription`, read successfully within
`usage_stale_minutes`, whose refresh has not failed `failures_to_notify` times in a row and whose refresh token is
not expired. A licence whose room is not known is never a target of §26: an API key has no window to read and is
billed per use, so it is never one. Why: the operator's "a licence that still has room"; a licence's room is known
only from a reading; the sonnet window is shown and does not count, since the operator named the session and
weekly windows. How it is checked: unit, a table of readings, ages and healths and the room each gives.
26. **On each reading, a licence at its threshold moves every consumer bound to it.** After writing a reading of
licence L, when `switch_auto` is on, L has one or more consumers bound to it, and L's `sessionPct` or `weeklyPct`
is at or above `switch_at_pct`, the manager chooses the target: among the other licences whose room is known and
whose `sessionPct` and `weeklyPct` are both below `room_below_pct`, the one with the most room, the first by name
on a tie. It then moves every consumer bound to L to the target, each as the `switch` verb does (its binding's
generation one more, `why` naming L, the window, its percentage and its reset, `by` `usage`), says
`binding.changed` per consumer (§21) and one `licence.switched` (§27), and tells the operator (§28). A consumer
that inherits its node's licence (ADR 0183 §20) follows its node. A licence below its threshold, or with no
consumer, moves nothing; a consumer is never moved back by the rule, only off a licence at its threshold, so no
consumer flaps between two licences. Why: the operator, 2026-11-11 ("switch to a licence that still has room",
"on all nodes"; the manager itself, never a session); the order of choice and the tie are the design's choice.
How it is checked: unit, a table of licences, readings and bindings and the moves each gives; whole (manager, a
fixture vendor), a reading of 99 % moves four bound consumers to the licence with the most room in one act.
27. **Every switch is said as `licence.switched`.** The manager declares `licence.switched` in `emits` again and
says it once per act that moves consumers, by `switch` or by §26: `from`, `to`, `consumers` (the list), `by` (a
person's name or `usage`), `why`, and for §26 `window` (`session` or `weekly`), `pct`, `resetsAt` and the target's
`room`. The same fields are the log entry `switched` (§7). Why: the operator, 2026-11-11 ("each switch said as an
event"); this replaces ADR 0206 §16 for this module (A2). How it is checked: unit, one event per act with the consumers moved.
28. **Every switch the manager makes is told to the operator.** After an act of §26 the manager calls
`operator-channel.tell` (ADR 0380): `subject` the seat `anthropic-licence-manager`, `headline` "<consumers> moved
to <target>", and one line "<L> reached <pct> % of its <window> window, which resets at <time>; <target> has
<room> % room". A switch by the `switch` verb is not told, since the person who asked knows it. Why: the operator,
2026-11-11 ("told to the operator"); a notice that needs no answer is `tell` (ADR 0380). How it is checked: unit,
§26 calls `tell` once per act, and `switch` never.
29. **No licence with room is a condition, cleared when one has room.** When §26 finds no target for L, the manager says the condition `licence.<L key>.no-room` (§9), severity `urgent`: headline "No licence has room for
<consumers>"; "Needs you: add a licence, or wait for <L>'s <window> window to reset at <time>"; explanation
naming L's percentage and every other licence's room or why it is not known; resolved line "<consumers> have a
licence with room again". The manager clears it when a later reading of L falls below `switch_at_pct`, when its
consumers have moved (a target was found at a later reading of L), or when L has no consumer left. Why: the
operator, 2026-11-11 ("a condition when no licence has room, cleared when one has"); its words are ADR 0253's. How
it is checked: unit, with every licence at its threshold the condition is said once and cleared by a reading with
room; whole, the router takes its raise and its clearing.
30. **A licence near its limit is warned about once per crossing.** When a licence with one or more consumers has
`sessionPct` or `weeklyPct` at or above `warn_at_pct`, the manager says the condition `licence.<L key>.near-limit` (§9),
severity `warning`: headline "<L> at <pct> % of its <window> window"; "Nothing for you to do: <consumers> move to
<target> at <switch_at_pct> %" while §26 would find a target, "Needs you: no other licence has room" when it would
not; explanation naming the reset time. It is written again only when what it says changes (ADR 0289 §1), and
cleared when both windows read below `warn_at_pct`. Why: issue 1409 (a warning before the limit, cleared when the
reading falls back); ADR 0183 §21 (warned once per crossing of a declared threshold). How it is checked: unit, a
rising series of readings says the condition once at the crossing and again only when its words change; a reading
after the reset clears it.
31. **A licence whose usage cannot be read is a condition.** When a subscription's latest successful reading is
older than `usage_stale_minutes`, the manager says the condition `licence.<L key>.usage-unread` (§9), severity
`warning`, naming the vendor's reason from `readError`; "Needs you" only when consumers are bound to it ("<consumers>
are not moved off it while its usage is unknown"); cleared at its next successful reading. Why: principle 3 (a
switch that cannot see must say so). How it is checked: unit, three failed readings in a row write it, one
success clears it.
32. **The rule is started by hand too.** `licences_switch_now` (§39) runs §26 for one licence or all at once, from
their latest readings, and `licences_read_usage` (§38) reads first; with `dry` true either answers what §26 would
do and does nothing. Why: principle 4 (everything it does by itself can be started by hand). How it is checked:
unit, `dry` moves nothing and answers the same moves as the act.
33. **The settings, with their defaults, set through mesh-config.** The manifest's `config` gains, each `trusted`
and read from the settings file at every reading so a change takes effect at the next one: `switch_auto`
(boolean, default true: whether §26 moves consumers; off, §29 and §30 still raise), `switch_at_pct` (number 1 to
100, default 99), `room_below_pct` (number 1 to 100, default 90: what a target's windows must both be below),
`warn_at_pct` (number 1 to 100, default 90), `usage_minutes` (number 1 to 60, default 5) and
`usage_stale_minutes` (number 5 to 180, default 15). The settings file's template and its `takes-config` list
them beside today's five. A person sets one with `mesh-config.config`, at mesh or node level. When
`room_below_pct` is above `switch_at_pct` or `warn_at_pct` is above `switch_at_pct`, the manager says the condition `settings.<setting>.invalid` (§9) (warning, naming both values) and moves no consumer until it is fixed. Each change of
a value it reads is written in the audit under `settings`. Why: the operator, 2026-11-11 ("make it configurable";
99 by default); 90 as the room and the warning is the value the session used that night, so a licence warned
about is never a target; the bounds are the design's choice. How it is checked: unit, each setting's default and
bound, and the invalid pair moving nothing.
## The tools
*Each answers through the mesh MCP server; any person and agent sessions may call them. "Reads" and "acts" are each
tool's effect (the manifest's mark).*
34. **`licences` shows every licence with its latest usage and room.** The seat verb answers, per licence, today's
fields (`name`, `kind`, `identity`, expiries, `failures`, the consumers bound to it) and gains `usage` (§2's
latest reading with its reset times and `readAt`), `room` (§25, or why it is not known), `target` (whether §26
would choose it now), `adoptedAt`, `adoptedFrom` and the open conditions about it. Reads. Why: principle 4; the
operator's "read every licence with its latest usage and reset times". How it is checked: unit, an answer per
licence carries each field and no token.
35. **`usage` answers the history.** The seat verb takes `licence` (absent for all), `since` (a time) and `limit`
(default 20, at most 2000) and answers entries of the log `usage`, newest first, the flattened reading and, with
`raw` true, the vendor's answer. Reads. How it is checked: unit, `since` and `limit` bound the answer.
36. **`switch`, `bind` and `release` keep their arguments.** Each also says §21's and §27's events and writes the
audit with the caller as `by`. Acts. How it is checked: unit, as §21 and §27.
37. **`licences_switches`: every switch and why.** A module tool reading the log `audit` under `switched`: `licence`,
`consumer`, `since` and `limit` (default 20), newest first, each with `at`, `from`, `to`, `consumers`, `by`,
`why` and, for §26, the reading that moved them. Reads. How it is checked: unit, a switch by `usage` and one by a
person are answered with their causes.
38. **`licences_read_usage`: read usage now.** Takes `licence` (absent for every subscription) and `dry`; reads as
§24 does at once and then applies §26 unless `dry` is true; answers each reading and what §26 did or would do.
Acts. How it is checked: unit, as §24 and §32.
39. **`licences_switch_now`: apply the rule now.** Takes `licence` (absent for all) and `dry`; applies §26 from the
latest readings, even when `switch_auto` is off, since a person asked; answers the moves. Acts. How it is checked:
as §32.
40. **`licences_settings`: the settings in force.** Answers each setting of §33 and today's five: its value, its
default, where it was set (mesh, node or the default, as mesh-config answers it) and whether §33 finds it invalid.
Reads. How it is checked: unit, a value set at node level is answered with its level.
41. **`licences_kept`: every entry it keeps.** Takes `entry` (one of §2 to §9's local names; absent for all) and
`key`; answers each entry's keys (or the subjects of a log with their last entry), one key's value, and its fill
against its cap. Reads. Why: principle 4 and ADR 0564 §12 (a read of every bucket and stream a part keeps). How it
is checked: a unit test lists every bus-state entry of the manifest and fails on one this tool does not read.
42. **`licences_resync`: recover.** Says `binding.changed` again for every binding, `holdings.asked`, says again each condition it holds (§9) and calls `licence-put` again for each licence's `access` (§15); answers
what it said. Acts. Why: principle 4 (everything that goes wrong is recovered through its tools): a consumer, the
vault or the keeper that missed an event converges on one call. How it is checked: unit, each is said once per
binding, licence and condition.
43. **`licences_conditions`: the conditions it said.** Answers each key of `conditions` (§9) with its body and
`since`. Reads. Why: principle 4 (everything it holds can be read). How it is checked: unit.
44. **`licences_carried`: the carry-over's record, and its recovery.** Answers `carried.postgres` (§8), or, when it
is absent, the failure `carry-over-failed` names; with `run` true and the key absent, starts §11 again; refused
with the key present. Acts with `run`, reads without. How it is checked: unit, `run` is refused once carried.
45. **`refresh` and `adopt` stay.** `refresh` (one licence or every due one, under its lease) and `adopt` (an API key
from a file on the manager's node or sealed to its `public-key`) are unchanged but for reading and writing the
grant in the vault (§15, §16). Acts.
## A further licence
46. **A subscription licence is added by logging in on any node.** A person logs in to the new account (a team seat
among them) with the agent's own login on any node running `claude-code`. That node's `holdings-changed` (§23)
carries a refresh token whose fingerprint the manager does not hold, so it is a candidate; the manager asks that
node's `claude_code_grant` for it sealed to its key, refreshes it under the account's lease, and on success it is
a new licence, named by its account's address (ADR 0206 §4, §7, §8; §10), its grant handed to the vault (§15). The
node it came from is bound to it (ADR 0209); a person who wants that node back on its licence calls `switch`.
From the next reading (§24) it has a room and is a target of §26. Nothing is configured and no tool beside the
login is needed. Why: ADR 0206, ADR 0209. How it is checked: whole (manager, a fixture vendor), a report with a new
fingerprint is adopted, listed by `licences` with its organisation, and chosen as a target at its first reading
with room.
47. **A team seat's licence is shown as any subscription.** `licences` answers its `organisation` beside its address;
`kind` stays `subscription`. Whether the vendor's usage answer for a team seat carries the same windows is not
known (a guess that it does); if it does not, §31's `usage-unread` says so and it is never a target. Why: the
operator's new team-seat licence. How it is checked: live, after the login, `licences` lists it with a reading.
48. **An API key is added with `adopt`**, from a node through `claude_code_add_api_key` or from a file on the
manager's node (ADR 0209), and is never a target of §26 (§25). Why: ADR 0206 §6, ADR 0209.
## Delivery
49. **One pull request in mesh-catalog, with hq.** The three modules change in one mesh-catalog pull request; hq's
pull request carries this part, the main file and the glossary. Why: one milestone, one release (the operator's
instruction on designing a change as it should be).
50. **The order is the module graph's.** The edges the delivery plan derives: the bus module creates the manager's
bus-state entries before its want moves (the provision `bus-state`, ADR 0570 §4); `mesh-vault`'s `licence-put`
and its consumption of `binding.changed` are served before the manager calls them (a served verb); and `claude-code`
declares `holdings-changed` before the manager consumes it (ADR 0595 §11's judge refuses the reverse). It waits for
no other milestone: the operator channel's condition subject, the vault and mesh-sdk are used as main holds them
(A4). A node's agent keeps its last token, which lives hours, while the manager moves.
51. **Tonight's interim rule ends with the delivery.** Until the delivery, a session follows the readings and calls
`switch` (issue 1409's note of 2026-11-11 20:11); once §26 runs live, that session stops, said on issue 1409.
## Acceptance
52. **The tests of the milestone**, each run in its module's suite or live after the delivery:
- every row of today's database is carried over, counted in `carried.postgres`, and a second start carries
nothing (§11 to §13);
- after the delivery `licences_kept` and the vault's `secret_holders` show no token outside the vault (§20);
- a reading at `switch_at_pct` moves every consumer bound to that licence to the licence with the most room,
says one `licence.switched` and one `binding.changed` per consumer, and tells the operator once (§26 to §28);
- with every licence at its threshold, `no-room` is raised once and cleared by a reading with room (§29);
- a licence crossing `warn_at_pct` is warned about once (§30), and one that cannot be read is said (§31);
- each setting of §33 changes the rule at the next reading;
- every entry the manager keeps is read by `licences_kept`, and every act it takes by itself is started by a tool
(§37 to §45);
- live: a login to a new account on a node is adopted and listed with its usage (§46).
## The lead's answers to the prepare's questions
The prepare's design questions (2026-11-11), each answered by the lead in one line against
[the principles](../../../00-META/principles.md), as the operator's way of working says. A developer builds these as
written; they are cited as "design 39 bus-and-switch B*n*".
- **B1** (`licence-put` is not in the vault's seat definition): the vault's seat definition in mesh-contracts gains the
verb, and mesh-contracts joins this milestone as a second member of its delivery group (P5).
- **B2** (the validator refuses bus state by template from mesh-vault): `licence-bindings` stays in the vault's old
`state` section (P7).
- **B3** (who may read a licence's grant): no machine guard on `licence-put` or `get`; the generation guard stays.
Inside the mesh every module is trusted, and who may do what is not a question (P6; the operator's instruction
who-may-is-not-the-operators-question).
- **B4** (the manager has no vault key to seal to): two steps with a key made for the call, as `move-in` does (P7).
- **B5** (the vault numbers generations itself): the caller's generation is the record's generation (P8).
- **B6** (the vault takes only lower-case names): §10's key form is lower-case (P7).
- **B7** (claude-code needs the licence's key and the account's identity): `binding.changed` and `current` carry `key`,
and `current` keeps `identity` (P1, P3).
- **B8** (`get` names no consumer): the access token goes to machine M when any binding of a consumer on M names that
licence (P7).
- **B9** (no line names the `invokes` entries or claude-code's `consumes mesh-vault.secret-changed`): each is declared
in its module's manifest (P1).
- **B10** (`bindings` takes no `consumer`): an optional `consumer` argument; the §3 fields answered unpaged; the vault
reads it once at start (P7).
- **B11** (one generation counter for every consumer): a counter per consumer, from the higher of its own and its
node's last reported generation; `GenerationsAbove` goes (P7).
- **B12** (holdings unknown after a restart): the manager says `holdings.asked` at every start (P1).
- **B13** (`offered` key holds a colon; two outcomes §5 does not list): the key goes through §10's key form, and all
five outcomes are kept (P7).
- **B14** (`since` and `adoptedFrom` have no column; the live database's name): `since` is the carry-over's time,
`adoptedFrom` comes from the audit, and §14 checks the database the module's binding names (P7).
- **B15** (§20 retires `grant-key`, §1 and A3 keep it): only the sealing at rest goes now; `grant-key` is retired in
the module's next change, with the store database (P7).
- **B16** (`licence.bound`, `licence.released`, `licence.rotated`): dropped; `binding.changed` and the audit log
cover them (P8).
- **B17** (the conditions' scope and id): scope `module`, id `claude-licence-manager` (P7).
- **B18** (no mesh or node level for a module's own setting in mesh-config): the settings are the module's own values
on the manager's node, set and read through mesh-config as any module's own setting (P7).
- **B19** (`current`'s health thresholds and §34's field names): failing at `failures_to_notify`, expiring within
`refresh_warn_days`; today's field names are kept (P7).
- **B20** (the audit log is full): the act fails loudly and says so (P3).
- **B21** (no tool reads the vault's `licence-bindings`): `vault_first_views` and `vault_first_view_again` cover it (P4).
- **B22** (three internal forms): `bindings` stays at form 0; each reader decodes the event bodies with fixtures
copied from the writer; nothing to build for an inheriting consumer (P7).
- **B23** (what `licence-put` names a licence by): its `licence` argument is the licence's key of §10; the manager forms
keys, the vault does not (P8).
- **B24** (an equal generation in `licence-put`): with the same value it is unchanged; with a different value it is
refused loudly as not newer (P3).
- **B25** (the vault matching a token's secret name to a binding it learned at start): the `bindings` verb answers
`key` beside the §3 fields, as `binding.changed` does (B7); the vault's `licence-bindings` keeps `licence`, `key` and
`generation`, and compares the secret's key with the binding's key. One form travels; the vault never computes §10's
key form (P1, P7).
- **B26** (the form of `carried.postgres`): `{at, tables: {<table>: {read, written}}}` (P7).
- **B27** (the carry-over in the audit log): it appends one `carried` entry, the kind §7 lists (P7).
- **B28** (an `offered` row older than 90 days): counted as read, not written (P7).
- **B29** (`adoptedFrom` at the carry-over): the newest `adopted` audit row's `node`, else its `from` (P7).
- **B30** (when bind and switch say `licence.switched`): only when the consumer moves from another licence (P8).
- **B31** (an access token asked before the vault's first view of the bindings is answered): refused, saying why (P3).
- **B32** (a first view of the bindings racing an event heard just before it): a first view never removes or replaces a
binding whose generation is newer than the view's; the newer by generation stays (P3).
- **B33** (what §19's "applied" means): the home holds the token; a home with no token is fetched again at the same
generation (P3, ADR 0266).
- **B34** (a failed usage reading): no `usage` entry is appended; it is said by `readError` and `usage-unread` (P3).
- **B35** (a reading with no weekly window): its room is unknown, and it is never a target (P3).
- **B36** (both windows at the threshold): the act names the higher, the session window on a tie (P7).
- **B37** (a setting outside its bound): it keeps its default and raises `settings.<setting>.invalid`, cleared when the
setting is fixed (P3).
- **B38** (a subscription never read successfully since its adoption): `usage-unread` is raised once no reading has
succeeded for `usage_stale_minutes` counted from its adoption (P3).
- **B39** (a condition's headline longer than the operator channel's 60 characters): the headline names counts and
short names only ("No licence has room for 4 machines", "<account> at 99% weekly"); the full list, the licence and
the reset time go in the condition's detail. A cut at 60 characters with "…" stays as the last guard so a condition is
never refused unsaid (P3).
- **B40** (§34's `target`): true for the licence §26 would move a full licence's consumers to once it reaches its
threshold: the best licence with room now, whether or not any licence is at its threshold; so the tools say where
consumers go next (P4).
- **B41** (§9 and §29's whole test and the router): the manager runs whole on the test bus, and its condition is
taken off its subject and decoded by the operator channel's client, the contract the router reads; the router, another
module, is not run in this module's test, and the report says so (P1).
- **B42** (which first view `vault_first_view_again` asks again): a new argument `of` names the view by its owner's
state (`mesh-delivery-assigner.assignments` or `claude-licence-manager.bindings`); absent, both are asked again, as
the tool did before for its one view (P4).
- **B43** (B32 for a consumer absent from the first view): a `licence-bindings` key written by an event after the first
view was asked is kept; only a key written before it was asked, and absent from the view, is removed (P3).
- **B44** (§12's leases, review round 1): the carry-over carries each lease still live at its run, as a `leases` key with
its remaining lifetime, so the new manager never refreshes a licence the old one holds a refresh lease on, whatever the
order in which the delivery stops one and starts the other; an expired lease is counted read and not written (P3, P7).
- **C1** (the operator, 2026-11-12: "the licence manager module should have a service part and a tools part"; met live
when the manager, run inside the machine's tool runner, was issued none of its own logs): the module declares
`runs-as claude-licence-manager` with an account of its own, as the vault and the mail reader do. Its own runtime runs
the service part (the carry-over at start, the usage timer, the switch rule, the conditions) and serves the tools part
beside it, so every kept entry, log and secret of §1 to §20 is issued to its own account, never to the tool runner's.
The tools act only through the running service: no tool keeps a second copy of what the service does (P1, P7).
- **C2** (review of C1: the key-pair file the manager wrote in its state directory under the tool runner's account,
which the node-engine does not hand to the new owner): when the manager cannot read the key pair it finds, it makes a
new one in its place and says so in its journal; every use passes the public key per ask, so nothing depends on the
old pair (P3, P7). The node-engine handing a program-written directory's contents to a new owner (every module that
moves to `runs-as`) is noted on its issue, not built here.
- **C3** (met live after C1: the manager, now in its own runtime, was still issued none of its logs): the two logs
`usage` and `audit` are declared in the manifest's `logs` section, the form the runtime on main issues today; the
states stay templates. The move of logs to templates is design 47 milestone G's (P7).
- **C4** (the operator, 2026-11-12: "the session limit has been deprecated by anthropic"; met live: both licences read
`sessionPct` empty, so neither had room and the switch could choose no target): a licence's room is 100 less its
weekly percentage alone; the session window takes no part in room, the switch threshold, the warning or the
conditions, wherever §24 to §33 name it. A reading with no weekly percentage still has unknown room (B35).
- **C5** (met live 2026-11-12: `licences_switches` answered every switch carried from the old database with no `to` and
no `consumers`): a carried entry keeps the old manager's fields as §12 copies them, a switch written as its
`consumer`, its `licence` and `from`; §37's reader answers such an entry with `to` its `licence` and `consumers` the
one `consumer`, and its filters by licence and consumer read it the same way. The log is not rewritten (P3, P7).
+244
View File
@@ -0,0 +1,244 @@
---
layer: to-be
design: 39
milestone: parts
updated: 2026-11-12
code: [mesh-catalog modules/claude-licence-manager]
decisions:
- "ADR 0183"
- "ADR 0050"
- "ADR 0054"
- "ADR 0040"
- "ADR 0192"
- "ADR 0198"
- "ADR 0259"
- "ADR 0349"
- "ADR 0436"
- "ADR 0570"
- "ADR 0230"
- "ADR 0595"
- "ADR 0672"
- "ADR 0675"
---
# Design 39, milestone parts — the licence manager built of its parts
**What it builds.** The module holding the seat `anthropic-licence-manager` is one manifest built of four parts,
each in a form the manifest validator accepts and the builder, the node-engine and the tool runner honour today
([design 20](../20-writing-a-module.md) *Who reads a manifest* and *What a module is built of*; playbook
[06](../../../00-META/process/06-writing-a-module.md) *Where the parts of the mesh read a manifest differently*): the
manager bundle, which runs long and answers the seat's verbs and every act; the tools bundle, which answers the
module's reading tools; the settings file; and the module's account with its directory. The carry-over from the store
database, done live, leaves the module whole in this milestone, with its requirement of the database and of the secret
`grant-key`, and both provisions then retire on the operator's Approve. The whole shape is in the design's main file
([39](../39-the-anthropic-licence-manager.md)); this part holds every line the milestone builds, cited as "design 39
parts §n".
**Why now.** The operator, 2026-11-12: "build the anthropic-licence-manager as clean as possible"; a module is one
manifest with several parts; the seat stays named for the vendor (ADR 0183 §4, ADR 0050, ADR 0054), and a second
vendor is a module of its own. That morning the operator had said "the licence manager module should have a service
part and a tools part" (design 39 bus-and-switch C1). Today, after milestone bus-and-switch, one Go bundle `code` does
everything in one program: it runs long, answers the seat's ten verbs and the module's eight tools, and keeps the
carry-over's code, which ran once, live, at 2026-11-12 09:44:59Z, carrying every row of every table (issue 1409's note
of that morning); for that code alone the module still requires `postgres-database` and the secret `grant-key`
(bus-and-switch A3, B15).
**Where.** One repository, mesh-catalog, and one of its modules, `claude-licence-manager`; hq carries this part, the
main file and the glossary. No other module changes: every verb, event and provision the manager uses is served today
(§17).
**The shape at a glance.**
| a module's part | its artifact | how it runs | its account | what it keeps or reads |
|---|---|---|---|---|
| the manager | `manager`, a Go bundle, binary `claude-licence-manager` | loaded by the module's runtime of its own; runs long and answers the seat's ten verbs and the three acting tools | the module's account `claude-licence-manager`, on the runtime's bus account | writes all its kept data (bus-and-switch §1 to §9) and its two logs; reads its key pair and settings file |
| the tools | `tools`, a Go bundle, binary `claude-licence-manager-tools` | loaded by the same runtime; answers the four reading tools | the same | reads the kept data and the logs, writes nothing |
| the settings | `files`, an archive of the templates directory | the `settings` file resource, written by the node-engine | owned by the module's account | holds the eleven settings mesh-config sets |
| the account and its directory | none | the `user` resource `account` and the `directory` resource `state` | `claude-licence-manager` | the key pair, the settings file, the runtime's `broker` secret |
## The module's parts
1. **The module is four parts in one manifest, and runs no process resource, container or run-once step of its
own.** `build.artifacts` names exactly `manager`, `tools` and `files`; `resources` names exactly `account`,
`state` and `settings`, in that order; the module's runtime of its own is the process the node-engine composes for
`runs-as` (design 20 *A runtime of its own*), never one the manifest writes. Why: the operator, 2026-11-12; a module
is one piece of software built of parts (ADR 0040); principle 7 (fewer parts). How it is checked: the manifest
validator accepts the manifest at the change's check, and the composition step composes it for the manager's
machine; a catalog test of the module reads its manifest and fails on any other artifact or resource, a `process`
or `container` resource, or a `run-once` field.
2. **The manager bundle runs long and answers the seat's verbs.** The artifact `manager` (today's `code`, renamed):
kind `bundle`, `language` go, `system` arch, `from` `cmd/manager`, `binary` `claude-licence-manager`, `loads`
that binary, and `env` with exactly two words, `MESH_LICENCE_STATE` (`${dir:state}`) and `MESH_LICENCE_SETTINGS`
(`${dir:state}/settings.json`). It is loaded by the module's runtime of its own and runs long (ADR 0198 §1, §2: a
bundle may serve tools, run long, or both): its usage timer, its refresh cadence, the switch rule on each reading,
its consumption of `claude-code.holdings-changed`, the conditions it judges at its start, and every event of
bus-and-switch §21 to §27. It registers the seat's ten verbs (`licences`, `bindings`, `bind`, `switch`,
`release`, `refresh`, `usage`, `adopt`, `current`, `public-key`) under the seat and the three acting tools of §4
under the module. It is the only part that writes the module's kept data and logs, emits, calls the vendor, calls
the vault, tells the operator and reads the key pair. Why: one rotation source (ADR 0183 §6); the binary keeps its
name so the runtime's journal and the loaded entrypoint stay as they are. How it is checked: unit tests as
bus-and-switch has them, moved with the code; the catalog test of §1 reads the artifact's fields.
3. **The tools bundle answers the module's reading tools, and writes nothing.** The artifact `tools`: kind `bundle`,
`language` go, `system` arch, `from` `cmd/tools`, `binary` `claude-licence-manager-tools`, `loads` that binary, and
no `env`: the runtime hands a module's words to all its bundles (ADR 0192), so it reads `MESH_LICENCE_SETTINGS`
from the manager bundle's words, and a word two bundles gave different values would be refused at composition. It
registers under the module the four reading tools, `licences_switches`, `licences_settings`, `licences_kept` and
`licences_conditions`, with the arguments and answers of bus-and-switch §37, §40, §41 and §43. It reads the
module's kept data with the runtime's bus-state calls and the logs `usage` and `audit` with the runtime's log
calls, each answered as the module whose bundle asks; it calls `seat:mesh-config.config` for `licences_settings`
(bus-and-switch B18). It writes no key, appends to no log, emits nothing, subscribes to no event, asks no
heartbeat, calls neither the vendor nor the vault and opens neither the key pair nor a secret. Why: principle 4
(everything a module holds can be read): the runtime serves the tools of every bundle of a module that loaded, so
these four still answer while the manager bundle fails to load or stops; C1 (the tools act only through the running
manager) holds, since these do not act; principle 1 governs reading between modules, and both bundles are the one
module reading its own data. How it is checked: a unit test of the tools bundle runs each tool against a fixture
of the runtime's calls and fails on any write, append, emit or subscribe; whole, with the manager bundle refusing to
start, `licences_kept` answers every entry.
4. **Every act is the manager's.** The three tools that act, `licences_read_usage`, `licences_switch_now` and
`licences_resync` (bus-and-switch §38, §39, §42), are registered by the manager bundle beside the seat's verbs, and
no act is answered by the tools bundle. A person's act on a consumer or a licence stays a seat verb (`bind`,
`switch`, `release`, `refresh`, `adopt`). Why: C1 (no tool keeps a second copy of what the manager does);
ADR 0183 §6; principle 7 (no call between the two bundles, so no path the mesh lacks). How it is checked: the
catalog test of §1 finds the module's `tools` to be exactly the four of §3 and the three of this line; a unit test of each
bundle lists what it registers.
5. **The code both bundles share is one package of the module.** The package `internal/licences`, in the module's
one Go module, holds the layouts of the kept data and of the log entries (bus-and-switch §2 to §9), the key form
(§10), the names of the entries, and the settings with their defaults, bounds and validity (§33, B37); `cmd/manager`
and `cmd/tools` each import it, and neither holds a copy of what it holds. Why: a layout in one place, so the reading
tools never read a form the manager no longer writes; it is code the module owns alone (design 20 *Where a seat's
code goes*: a module's own package). How it is checked: the review reads that no type of a kept entry is declared
in either command package; `go vet ./...` at the module's root builds both.
6. **The settings file stays, without the database's template.** The resource `settings` (a `file` at
`${dir:state}/settings.json`, mode 0600, owner `claude-licence-manager`, `template` `files/settings.json`,
`merge` json, `takes-config` the eleven settings of bus-and-switch §33 and today's five) and the archive `files`
(`from` `files`, the one archive the templates are read from, ADR 0349 §2) stay as they are. The template
`files/database.url` is deleted. Why: the manager reads its settings at every reading (bus-and-switch §33); the
database's address has no reader once §9 holds. How it is checked: the composition step writes the file; the catalog
test of §1 finds no file under `files` but `settings.json`.
7. **The account, its directory and the runtime's secret stay.** `runs-as` `claude-licence-manager`, `secrets-owner`
the same, the own secret `broker` at `${dir:state}/broker`, the `user` resource `account` and the `directory`
resource `state` (mode 0700, owner the account, `place` `.`, `unknown` `program`) stay as they are; the key pair
`manager-key.json` stays in that directory, read and made only by the manager bundle (C2). No `runtime-health` is
added: the node-engine's judgement that the runtime is alive stands as its health. Why: design 20 *A runtime of its
own*; principle 7. How it is checked: the catalog test of §1.
8. **The seat's verbs are answered on the bus account of the runtime of its own, never by a process account.** The
manager bundle answers the verbs through the runtime that `runs-as` gives the module (ADR 0259 §8; ADR 0436 §5:
`runs-as` is unchanged and still gets a runtime of its own), as the router answers the operator channel's. The
manager is not a `process` resource with `bus`: a process account is granted no subject on which a tool or a seat's
verb is answered and holds no seat (ADR 0436 §3), which only ADR 0672 §7 lifts, for the recovery module, and
ADR 0675 §1 and §2, for the bus module's keeper; and a process account's grant names events, work queues, states,
asks and calls (ADR 0436 §1), never the old-section logs `usage` and `audit` the manager appends to (C3). Why: these
records. How it is checked: the catalog test of §1 finds no `process` resource and no `bus` key in the manifest;
whole, after the delivery, `anthropic-licence-manager.licences` answers through the mesh MCP server.
## The carry-over leaves
9. **The carry-over is done, and its code leaves the module now.** The carry-over (bus-and-switch §11 to §13) ran on
2026-11-12 at 09:44:59Z, every table's count equal and no lease live (issue 1409's note of 10:4x CET). In this
milestone the code that reads the store database, the carry-over itself, the opening of grants at rest with
`grant.key`, the gate that answers "the carry-over has not ended", the condition `carry-over-failed` and its
recovery go, with the postgres driver in the module's `go.mod`. No run-once step is built for it: no row is left to
carry, a fresh install has no database (bus-and-switch §11: read as no rows), and a run-once process with a bus
account could not append to the logs (§8). Why: bus-and-switch A3, B15 and §14 named this change as the module's
next; principle 7. How it is checked: the module's `go.mod` requires no postgres driver; a unit test fails on the
words `DATABASE_URL_FILE` or `MESH_LICENCE_KEY_FILE` anywhere in the module's code.
10. **What leaves the manifest, and what stays of the carry-over.** `requires` loses `postgres-database` and `secret`;
`contributes`, `binds` and the whole `secrets` section go; `env` loses `DATABASE_URL_FILE` and
`MESH_LICENCE_KEY_FILE`; the resource `database-url` goes; the tool `licences_carried` goes. The kept-data entry
`carried` stays, with its one key `postgres` as the record of the carry-over, read by `licences_kept`; nothing
writes it again. Why: removing the entry would drop kept data, which needs a warrant and a snapshot (ADR 0595 §5),
for nothing gained; principle 7. How it is checked: the catalog test of §1; after the delivery `licences_kept`
with `entry` `carried` answers the record of 09:44:59Z.
11. **The manager starts without waiting for a carry-over.** At its start the manager bundle judges each kind of
condition anew from its own state and says cleared each key of `conditions` it no longer holds (bus-and-switch
§9), `carry-over-failed` among them when present; then it says `holdings.asked` (B12) and starts its timers. It
reads `carried` never. Why: bus-and-switch §9, B12. How it is checked: unit, a start with a `carry-over-failed` key
in `conditions` says it cleared once and deletes the key.
12. **The pull request waits on two readings.** Before the push, the lead reads `licences_carried` of the running
build answering `carried.postgres` with every table's read equal to its written, and `mesh-vault.secret_holders`
listing `licence.<key>.grant` and `licence.<key>.access` for every licence `licences` answers, and writes both
readings in the milestone file. Why: bus-and-switch §14 and §20 (the database's requirement goes only once the
carry-over is done and no grant is left outside the vault); principle 3. How it is checked: the code review finds
both readings in the milestone file.
## The store database retires
13. **Both provisions retire, each on the operator's Approve, asked by the provisioner.** Once the new build's want
moves, no want needs this module's `postgres-database` nor its secret `grant-key`, so the holder of
`mesh-provisioner` marks both keys `retiring` and raises one ask each on the operator channel, saying what is
removed from which provider on which node (ADR 0570 §5). On the warrant postgres retires the consumer's account of
the database `claude_licences` and the vault removes `grant-key`; on a decline both stay retiring. The module asks
nothing. Before the delivery starts, the lead tells the operator that these two asks will come and what each
removes. Why: ADR 0570 §5; this replaces bus-and-switch §20's `mesh-vault.retire` of `grant-key`. How it is
checked: after the delivery `operator-channel.asks` holds both asks; after each warrant the provision's key is gone.
14. **The database's data is removed only by the operator.** Retiring keeps the database `claude_licences` and its
data (ADR 0570 §5). Its removal is a person's act, the postgres module's tool `provisioner_delete` called through
the mesh (ADR 0230 §5, §34): after both warrants the lead asks the operator for it, saying it removes the store's
copy of the licences, bindings, grants sealed with `grant-key`, usage and audit, whose every row is on the bus or
in the vault (§12). Why: ADR 0570 §5, ADR 0230; data removed is the operator's. How it is checked:
`mesh-store.databases` lists `claude_licences` until the operator's call and not after it.
15. **The files the database's requirement wrote leave the state directory with it.** `database.url` is the
resource `database-url`, which the node-engine removes as it removes every resource that leaves a declaration
(playbook 06, step 5); `database.json`, `database.secret` and `grant.key` are the files it wrote for the
requirements of §10, and whether it removes such a file when its requirement leaves is not known (a guess that it
does). A file left behind is a fault of the node-engine, noted on its issue and never removed by hand. Why:
principle 2. How it is checked: after the delivery, `node-reads.list` of the module's state directory on the
manager's machine lists none of the four.
## Delivery
16. **One pull request in mesh-catalog, with hq.** The module's change is one mesh-catalog pull request; hq's pull
request carries this part, the main file and the glossary. Why: one milestone, one release (the operator's
instruction on designing a change as it should be).
17. **No order between modules; within the module, the node-engine's.** The change adds no served verb, provision
or event and drops none another module uses: the manager still calls `seat:mesh-vault.licence-put` and
`seat:mesh-vault.get`, `seat:operator-channel.tell`, `seat:mesh-config.config` and `claude-code.claude_code_grant`,
and consumes `claude-code.holdings-changed`, each served or said today, so the delivery plan derives no edge. On
the manager's machine the node-engine writes the state directory and the settings file, unpacks both bundles, and
restarts the module's runtime once, since its `restart-on` names every bundle's archive; the runtime then loads
both. The kept data keeps its names and form, so no restore point is taken (ADR 0672 §1 is for a form change), and
the delivery itself moves no data; a node's agent keeps its last token, which lives hours, while the runtime
restarts. Why: the module graph orders deliveries; ADR 0595 §11. How it is checked: the delivery plan names one
module and no edge.
## Acceptance
18. **The tests of the milestone**, each run in the module's suite or live after the delivery:
- the manifest is accepted by the manifest validator, built by the builder and composed by the node-engine for
the manager's machine, with the artifacts and resources of §1 and nothing else (§1 to §8);
- after the delivery the runtime serves the seat's ten verbs and the module's seven tools, the four reading tools
from the tools bundle (§2 to §4);
- with the manager bundle refusing to start, the four reading tools still answer (§3);
- the module's code names no database and no `grant.key` (§9), and `licences_kept` answers the carry-over's record
(§10);
- after the delivery the provisioner holds both retiring asks, and after the operator's call the database is gone
(§13, §14);
- the state directory holds none of the database's files (§15).
## The lead's answers
- **D1** (§12's vault reading, review round 1: no vault tool lists licence secrets, so `secret_holders` cannot show
`licence.<key>.grant` and `.access`): the reading that proves bus-and-switch §20's condition is, for every licence
`licences` answers, a rotation after the carry-over with no failure (`rotatedAt` later than `carried.postgres.at`,
`failures` 0), since a rotation reads the grant from the vault and puts both secrets back; and the vault's journal
handing `licence.<key>.access` to a bound node. Both go in the milestone file before the push (P3, P7).
- **D2** (§17's wording): the manager calls `seat:mesh-config.config` no more; the tools bundle does (§3). `invokes`
is the module's, so the manifest is unchanged (P8).
- **D3** (§15's check, met after the delivery: `node-reads.list` and `node-files.list` refuse the module's state
directory as a place a secret is kept, so no tool can say its database files are gone; prepare in the fix's map):
- §15 is read with `node-files.list`, not the retired `node-reads` (P7, P8).
- `node-files.list` of a place a secret is kept, at any depth beneath it, answers each entry's name, type, owner,
group, mode, size and a link's target held to the content rule, never content, and marks the place
`secret-place` as `stat` does; this holds for the mesh's places and a machine's credential places, and a process's
`/proc/<pid>/` entries stay refused (P4, P6, P7).
- `node-files.stat` of a path that does not exist answers that it does not exist, not that file modes refuse it, so
§15's "gone" can be read (P3).
- The seat contract's `list` text in mesh-contracts says so in the same delivery group; playbook 08's one-repository
rule is bent for one line of text, said in the pull request (P5).
- The lines the fix touches cite ADR 0394 §7, the record that refuses a secret place (P8).
- The fix runs under issue 1409, whose part this check is, with a note on issue 1844; its own pull requests, since
the part's pull request has merged (P7).
- **D4** (met live: the store held the module's database as `mesh_novox_licmgr`, the name postgres gave it, not the
`claude_licences` the manifest asked for): §13 and §14 read the database as the one postgres made for the module,
named in `mesh-store.databases`; it was deleted on the operator's yes on 2026-11-12, set aside first by postgres's
own tool, because the provisioner never retired it (P3).
@@ -1,10 +1,20 @@
---
layer: to-be
status: in-progress
code: [mesh-catalog modules/claude-licence-manager, mesh-vault]
updated: 2026-10-26
code: [mesh-catalog modules/claude-licence-manager, mesh-catalog modules/claude-code, mesh-catalog modules/mesh-vault]
updated: 2026-11-12
decisions:
- "ADR 0550"
- "ADR 0595"
- "ADR 0596"
- "ADR 0437"
- "ADR 0564"
- "ADR 0570"
- "ADR 0230"
- "ADR 0380"
- "ADR 0253"
- "ADR 0289"
- "ADR 0349"
- "ADR 0209"
- "ADR 0206"
- "ADR 0183"
@@ -15,6 +25,13 @@ decisions:
- "ADR 0126"
- "ADR 0132"
- "ADR 0159"
- "ADR 0040"
- "ADR 0192"
- "ADR 0198"
- "ADR 0259"
- "ADR 0436"
- "ADR 0672"
- "ADR 0675"
---
# 39 — The Anthropic licence manager
@@ -29,45 +46,62 @@ which is the consumer on every node.
## Every grant in the vault
*Decided 2026-10-26 (ADR 0550 §3), on the operator's answer that every secret lives in the vault; built in
ADR 0547's second milestone (issue 1126). Until it is delivered, the sections below hold as first written.*
*Decided 2026-10-26 (ADR 0550 §3), on the operator's answer that every secret lives in the vault; built in milestone
bus-and-switch (§15 to §20 of its part, [39-milestones/bus-and-switch.md](39-milestones/bus-and-switch.md)), the vault
serving the licence secrets to the tool runner's account (principle 6; its answer A1). Until it is delivered, the
sections below hold as first written.*
- **The vault keeps every licence's secrets**: the refresh tokens, the API key and the access tokens are vault
secrets ([24](24-the-secrets-vault.md)). The manager keeps its bindings, its licence records without a secret
(account, kind, health, usage, audit), and nothing at rest that is secret.
- **The manager stays the one rotation source.** It alone calls the vendor's token endpoint: to refresh or adopt,
it asks the vault for the grant sealed to itself and hands what it gets back to the vault. A grant offered at
login, sealed to the manager's key (its `public-key` verb is kept), is handed on to the vault the same way.
- **A holder's token comes from the vault.** The agent module on a node registers its public half with the vault,
not the seat, and asks the vault for its token sealed to it. The vault serves only the licence the manager's
binding names for that holder, and says `secret-changed`, never a value. The seat's `current` verb answers which
licence a consumer holds and its health, never the token.
- **It waits for accounts of their own.** The vault hands a value only to a module running under a bus account of
its own (issue 569), so the move waits until the manager and each agent module run under one.
*How it is checked:* a unit test that the vault refuses a token to a holder no binding names or to one without an
account of its own, and a scan after the move that the manager's tables hold no grant.
- **The vault keeps every licence's secrets**, two per licence: its grant, and the access token a consumer is handed
([24](24-the-secrets-vault.md)). The manager keeps its bindings and its licence records without a secret.
- **The manager stays the one rotation source**: it reads a grant from the vault for one act and hands what the
vendor answers back to it, keeping nothing at rest.
- **A consumer's token comes from the vault**, which serves only the licence the manager's binding names for that
consumer, learned from the manager's `binding.changed`; the seat's `current` answers which licence and its health,
never the token.
## 1. What it is
A module, `claude-licence-manager`, holding the mesh-scoped seat **`anthropic-licence-manager`**. One
holder, on the node the operator assigns it to — the control node is the natural one, and nothing in the
definition says so. It requires a database for its own store and the bus; it claims the seat; it serves
the seat's verbs. It has no port, no route, no file under anyone's home.
definition says so. It claims the seat and serves the seat's verbs. It has no port, no route, no file under anyone's
home.
Its store holds four things:
**It is one manifest built of four parts** (ADR 0040; milestone parts, its part
[39-milestones/parts.md](39-milestones/parts.md)), each in a form the manifest validator accepts and the builder, the
node-engine and the tool runner honour today ([20](20-writing-a-module.md)):
| table | holds |
|---|---|
| **licences** | name, kind (`subscription` or `api-key`), the account's identity (id, address, organisation) once adopted, the grant encrypted at rest, when the access token expires, when the refresh token expires, consecutive failures, the refresh lease, when a person was last notified |
| **bindings** | one row per consumer: kind (`node-agent`, `node-session`, `worker`), its key (the node, or the node and the worker), the licence, or *inherit* |
| **usage** | the vendor's readings per licence per period, raw beside normalised (ADR 0054) |
| **audit** | every switch, adoption, refusal and drift, with who asked |
| a module's part | its artifact | how it runs |
|---|---|---|
| the manager | a Go bundle | loaded by the module's runtime of its own (`runs-as`), under the module's account: it runs long (the usage timer, refreshes, the switch rule, the conditions), answers the seat's verbs and every tool that acts, and alone writes what the module keeps (ADR 0198 §1, §2) |
| the tools | a Go bundle | loaded by the same runtime: the tools that only read the module's kept data and logs, served while the manager fails |
| the settings | the templates' archive | the settings file the node-engine writes from the template and mesh-config's values |
| the account and its directory | none | a `user` and a `directory` resource |
**The grants are encrypted with a key the vault made for the manager** — its one `secret` requirement.
The vault keeps that key; the manager keeps the grants. That is ADR 0050's carve-out, one module, one
node, the long-lived grants only. ADR 0550 §3 ends the carve-out: the grants move into the vault, and the
`licences` table keeps no grant (*Every grant in the vault*, above).
The seat's verbs are answered on the bus account of the runtime of its own (ADR 0259 §8, ADR 0436 §5), never by a
process account, which answers no seat's verb (ADR 0436 §3, lifted only for the recovery module, ADR 0672 §7, and the
bus module's keeper, ADR 0675 §1, §2).
**It keeps all its data in the bus's own storage, by the kept-data templates, and requires no database**
(ADR 0595, ADR 0596; the operator, 2026-11-11). Built in milestone bus-and-switch (§1 to §14 of its part), which carries
today's store database over at the manager's first start, reading it and never writing it (done live on
2026-11-12); milestone parts drops the carry-over's code and the database's requirement, which then retires on the
operator's Approve (ADR 0570 §5), its data removed only by the operator (principle 7; its answer A3):
| entry | template | holds |
|---|---|---|
| `licences` | state | one key per licence: kind, the account's identity, expiries, failures, its latest usage reading with its reset times; never a secret |
| `bindings` | state | one key per consumer: its licence, kind, generation, and who or what moved it last |
| `leases` | state | one key per lease, ended by its holder or its two-minute lifetime |
| `offered` | state | one key per grant a node offered, with its outcome |
| `usage` | log | every reading, raw beside normalised (ADR 0054), 35 days |
| `audit` | log | every adoption, refusal, rotation, binding, switch and setting, kept for good |
| `carried` | state | the record of the one carry-over from the store database, kept and never written again (milestone parts §10) |
| `conditions` | state | each condition it said on the operator channel, so a restart can clear what it said |
Every grant and token is the vault's (*Every grant in the vault*, above). Until milestone parts is delivered, the
module still requires its store database, its tables `licence` (with the grant encrypted at rest with a key the vault
made for the manager, ADR 0050's carve-out), `binding`, `lease`, `offered`, `usage` and `audit`, read by the
carry-over's code alone and written by nothing.
## 2. The licences it manages today
@@ -135,12 +169,15 @@ own, with its own agent directory and credentials file, which the agent module o
it as it writes the operator's — the predecessor ran its agents exactly so.
**Binding is a person's act through the seat's verbs**, listed by the mesh MCP server: `bind`, `switch`,
`release`. **Exhaustion is observed, not acted on**: usage is read every few minutes, a crossing of a
declared threshold in the five-hour window is notified once per crossing, and moving a consumer is the
operator's call. Switching remains a reaction, not a declaration
(ADR 0024), and an automated policy — move to the
least-used licence, stay off a dying one — is designed later if wanted, on the readings this module
already keeps.
`release`. **A licence at its threshold is left by the manager itself** (the operator, 2026-11-11: "when the current
licence usage is at 99%, switch to a licence that still has room", "on all nodes"; built in milestone
bus-and-switch, §24 to §33; the operator's answer replaces ADR 0183 §21 and ADR 0206 §16, §18 for this module, with
no record, its answer A2): on each of its own usage readings, a licence whose session or weekly usage reaches the threshold (99 % by
default) has every consumer bound to it moved to the licence with the most room; each switch is said as
`licence.switched` and told to the operator; a licence near its limit is warned about once per crossing, and no
licence with room is a condition until one has room. The threshold, the room a target needs and the switch itself
are settings. Switching stays a reaction to a reading, never a declaration (ADR 0024). Until the milestone is
delivered, exhaustion is observed and moving a consumer is the operator's call.
## 6. Adopting a grant
@@ -180,9 +217,14 @@ report, and adopted by refreshing it.
**Events**, no secret in any: `licence.adopted`, `licence.failing`, `licence.refused`, `usage.read` —
the audit logger records them all. *2026-10-04 (ADR 0206):* `licence.rotated` and `licence.switched` are
gone; a rotation or a switch is a new generation in the `bindings` state.
gone; a rotation or a switch is a new generation in the `bindings` state. *Milestone bus-and-switch:*
`binding.changed` says every change of a binding, `licence.switched` comes back for every switch, `holdings.asked`
asks every node's agent module for its holdings, and its conditions are said on the operator channel (§9, §21, §23,
§27 of its part).
**State**: `bindings`, which it keeps; the agent module's `holdings`, which it reads.
**State**: `bindings`, which it keeps; the agent module's `holdings`, which it reads. *Milestone bus-and-switch:*
the kept data of §1, and no other module's state read: the agent module's holdings are heard as its
`holdings-changed`, and the agent module follows its binding by `binding.changed` (ADR 0596).
**The seat's verbs**, the contract every future holder must serve: `licences` (each with kind,
identity, expiry, failures, who is bound), `bindings`, `bind`, `switch`, `release`, `refresh` (now, one
@@ -191,10 +233,17 @@ or all), `usage` (current and history), `adopt` (a file on the manager's node, o
consumer sends — ADR 0206; from ADR 0550 §3, which licence a consumer holds and its health, never the
token, which the vault hands). The manager asks a node for a candidate grant by the agent module's own tool.
The module's own tools beside the verbs (milestone bus-and-switch, §37 to §44): every switch and why, the settings
in force, usage read now, the switch rule applied now, every entry it keeps with its fill, the conditions it
said, the carry-over's record, and one recovery that says every binding, condition and access token again.
## 8. Settings
The refresh cadence; the usage threshold; the notification cooldown. Each declared with a default, so
one definition serves and one mesh may differ.
one definition serves and one mesh may differ. *Milestone bus-and-switch* (§33 of its part) adds whether the manager
switches by itself, the threshold that moves consumers (99), the room a target needs (both windows below 90), the
warning's threshold (90), the reading's cadence (5 minutes) and when a reading is too old (15 minutes), each set
through mesh-config.
## How it is checked
@@ -208,10 +257,24 @@ one definition serves and one mesh may differ.
| the mesh MCP server lists the seat's verbs and `switch` changes a workstation's token end to end | ADR 0132, the exit of the build |
| the vault refuses a token to a holder no binding names, or to one without a bus account of its own | ADR 0550 §3 |
| after the move, the manager's tables hold no grant | ADR 0550 §3 |
| every row of the store database is carried over once, and a second start carries nothing | bus-and-switch §11 to §13 |
| a reading at the threshold moves every consumer of that licence to the one with the most room, said as an event and to the operator | bus-and-switch §26 to §28 |
| no licence with room raises one condition, cleared by a reading with room; a licence near its limit is warned about once | bus-and-switch §29, §30 |
| every entry the manager keeps is read by a tool, and every act it takes by itself is started by one | bus-and-switch §37 to §45 |
| the manifest names the manager bundle, the tools bundle, the settings and the account, and no process, container or run-once step; the validator accepts it, the builder builds it and the node-engine composes it | parts §1 to §8 |
| with the manager bundle refusing to start, the tools that read still answer | parts §3 |
| the module names no store database and no `grant.key`; both provisions retire on the operator's Approve, and the database goes only by the operator's call | parts §9 to §15 |
## Milestones
| Milestone | What it builds | State |
|---|---|---|
| the manager and its consumer | §1 to §8 as first written: the module, its seat, its store database, adoption and the sealed hand-over (ADR 0183, ADR 0206, ADR 0209) | delivered; built as design [40](40-building-the-operators-agent-and-its-licence-manager.md) describes |
| bus-and-switch | its kept data on the bus and its store database carried over and retired; every grant and token in the vault (ADR 0550 §3); bindings and holdings as events; the switch on its own usage, its warning and its condition; its tools. mesh-catalog's `claude-licence-manager`, `claude-code` and `mesh-vault` in one pull request, on what main holds today | delivered 2026-11-12; its part is [`39-milestones/bus-and-switch.md`](39-milestones/bus-and-switch.md), its four questions answered by the principles (A1 to A4) |
| parts | the module as four parts in one manifest: the manager bundle (running long, the seat's verbs, every act), the tools bundle (every tool that reads), the settings, the account; the carry-over's code and the requirements of the store database and `grant-key` leave, both retiring on the operator's Approve and the database removed by the operator. mesh-catalog's `claude-licence-manager` alone | designed; its part is [`39-milestones/parts.md`](39-milestones/parts.md) |
## What this does not settle
- An automated switch on exhaustion (§5).
- Whether an OAuth refresh token is single-use; the lab measures it, and §3 holds either way.
- How the mesh's own session and a worker read their token on a node once those exist
([to-be 15](15-the-agent-session.md), ADR 0003):
@@ -60,8 +60,8 @@ Measured 2026-10-03 on the four machines and in the repositories.
| a bundle's words | paths and constants written with `${dir:…}` and `${port:…}` only; a fact the mesh knows reaches a bundle as a file whose path is a word | the agent module's facts file and settings file |
| a bundle calling a tool | `mesh/ask` through the runtime that launched it (ADR 0198); no bundle holds a bus credential | how the manager visits every node |
| a module's own long-running code | a bundle the runtime launches and restarts (ADR 0198); the runtime's subscription and grants built, the modules moving in design 38 WP4c's waves | the manager's daemon (WP3, WP4) |
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalogue, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalogue; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalog, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalog; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
| the predecessor's manager and consumer | the lease per licence, the expiry floor, the lineage comparison, the identity guard, three touchpoints, cooldowns | ported as logic with its tests |
## The order the work allows
@@ -99,7 +99,7 @@ resolves it. The runtime's port stays what its code fixes; assigning it is
second question and not this package's.
**Proof.** The plan for a workstation carrying a consumer of `mcp-endpoint` shows it bound to the
runtime's port; the controller's tests and the catalogue's checks pass.
runtime's port; the controller's tests and the catalog's checks pass.
## WP2 — The agent module
@@ -130,7 +130,7 @@ runtime's port; the controller's tests and the catalogue's checks pass.
**Proof, before anything runs live.** Unit tests: the renderer writes the mesh's keys and nothing else;
it writes nothing when nothing changed; the credentials write strips a refresh token and is atomic; the
lineage cases from the predecessor; a sealed hand-over opens only with the module's key; no tool's answer
contains a token. The catalogue's checks pass.
contains a token. The catalog's checks pass.
**Proof, live, on one workstation, configuration only.** Assign the module; set the node's role; push.
The agent's managed directory holds the two files; everything under the person's agent directory is
@@ -188,11 +188,11 @@ credentials file is left as it was and the agent authenticates through the key-h
*The live mesh and mesh-catalog. One day.* The module on every node, the predecessor's files removed on
the second workstation; a login under a licence's account collected and adopted, and one under the wrong
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalogue;
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalog;
designs 36 and 39 set to `implemented` with the as-is written
([playbook 02](../../00-META/process/02-graduation.md)).
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalogue
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalog
has no module built on the old placement.
## The home, personal rules and lessons — WP7 to WP10
@@ -259,7 +259,7 @@ WP10 the rest of the nodes, and the as-is (the live mesh)
neither list, the watched entries and the pending requests.
**Proof, before anything runs live.** The module's tests of design 36 "How it is checked" for ADR 0273 §1,
§2, §3 and §4, each failing on the commit before. The catalogue's checks pass.
§2, §3 and §4, each failing on the commit before. The catalog's checks pass.
### WP8 — Personal rules and the operator's items, live
@@ -2,7 +2,7 @@
layer: to-be
status: in-progress
code: [mesh-host, mesh-controller, mesh-catalog]
updated: 2026-10-16
updated: 2026-11-04
decisions:
- "ADR 0394"
- "ADR 0310"
@@ -146,7 +146,7 @@ against WP2's controller before anything is published.
the node. The rendering is ADR 0203's and ADR 0204's: module order, a naming line per contribution,
`PATH` entries added only when missing. `${machine:…}` in a contributed value is resolved first.
- **Refusals.** A variable set by two modules on one node is refused, naming both. A placeholder in a
module that does not claim the matching seat is refused, both at the catalogue check and at
module that does not claim the matching seat is refused, both at the catalog check and at
composition.
**Proof.** The controller's tests:
@@ -157,7 +157,7 @@ against WP2's controller before anything is published.
- each refusal, by name;
- the seat table carries both seats and refuses a module declaring either.
The catalogue check over the whole catalogue passes.
The catalog check over the whole catalog passes.
## WP3 — The modules
@@ -188,7 +188,7 @@ The catalogue check over the whole catalogue passes.
- **`zsh-autosuggestions` and `zsh-syntax-highlighting`, new.** Each declares its package and
contributes its loader from the distribution's path, in the `normal` and `last` slots.
**Proof.** The controller's catalogue check over the whole catalogue passes. The modules' tests pass.
**Proof.** The controller's catalog check over the whole catalog passes. The modules' tests pass.
A rehearsal composition for a node holding all five shows:
- the `.zshrc` block with the prompt in `normal` and highlighting in `last`;
@@ -227,7 +227,7 @@ The user-scoped units of `mesh-host` #72 stay to-be 38's WP6.
1. Merge WP1 and roll the node-engine.
2. Merge WP2, and push the controller.
3. Merge WP3 and WP4, and build the new modules by hand: a new catalogue module's first build is asked
3. Merge WP3 and WP4, and build the new modules by hand: a new catalog module's first build is asked
for, not automatic.
4. On one server, assign `node-env`, `zsh`, `zsh-autosuggestions` and `zsh-syntax-highlighting`, and
push. Then check:
@@ -262,7 +262,7 @@ group**: the lighting daemon's, the container runtime's, a serial port's. It dec
│
node-engine usermod --append ── recorded on openrazer.account: "the mesh added openrazer"
│
account judge database lists it? ── running manager holds it? ── no: relogin needed
account probe database lists it? ── running manager holds it? ── no: relogin needed
│ (module openrazer, kind account)
controller module.openrazer.<machine>.unhealthy, cleared on the first look after a new login
```
@@ -280,7 +280,7 @@ group**: the lighting daemon's, the container runtime's, a serial port's. It dec
when the resource is undeclared, is given back. It is not given back while another declared resource
asks for it. The account is taken out of that one group (`gpasswd --delete`), read back, and a failure
is said and is not fatal, as giving back a shell is (WP1).
- **The node-engine, looking.** An account judge sits beside liveness and the failed-unit reading
- **The node-engine, looking.** An account probe sits beside liveness and the failed-unit reading
(issue 315). On every look it reads, for each account a module puts in groups, the database's groups
and the groups of the account's running manager, from that process's status file. It states each as the
module's resource of kind `account`: healthy, or unhealthy with *not in the group* or *relogin needed*.
@@ -328,7 +328,7 @@ control-node withholds it until a call to it needs a person's approval. The othe
writes it into, and a word naming that file for its tools bundle. The bundle registers `execute` only
when the file says `serve`. `zsh_config` also answers whether `execute` is served on the node, and why.
**Merge order.** The controller first, then the catalogue. Either order composes. With the catalogue
**Merge order.** The controller first, then the catalog. Either order composes. With the catalog
alone and the control-node's value set, the self-check would say the holder there is silent.
**Proof.** The tests ADR 0268 lists under *how it is checked*. Live, after both are merged and rolled
@@ -382,7 +382,7 @@ move, write or delete a file, and a cleanup needed a shell on the machine. Most
project folders, downloads, logs, scratch.
- **A node seat, `node-files`, on every node**, held by a module of its own, `mesh-files`, beside `mesh-reads`
in the catalogue; the controller holds the seat's definition beside the other node seats. It has a verb for each file action: list,
in the catalog; the controller holds the seat's definition beside the other node seats. It has a verb for each file action: list,
stat, readlink, read, search, write, copy, move, delete, make a folder, change mode, change owner, account and group
lookup, and the trash's list and restore. Each says what it replaces (ADR 0245): `ls`, `stat`, `readlink`, `cat`,
`grep`, `cp`, `mv`, `rm`, `mkdir`, `chmod`, `chown`, `getent passwd`, `getent group`, `id`, and `ssh <node> cp` for a
@@ -0,0 +1,186 @@
---
layer: to-be
status: in-progress
design: 42
milestone: zfs-owns-zfs
updated: 2026-11-13
decisions:
- "ADR 0679"
---
# 42, milestone zfs-owns-zfs — the lead's answers to the prepare's questions
The lead's answers to the prepare's design questions (Q-n) and to the places where a design line and the code
disagree (C-n), for the milestone part [zfs-owns-zfs](zfs-owns-zfs.md): each one line, naming the principle that
settles it (`00-META/principles.md`), cited as "design 42 zfs-owns-zfs A-n". The code read is the prepare's, at the
`origin/main` commits its milestone file names.
- **A-1** (Q-1, §8; principles 2 and 7) The pool's name is not held by mesh-config: it is the zfs module's
`values` in the home server's assignment (seat `mesh-module-assigner`), a layer mesh-config's rename does not move.
So the module keeps `renamed-from: scrub-pool` for the values mesh-config holds, and at the delivery the lead moves
the assignment's value: `pool` set to that name through mesh-config for the home server, and the assignment
proposed without `scrub-pool` through `mesh-module-assigner.propose`, both before the new version is placed.
- **A-2** (Q-2, §14; principle 7) The module's ZED folder also holds the package's `zed-functions.sh`, from the
module's files archive, root's, mode 0644, under `if-config: [zed=on]` beside `zed.rc`; the handlers find it where
ZED points them, and nothing else changes.
- **A-3** (Q-3, §13; principles 2 and 7) `trim-schedule: none` is a set value meaning "no trim by the mesh": the
distribution's trim timers are held stopped for `none`, `weekly` and `monthly` alike; only an unset value leaves
them as found.
- **A-4** (Q-4, §3; principle 3) The validator refuses a `:` in a `config-lines` prefix, beside `}`, `$` and a
newline, naming the key.
- **A-5** (Q-5, §3; principle 3) The validator refuses `mib-bytes` on a value whose type is not a number, and a
`config-lines` key the module does not declare, as it refuses `${config:<key>}` of an undeclared key today.
- **A-6** (Q-6, §18, §24; principles 1 and 6) The follower's process bus declares
`invokes: ["seat:node-service-manager.start"]`, and the module's `invokes` carries the same for its tools bundle.
- **A-7** (Q-7, §14, §18; principle 7) `/etc/zfs/mesh-zed.d` is a directory resource under `if-config: [zed=on]`
with `unknown: module`; `/run/zfs-tools` is a directory resource with `unknown: program`, as `/run/zfs-events` is.
- **A-8** (Q-8, §18; principle 3) `zfs-tools apply` exits 0 on a refusal and on a wait, which it says by event and
in its state `applied` and which make the module unhealthy by §29; it exits non-zero only when a command it ran
failed, so the node-engine gates the module on a real failure.
- **A-9** (Q-9, §30; principle 7) When the module leaves a node, its kept data (the states `applied`, `schedule` and
`findings`, the ledger `snapshots`, the events stream) is kept as every module's kept data is on leaving; a return
finds its scheduled ZFS snapshots' records.
- **A-10** (Q-10, §27; principle 7; corrected by A-32) The findings stream is the one design 47 G §383 defines for every observer:
`zfs-findings`, its events `finding-written.*`, `finding-cleared.*` and `findings-stated.*`, kept 7 days and
16 MiB, said by the writer generated from the seat `mesh-conditions`' definition; the module declares it as §383
says and builds nothing of its own for it.
- **A-11** (Q-11, acceptance test 8; principles 5 and 7) Test 8 runs in the test lab, on a pool built on a
file-backed device, never on the home server's pool.
- **A-12** (Q-12, §24; principles 3 and 4) A hand act's tool waits at most 30 seconds for the act's event; past it,
it answers "started", naming the act and that `zfs_module_events` reads its outcome, and never answers "done"
for what it did not see end.
- **A-13** (C-1, §4; principle 7) The node-engine states the new reading as `word:resources.if-config` and
`form:config-lines` (and `form:config-json`, A-15), computed by `ManifestReads` and marked by `ManifestNeeds`;
stopping at the first failure is stated by no capability, since the node-engine build that reads those words is
the build that stops.
- **A-14** (C-2, §11; ADR 0679 §3; principle 7) The validator accepts a config value with neither a default nor
`required` as "not managed until set"; built in the validator's subtasks of this milestone.
- **A-15** (C-3, §9; principles 2 and 7) The config file's content is one new node-engine placeholder
`${config-json}`: the module's set config values as one JSON object, an unset value absent, lists and objects as
JSON; no unset value refuses the module. It is built with §2 and §3 in the node-engine, the validator and
mesh-sdk's form list, and stated as `form:config-json`; no existing form writes an unset value as absent.
- **A-16** (C-4, §18; principle 7) The follower, which runs from boot, starts the apply unit once at its own start
and on the events §27 names; the apply's own wait for an unimported pool (§18) stands for the order after
`zfs-import.target`. No new process field.
- **A-17** (C-5, §24; principle 7) A hand act writes its act into the state `schedule` under its own key and starts
`zfs-mesh-run.service`; the run reads the key, acts under the same lock and guards, and deletes it. No instance
units.
- **A-18** (C-6, §24; principle 7) `node-service-manager.start` is called with `{scope, unit}`; the tool keeps
`why` in its own event. The seat is not changed.
- **A-19** (C-7, §26; principle 7) The ledger `snapshots` declares a fixed `max-mib` sized from the bound of 168 per
kind (§11); a full ledger takes §21's "ledger full" path.
- **A-20** (C-8, §21, §22, §26; principles 1 and 7) A process cannot write a ledger: the ledger is served to a tools
bundle alone, through the tool runner (mesh-sdk `buscalls`, `ProcessBus` has no ledger). The scheduled run records
`taken` and `removed` by calling its own module's tool `zfs_snapshots_record` (declared in the run's process bus
`invokes`), whose bundle applies the operation to the ledger; the tool refuses a snapshot not named `mesh-`. No new
field in mesh-sdk.
- **A-21** (C-9, §13; principle 2) When one record of a unit goes while another still declares it, the found state
passes to the record that stays, as a file is handed to the resource still declaring its path (ADR 0223); built in
the node-engine's §2 subtask.
- **A-22** (C-10, the test budget; the test-budget instruction) The node-engine's repository is over its budget (about 1,742 unit tests
against 500): each new table test merges or replaces at least as many tests in the file it touches, named in the
build map, so the count does not rise.
- **A-23** (C-11, §9, §11; principle 6) The config file carries `"trusted": false`; each value's own `trusted` (§11)
decides who sets it.
- **A-24** (C-12, §2, §3; principle 3) `config-lines` and `config-json` are added to the validator's and the
composer's placeholder tables, and the validator counts `if-config`, `config-lines` and `config-json` as placing
their keys.
- **A-25** (A-15, S2; principle 3) `${config-json}` holds no colon, so the form search that finds `${<form>:` does not
find it: the manifest's reading also finds the exact token `${config-json}`, and a manifest that holds it needs
`form:config-json`, so no node-engine that cannot write it is given the module.
- **A-26** (§1, met in develop; principles 3 and 7) §1 stands: a module declares a resource another of its resources
needs before that one, since the apply now stops at the first failure. The pacman module declares its package
before its config file, and its package fails while the config file is missing, so the stop would keep a node
from restoring its package manager: in this milestone the pacman module's config file moves before its package,
and the node-engine's probe test of that restore keeps the pacman module's own resource names, in that order.
- **A-27** (§11, met in develop; principle 7) `snapshots` is an object, so its `none` is the object holding only the
key `none` set to true; keep counts are its other form, and the text `none` is refused as the type refuses it.
- **A-28** (§11, met in develop; principle 3) One refused value refuses the whole config: nothing is converged until
every value is valid, and each refused value is its own `config.refused`.
- **A-29** (§11, §18, met in develop; principle 3) The node's memory is read by the apply and given to the parse; a
memory that cannot be read refuses `arc-max` and `arc-min`, never guesses them.
- **A-30** (§21, §22, §26, A-19, A-20, met in develop; principles 1 and 7) The ledger `snapshots` uses the ledger's
own forms (ADR 0597) and nothing new: one type `snapshot`, its fields `dataset`, `name`, `kind` and `at`, all text;
an instance `snapshot/<kind>-<UTC stamp>-<dataset>`, the dataset's `/` written `~` (a character no ZFS name holds);
the operation `taken` with the effect `set-fields` on those four fields, and `removed` with the effect `remove`
naming no map, so the instance's state goes while its history keeps both operations; one index `by-dataset-kind` on
`dataset` and `kind`, read through `mesh/ledger.query`, the newest `at` among a kind's entries being its last.
- **A-31** (§21, §22, §28, A-20, met in develop; principles 1 and 7) The scheduled run reads the ledger as it writes
it, through its own module's tools: its process bus also invokes `zfs.zfs_snapshots_schedule`, whose answer holds
the ledger's entries (§28), and the run takes a kind's last entry and the entries to prune from that answer.
- **A-32** (corrects A-10, §27, met in develop; principles 2 and 7) The writer A-10 names is not on mesh-sdk's main: it
is built only on design 47 G's branch, a milestone not yet delivered. The follower says its findings in its own
state `findings`, keyed and written as every observer on main writes its findings today, and declares no
`zfs-findings` stream; G's change of §387 writes that stream into every module that declares a state `findings` on
main when it lands, the zfs module among them. Read before A-10 was given: nothing; the lead now reads what an
answer names first (playbook 07, step 0b).
- **A-33** (§20, met in develop; principle 3) A trim schedule set without its `trim-day` or `trim-time` runs no trim and
is said: the parse refuses it as one refused value (A-28), naming the missing value.
- **A-34** (§24, A-18, met in develop; principle 7) A hand act's `why` and `by` are kept in its key of the state
`schedule`, which the run deletes once it acted, and its outcome is the event the act itself emits (§25); no new
event kind.
- **A-35** (§21, §22, met in develop; principle 3 and the operator's "we may absolutely not lose any data") A kind's
keep count of 0 is that kind's `none`: none is taken, its existing scheduled ZFS snapshots are kept and a warning
names them; only a count above 0 prunes past it.
- **A-36** (corrects A-30, §26, met in develop; ADR 0597; principle 7) The ledger's `remove` effect names a map and a
key (the validator refuses one without), so the instance is one per dataset and kind:
`snapshot/<kind>-<dataset>` (the dataset's `/` written `~`), its state a map `taken` keyed by the ZFS snapshot's
name; `taken` is the effect `set-entry` into `taken` under the field `name`, with `dataset`, `name`, `kind` and `at`;
`removed` is the effect `remove` from `taken` under `name`. A kind's live ZFS snapshots are the instance's map, its
last the newest `at` there, so no index is declared.
- **A-37** (§18, §20, §26, §27, A-6, A-20, met in develop; ADR 0436 §1, §2; principle 7) A process's bus account is a
user the module makes, never root, so the three root processes (the apply, the run, the follower) declare no `bus`.
The module makes a user `zfs-keeper` and one process `zfs-keeper` running as it, which holds the module's one process
bus account (its events, its states `applied`, `schedule` and `findings`, and its invokes of
`zfs.zfs_snapshots_record`, `zfs.zfs_snapshots_schedule` and `seat:node-service-manager.start`). It serves a unix
socket in the module's state directory that only root may open (the peer's uid checked), with a closed list of
calls: emit an event, put or delete a key of those states, and invoke one of those three. The root processes say
everything they say on the bus through it; with the keeper not answering they act as with the bus down (A-8), and
warn. The module's tools keep their own account as today.
- **A-38** (A-37, §25, §26, met in develop; ADR 0436 §1, ADR 0595 §1, §2; the operator's "a case the delivery cannot
handle is a gap in the build and delivery system") A process's `bus` today is judged and granted only against the
module-level `emits` and `state`, which ADR 0595 replaced by what a module asks for in `requires`. In this
milestone a process's `bus.emits` may name an event of the events stream its module asks for by template in
`requires`, and its `bus.state` a state its module asks for there: the validator accepts them (in the indexer's
process-bus rule), and the grants (mesh-grants) give the process's account publish on that stream's subjects of
its own node and the reads and writes of those states, as they give a module's tools account today. A name its
module does not ask for is refused as today.
- **A-39** (A-37, A-17, met in develop; principle 7) The keeper's closed list of calls also reads: `get` one key and
`keys` of the same three states, since the run reads a hand act's key and the apply its state `applied`.
- **A-40** (A-36, met in develop; principle 3) A ledger instance not yet written is the tool runner's answer to
`mesh/ledger.get` with a state `null` and sequence 0 (the tool runner's `internal/ledger/read.go` `Get`, in mesh-tools), never
an error: the module reads it so, and recognises no error by its text.
- **A-41** (§25, A-37, A-38, review round 1; principles 1 and 7; shared code belongs in mesh-sdk) The events stream a
module asks for by template `events` captures `mesh.bus.<module>.<local>.>` (the bus module's keeper, `PlanOf`), and
that is what the grants of A-38 give. The keeper publishes each event there, as a JetStream publish the bus
acknowledges, on `mesh.bus.<module>.<local>.<event>`, the subject named by a new mesh-sdk helper
`process.Account.BusSubject(local, event)` beside `EventSubject` and `StreamSubject`; never `process.Conn.Emit`,
which publishes on the module's old event subjects.
- **A-42** (§25, A-12, A-41, review round 2; principles 1, 3 and 7; the operator's "a case the delivery cannot handle
is a gap in the build and delivery system") A tools bundle has no way onto the events stream its module asks for by
template: the tool runner's `mesh/publish` publishes on the module's old event subjects and reads no binding (the
tool runner's `PublishAs`, `eventSubject`), and its `mesh/subscribe` hears only the old streams. This milestone
builds the one path it lacks, by name, beside the bus-state calls a bundle already makes: a call
`mesh/stream.publish` {stream (the local name), event, body, id} in mesh-sdk's `buscalls`, which the tool runner
answers for the bundle's own bound stream alone by an acknowledged JetStream publish on the binding's subject plus
`.<event>`, the id as the message id, under the grant the tool runner's account already holds (`boundOf`). The zfs
tools say every event of §25 with it, never `stdio.Emit`. A hand act's tool hears its act's event by reading its own
events stream with the calls it has (`mesh/stream.fill` for the last sequence before it starts the act, then
`mesh/stream.read` from the next one with the act's subject), for at most the 30 seconds of A-12. The other modules
whose bundles emit onto a template events stream (messenger) move to the call in a change of their own.
- **A-43** (corrects A-12 and A-42's wait, met in develop; principles 1 and 7: nothing polls) A hand act's tool does not
wait: once its act's key is written and the run's unit started, it answers "started" at once, naming the act and that
`zfs_module_events` reads its outcome, as §24 says ("the tool answers from the state and events afterwards"). The
bundle has no watch on its events stream, and a repeated read for 30 seconds is a poll.
- **A-44** (A-42, met in develop; principle 3; the module graph orders the delivery) `mesh/stream.publish` is a family
of its own, the capability `stream-publish`, which the new tool runner states; the zfs module's `capabilities` ask
for it (and for `bus-state-calls` and `ledger-calls`, the calls it already makes), so the delivery plan places the
zfs module's new version only on a machine whose tool runner answers the call, and never one where its events would
fail.
- **A-45** (§13, review round 3; ADR 0240; principle 3) Each long-running unit the module declares (the services
`import-cache`, `mount`, `target` and `zed-on`) declares its health of kind `unit`, as the catalog's other
long-running resources do, so no running part of the module goes unwatched.
- **A-46** (A-32, §26, §27, the group's check; ADR 0596 §2; principle 7) The state `findings` is declared as every
observer on main declares it, in the module's `state` list, not by template in `requires`: the conditions keeper
reads an observer's findings from that form only, and a state asked for in `requires` has no readers. The zfs
module's other states (`applied`, `schedule`), its ledger and its events stream stay in `requires`; the keeper
process's `bus.state` names `findings` as a state of the `state` list. Design 47 G §387 moves it when G lands.
@@ -0,0 +1,337 @@
---
layer: to-be
design: 42
milestone: zfs-owns-zfs
updated: 2026-11-12
code: [mesh-sdk, mesh-delivery, mesh-host internal/apply, mesh-catalog modules/zfs]
decisions:
- "ADR 0679"
- "ADR 0051"
- "ADR 0117"
- "ADR 0269"
- "ADR 0303"
- "ADR 0348"
- "ADR 0508"
- "ADR 0564"
- "ADR 0593"
- "ADR 0595"
- "ADR 0609"
- "ADR 0614"
---
# Design 42, milestone zfs-owns-zfs — the zfs module owns a node's ZFS, and changes nothing that is not set
**What it builds.** On a node that holds a ZFS pool (today the home server), the zfs module owns ZFS's packages, its
kernel module's options file, its host id, its own folder for ZFS's event daemon (ZED), its units and, for one pool,
its cache and log devices, its properties, its datasets' properties and a schedule of ZFS snapshots; each is changed
only once its config value is set and not empty (ADR 0679). The purpose and the whole shape are the main file's,
[*The zfs module*](../42-the-machines-modules-in-order.md#the-zfs-module). This part holds every line the milestone
builds, cited as "design 42 zfs-owns-zfs §n".
**Where.** Built under issue 1979. Four repositories, one delivery group with one check: mesh-sdk, mesh-delivery
(the manifest validator) and `mesh-host` (the node-engine) for §1 to §5, issue 1979's three node-engine subtasks
and the readers of their words; mesh-catalog's `modules/zfs` for §6 to §31. **Ordering edges** (ADR 0679 §16): the
readers in §4's order, then the zfs module's new version only on a node whose node-engine states the three
capabilities of §4 (a reader's capability the delivery plan derives, ADR 0614 §4).
## The prerequisites (ADR 0679 §16)
1. **The node-engine's apply of a module stops at its first failed resource** (ADR 0679 §16 (a)). When one resource
of a module fails, the node-engine applies none of that module's later resources in the declared order; its apply
report names the failed resource with its error and each one left unapplied, and the module's phase is `failed`.
Resources already applied stay as applied; other modules on the node go on as today. Today the apply goes on, and
stopped the distribution's scrub timer after the module's own timer failed. Checked by a unit test (a module of
three resources whose second fails leaves the third untouched and reports it as left) and by §5.
2. **A resource is declared only while a config value holds** (ADR 0679 §16 (b), §3). A resource of any type may
carry the manifest field `if-config`: a list of conditions, each `<key>` (the module's config value is set and is
neither `false`, `off`, `none` nor an empty list) or `<key>=<value>` (the value equals it; for a list value, the
list holds that entry). The resource is declared while any condition holds, and not at all while none does. A
resource that leaves the declaration this way is removed as any resource that leaves it: a file the node-engine
wrote whole is deleted, a unit gets its found state back (ADR 0269 §2). The manifest validator refuses an
`if-config` key the module does not declare under `config`. Checked by the node-engine's table test (a value unset,
empty, `off`, set; a list with and without the entry; a value going from set to unset removes the resource) and
the validator's own test.
3. **A template writes one line per entry** (ADR 0679 §16 (c), §4). A template file may hold
`${config-lines:<key>:<prefix>}`: for a list value one line per entry, `<prefix><entry>`, in the list's order;
for a set scalar value one line `<prefix><value>`; nothing for an unset value or an empty list. With a fourth part,
`${config-lines:<key>:<prefix>:mib-bytes}`, a number given in MiB is written in bytes. The prefix is literal text
with no `}`, `$` or newline, refused by the manifest validator; an entry holding a newline is refused before the
file is written, and the file is left as it was. Checked by the node-engine's table test (a list of two, an empty
list, an unset and a set scalar, `mib-bytes`, a newline in an entry) and the validator's own test.
4. **Every reader of the new words, in order** (design-it-as-it-should-be; ADR 0614 §4). mesh-sdk first: the
manifest types gain `if-config` on every resource and the template form `config-lines`, and the capability names
`if-config`, `config-lines` and `apply-stops-at-failure`. Then the manifest validator in mesh-delivery's indexer:
it reads both words and refuses them as §2 and §3 say. Then the node-engine: it builds §1 to §3 and states the
three capabilities. A manifest using `if-config` or `config-lines` reaches a node only through a node-engine that
states both; the delivery plan derives the edge from the stated capability, never from a fingerprint. Checked by
each repository's unit tests and by the delivery plan naming the edge.
5. **One integration test of the node-engine** (how-work-is-done: a change to how modules run carries one). It
starts the node-engine whole against the real bus with a module of a resource under `if-config`, a template with
`config-lines`, and a resource that fails: the conditioned resource appears when its value is set and goes when it
is unset, the template's lines follow the list, and the failing resource stops the module's apply with the later
resource left. It is the only integration test this milestone adds.
6. **The order on the node** (ADR 0679 §16, §9). The zfs module declares its scheduled run's resource (§20) before
the resources that stop the distribution's scrub timers, so by §1 those are stopped only once the run's timer is
active. The run's `schedule` is the list form `0,15,30,45 * * * *`, which today's node-engine writes as
`OnCalendar=*-*-* *:0,15,30,45:00`, so nothing waits on another fix. Checked by the module's manifest test (the
resource order and the schedule string) and by acceptance test 3 of the main file.
## What the module owns
7. **The packages** (ADR 0679 §1; ADR 0303 §2). The manifest declares `zfs-utils` and `zfs-dkms` present: the
node-engine installs a missing one, upgrades none, removes none. `zfs-utils-debug` is not declared and is left as
found. Fate: the README's "It installs nothing" goes; it says the two packages and why the debug package is not the
module's. Read by `pacman_packages` as declared by `zfs`. Checked by the manifest test.
8. **One pool, renamed `pool`** (ADR 0679 §1; ADR 0609 §4). The config value `scrub-pool` becomes `pool` with
`renamed-from: scrub-pool`: type string, trusted, required. mesh-config moves the value under the new name and
answers both names during the rename window (design 32, *A config value renamed*); every template and the module's
code read `pool`. **Rollback:** a rolled-back version leaves pool and dataset properties and devices as the last
apply set them (ADR 0679 §15); once the rename window has closed, going back to the version with `scrub-pool` and
`zfs-mesh-scrub` is a new delivery of that version, with its own rename back, never a rollback. Checked by the
manifest test (no `scrub-pool` but in `renamed-from`) and `zfs_config` showing `pool` on the home server.
9. **The module's config file** (ADR 0679 §9; ADR 0348 §5). `/etc/zfs-tools/config.json`, root's, mode 0644, written
by the node-engine from a template holding every config value of the module, lists and objects rendered as ADR 0348
§5 says; it replaces `/etc/zfs-tools/scrub.conf`, which the node-engine removes as a resource that left. The apply
unit (§18) has `restart-on` this file: its change is the path by which mesh-config's `config-changed` for the
module's values reaches the apply. The scheduled run (§20) reads it at each run. `/var/lib/zfs-scrub` stays and is
read by the run as today. Checked by the manifest test.
10. **What it never writes** (ADR 0679 §1, §2). The package's own `/etc/default/zfs` and `/etc/zfs/zed.d`, with its
`zed.rc` and its links, are left as the package ships them, and nothing of them is given back on leaving; ZFS's own
`/etc/zfs/zpool.cache` and `/etc/zfs/zfs-list.cache` are read only. `zfs_config` shows each, and a `.pacnew`
beside `/etc/default/zfs` or the package's `zed.rc` is a warning (§29). A dataset's `sharenfs` is the seat
`node-nfs-server`'s, `sharesmb` the SMB module's (issue 613): neither is on a list of §16, a value naming either
is refused (§11), and `zfs_config` never reports either as differing. The pool's files are the operator's (ADR
0051). Checked by the call-site test (§19) and §31.
## The config values and their forms
11. **Not managed until set and not empty; each of a fixed form** (ADR 0679 §3, §4). No new value has a default in
the manifest. An absent value and an empty list both mean not managed: the module writes nothing, converges
nothing and reports no difference for it, so nothing is ever removed by a blank value. A value of another form is
refused by the module's own parse before any file is written or command run, emitting `config.refused` (§25).
Trusted values (every list and object below, `log-mirror` and `hostid`) are set only on the operator's Approve (ADR
0348 §4, §8). The module never reads a value from the node into config.
- `arc-max`, `arc-min` (number): whole MiB, `arc-min` ≤ `arc-max`, `arc-max` at least 64 and below the node's
memory read from `/proc/meminfo`. ZFS's read cache in memory (the ARC) is bounded by them.
- `module-options` (list, trusted): entries `name=digits`, the name from the module's fixed list `l2arc_write_max`,
`l2arc_write_boost`, `l2arc_headroom`, `l2arc_noprefetch`, `l2arc_rebuild_enabled`, `zfs_txg_timeout`,
`zfs_dirty_data_max`, `zfs_scan_vdev_limit`, `zfs_scrub_min_time_ms`, `zfs_resilver_min_time_ms`,
`zfs_arc_sys_free`; `zfs_arc_max` and `zfs_arc_min` are refused here, as `arc-max` and `arc-min` hold them. Reason:
the ARC, L2ARC, transaction group and scrub parameters ADR 0679 §4 names, and nothing else.
- `hostid` (string, trusted): eight hexadecimal digits, not all zero (§15).
- `zed` (string): `on` or `off`. `zed-handlers` (list, trusted): from `all-syslog` (the journal handler) and
`resilver_finish-start-scrub` (scrub after resilver), the two ADR 0679 §12 names; no notify handler.
- `trim-schedule` (string): `none`, `weekly` or `monthly`; `trim-day` and `trim-time` with `scrub-day`'s and
`scrub-time`'s forms.
- `cache-devices`, `log-devices` (lists, trusted): names under `/dev/disk/by-id`, letters, digits, `-`, `_`, `.`
and `:` only. `log-mirror` (boolean, trusted): when true, `log-devices` holds exactly two names, added as one
mirror; otherwise each is a log device of its own. Reason: the log's form today is a mirror.
- `pool-properties` (object, trusted): `autotrim`, `autoexpand`, `autoreplace` (`on`, `off`, `default`) and
`failmode` (`wait`, `continue`, `panic`, `default`).
- `dataset-properties` (object, trusted): a dataset of the pool (the pool's name or one beginning `<pool>/`) to
properties of §16's list with their forms.
- `snapshots` (object, trusted): `none`, or keep counts `hourly`, `daily`, `weekly` and `monthly`, each a whole
number from 0 to 168, a kind with 0 not taken; for every filesystem of the pool. The bound 168 (a week of hours)
is the design's, so the ledger's cap (§26) stays bounded.
Checked by §31's refused-forms tests, one table per value, the empty list among them.
## Files, units and the event daemon
12. **The options file** (ADR 0679 §1, §4). `/etc/modprobe.d/zfs.conf`, root's, 0644, carries `if-config: [arc-max,
arc-min, module-options]` (§2) and holds a header comment, then by §3 `options zfs zfs_arc_max=` and `options zfs
zfs_arc_min=` with the values in bytes (`mib-bytes`), then `options zfs ` with each `module-options` entry. With
none of the three set it is not placed. Checked by the manifest test and §31's template test.
13. **The units** (ADR 0679 §1, §15; ADR 0117). Declared running and enabled at boot: `zfs-import-cache.service`,
`zfs-mount.service` and `zfs.target`, as they run on the home server today, so declaring them changes nothing.
Declared with no state, owned and left as found: `zfs-import-scan.service`, `zfs-share.service`,
`zfs-volume-wait.service` and `zfs-import.target` (reason: ADR 0679 §15 keeps only the first three running when the
module leaves, so no other ZFS unit is one the mesh would stop then). `zfs-zed.service`: running and enabled with
`if-config: [zed=on]`, stopped and disabled with `if-config: [zed=off]`, left as found while `zed` is not managed.
The distribution's `zfs-trim-weekly@<pool>.timer` and `zfs-trim-monthly@<pool>.timer` are stopped and disabled
with `if-config: [trim-schedule]` (the run trims, §20); its scrub timers are stopped and disabled after the run's
timer (§6). Read by `<node>/node-service-manager.status`. Checked by the manifest test (each unit, its state and
its condition).
14. **ZED runs from the module's own folder, without links** (ADR 0679 §13, §12). The folder `/etc/zfs/mesh-zed.d`
(root's, 0755), its `zed.rc` and a drop-in for `zfs-zed.service` whose `ExecStart` adds `-d /etc/zfs/mesh-zed.d`
carry `if-config: [zed=on]`. The module's `zed.rc` holds the package's variables but every `ZED_NOTIFY_*` and mail
line, and `ZED_SCRUB_AFTER_RESILVER=1`, which only the scrub-after-resilver handler reads. Each handler is a file
of its own in the folder, a copy of the package's script by its content kept in the module's files archive, mode
0755, with `if-config: [zed-handlers=<its name>]`, so only the handlers set are placed. The package's
`/etc/zfs/zed.d` is not read while the drop-in holds. The mesh creates no symlinks. The node-engine restarts
`zfs-zed.service` on a change of the drop-in, a handler or `zed.rc` (`restart-on`). Checked by the manifest test (no
link resource, each handler's condition).
15. **The node's ZFS id** (ADR 0679 §1, §3, §4, §17). `/etc/hostid` holds four bytes, binary, which no template writes,
so it is no resource of the manifest and the apply (§18) writes it with ZFS's own writer, `zgenhostid -f <value>`,
only while `hostid` is set, and only when the pool's label (`zdb -C <pool>`, its `hostid`) is 0 or equals the value:
then the next boot imports without `zpool import -f`. When the label shows another value, or cannot be read, it
writes nothing and emits `config.refused` naming why ("the pool's import would need -f", "the pool's label cannot be
read"). Before the Approve, the value's description and `zfs_config` say the change needs a check of the pool's
import at the next boot; the module never reboots. After the reboot, `zfs_pools` shows the pool imported. Emits
`config.applied` with target `file`. The apply reads `/etc/hostid` back at every start and after every write: while
`hostid` is set, a value there other than the set one emits `config.refused` naming it, because the node-engine's
composer cannot see a second writer of a file that is no resource. Each refusal of this line makes `zfs_health`
unhealthy (§29), naming the key and why, until an apply finds the label and the file agreeing with `hostid`, or
`hostid` is unset. No resource or template of the manifest writes `/etc/hostid`. Checked by §31 (unset: no
`zgenhostid`; a label of another id, or one that cannot be read: refused; 0 or equal: one run; a file holding
another value: refused; unhealthy, then healthy again; no manifest resource for the file).
## The pool
16. **Pool and dataset properties** (ADR 0679 §6). For each set pool property the apply runs `zpool set
<property>=<value> <pool>`, and for `default` `zpool set <property>=<ZFS's default> <pool>`. For each set dataset
property it runs `zfs set <property>=<value> <dataset>`, and for `default` `zfs inherit <property> <dataset>`, so
its source stays default. The dataset list is `compression` (`off`, `lz4`, `zstd`, `zstd-1` to `zstd-19`,
`gzip-1` to `gzip-9`, `zle`), `atime` and `relatime` (`on`, `off`), `recordsize` (a power of two from `512` to
`16M`, with `K` or `M`), `primarycache` and `secondarycache` (`all`, `metadata`, `none`), `sync` (`standard`,
`always`, `disabled`), `logbias` (`latency`, `throughput`), `quota` and `reservation` (`none` or digits with `K`,
`M`, `G` or `T`). A property already at its value runs nothing. Each change is read back with `zpool get -Hp` or
`zfs get -Hp` and emitted (§25). Checked by §31's convergence tests.
17. **Cache and log devices** (ADR 0679 §5). From `zpool status -P -L <pool>` the apply knows each device's kernel
path, and from the link under `/dev/disk/by-id` each declared name's. Then, in this order:
- an entry of the pool whose kernel path is a declared device's, but named by its kernel name, is removed
(`zpool remove <pool> <entry>`) and added again by its stable name;
- a declared device not in the pool is added with `zpool add <pool> cache|log [mirror] <names>`, never `-f`, and
only after its checks: a device with a partition table or a file-system signature (`wipefs --no-act <device>`)
is refused, but for a `zfs_member` signature, which is judged by its label (`zdb -l <device>`): this pool's
label is allowed, another pool's refused; a device that is a member of any imported pool is refused;
- only while `cache-devices` is set and not empty, a cache entry it does not name is removed;
- only while `log-devices` is set and not empty, a log device it does not name, one to rename, or the log when
`log-mirror` changes (a removal and a re-add, said as such) is removed only while the pool is ONLINE with no
read, write or checksum errors and no scrub or resilver runs; otherwise it is a wait, `config.waiting` {key,
waits-on, since}.
A declared device that is not present is a wait. Each change is read back from `zpool status -P` and emitted.
Checked by §31's convergence tests (the empty list among them) and acceptance test 2.
18. **The apply: who, when, and what wakes a wait** (ADR 0679 §9, §17). A `process` resource `zfs-config-apply`, `run:
./zfs-tools apply`, as root, oneshot, ordered after `zfs-import.target`, enabled at boot, with `restart-on` the
config file (§9); it converges §15 to §17 and writes the ARC values live. It holds an exclusive lock on
`/run/zfs-tools/apply.lock`; a second start waits for it. It converges nothing while a scrub or resilver runs on
the pool or before the pool is imported: it emits `config.waiting` with what it waits on and since when, and ends.
It never runs on a timer. **A wait is ended by an event:** when the event follower (§27) hears a scrub or a
resilver finish (ZFS's `scrub_finish` and `resilver_finish` events) or a declared device appear (a udev `add` of a
block device whose link under `/dev/disk/by-id` is declared, read with `udevadm monitor --udev
--subsystem-match=block`), it starts the apply unit through `<node>/node-service-manager.start`. While the bus or
the control-node is down, the apply converges from the config file it last got. It writes `arc-max` and `arc-min`
to `/sys/module/zfs/parameters/zfs_arc_max` and `zfs_arc_min` as well as to §12's file, and reads both back; each
`module-options` entry whose live value differs is a warning naming it, "takes effect at the next load of the
kernel module", never success and never unhealthy.
19. **The call-site test** (ADR 0679 §7, §17). Every command the bundle runs is built by one of its argument functions, and
the test (`TestTheBundleRunsOnlyReadingVerbsSnapshotAndScrub`, renamed `TestTheBundleRunsOnlyTheFormsOfADR0679`)
allows only: `zpool add <pool> cache|log`, `zpool remove` of a cache or log device, `zpool set`, `zfs set` and `zfs
inherit` of a listed property, `zfs snapshot`, `zfs set mesh:schedule=<kind>` on a ZFS snapshot named `mesh-`,
`zfs destroy -- <dataset>@<snapshot>`, `zpool scrub`, `zpool scrub -p` and `zpool scrub -s` of the pool, `zpool trim`
of the pool; and the reads `zpool status`, `zpool list`, `zpool get`, `zpool iostat`, `zpool events`, `zfs list`,
`zfs get` and `zfs holds`. It refuses every other `zpool` or `zfs` form and every `-f`, `-r`, `-R` and `-d`, so
data vdevs, spares, special and dedup devices, `upgrade`, a dataset's destroy, `rollback`, `rename` and `clear` are
never run (each an act of its own, asked of the operator). The same test pins the module's other root forms:
`wipefs --no-act <device>`, `zdb -l <device>`, `zdb -C <pool>`, `zgenhostid -f <value>`, `udevadm monitor --udev
--subsystem-match=block` and `systemctl show`, and refuses every other `zgenhostid` or `zdb` form: `zgenhostid`
only as `-f` with the value §11 validated, `zdb` only as these two reads. Fate: the README's list of allowed forms
is replaced by this one.
## The scheduled run and ZFS snapshots
20. **The scheduled run** (ADR 0679 §9). The `process` resource `zfs-mesh-scrub` (`./zfs-tools scrub-run`) becomes
`zfs-mesh-run` (`./zfs-tools run`), root as today, with §6's schedule. Each run takes the apply's lock; when the lock
is held it does nothing and emits `run.skipped` {reason: `lock-held`}. It does nothing while a scrub or resilver
runs or before the pool is imported, emitting `run.skipped` with that reason. Otherwise, in order: it starts the
scrub when due and returns at once, never waiting on it (today's wait with `zpool wait -t scrub` goes; the scrub's
end is heard by the event follower, §27); starts a trim when `trim-schedule` is due (`zpool trim <pool>`, by
`trim-day` and `trim-time` in the node's time zone, as the scrub reads its own); and takes and removes the ZFS
snapshots (§21, §22) when `snapshots` is set. Checked by §31 (lock held: nothing run; scrub due: started and the
run ends).
21. **Taking scheduled ZFS snapshots** (ADR 0679 §8). Each kind whose keep count is above 0 is due at the first run of
the hour (hourly), the first run after midnight (daily), the first run on Monday (weekly) and the first run on the
first of the month (monthly), each read in the node's time zone against the ledger's last entry of that kind (§26).
For every filesystem of the pool it runs `zfs snapshot -- <dataset>@mesh-<kind>-<YYYYMMDDTHHMMZ>`, the time in UTC,
never recursive, then `zfs set mesh:schedule=<kind>` on it, and records the ledger's `taken` operation. The run
takes no ZFS snapshot it cannot record in the same run: with the bus down or the ledger full it takes none and
emits `snapshot.skipped.schedule` {pool, capacity, reason}. No ZFS snapshot is taken while the pool is more than 80
% full (`zpool list -Hp -o capacity`): a warning says so, and `snapshot.skipped.schedule` is emitted with reason
`full`.
22. **Removing them, only by their own record** (ADR 0679 §8; the operator, 2026-11-12: "we may absolutely not lose any
data"). After taking, the run reads the ledger in the same run (no read, no removal) and, per dataset and kind,
removes the oldest past its keep count, all of them in that run. Before any destroy it checks the name against
`<a dataset of the pool>@mesh-<kind>-<YYYYMMDDTHHMMZ>`, that it carries `mesh:schedule=<kind>`, is in the ledger,
has no hold (`zfs holds`) and no clone (`clones` empty); anything else is refused, emitted and left. Then `zfs
destroy -- <dataset>@<snapshot>`, the ledger's `removed` operation, and `snapshot.removed.schedule` {snapshot, kind,
space freed in bytes}. When `snapshots` goes back to `none`, the existing scheduled ZFS snapshots are kept, and a
warning names them; that change removes nothing. A ZFS snapshot taken by hand is never removed.
23. **A person's ZFS snapshot stays apart** (ADR 0679 §8, §10). `zfs_snapshot` refuses a name beginning `mesh-` and
emits `snapshot.taken.person`; the run emits `snapshot.taken.schedule`. A waiter on a person's ZFS snapshot waits
on `snapshot.taken.person`, which no scheduled one is said on. Fate: the README's "No tool of the mesh destroys a
snapshot" becomes §22's rule.
24. **Hand acts run as the run does** (ADR 0679 §14). `zfs_snapshots_run_now`, `zfs_snapshots_prune_now` and
`zfs_config_apply` each start a oneshot instance of the unit `zfs-mesh-run@<act>.service`, the act from the fixed
set `snapshots`, `prune` and `apply`, through `<node>/node-service-manager.start`, with `why`; the instance runs
under the same lock and guards as §18 and §20, and the tool answers from the state and events afterwards. Their
events carry `.person` (`snapshot.taken.person`, `snapshot.removed.person`, `snapshot.skipped.person`).
`zfs_snapshots_pause` writes the key `paused` {by, why, at} in the state `schedule`, which the run reads and then
takes and removes nothing; `zfs_snapshots_resume` deletes it.
## What it says, keeps and shows
25. **Events, each kind on a subject of its own** (ADR 0679 §10). `config.applied` {target (`pool`, `dataset`,
`file` or `unit`), key, before, after, read-back}, `config.refused` {key, value, reason}, `config.waiting` {key,
waits-on, since}, `snapshot.taken.person` and `.schedule` {snapshot, by}, `snapshot.removed.person` and
`.schedule` {snapshot, kind, space freed in bytes}, `snapshot.skipped.person` and `.schedule` {pool, capacity,
reason}, `run.skipped` {reason}, and today's `scrub.requested`. They go on an events stream asked for by template
in `requires` alone and created by the bus module (ADR 0595 §1, §2, §4), kept 30 days; today's module-level `emits`
move to it. No module consumes `snapshot.taken` or `scrub.requested` today (read in mesh-catalog's manifests on
main); a session waiting through `mesh_wait` on `snapshot.taken` hears `snapshot.taken.person` after.
26. **Kept data, by template in `requires`** (ADR 0679 §10; ADR 0595 §1). A state `applied`, one key per config value,
`{before, after, at, outcome}`, written by the apply and deleted by it when that value goes back to not managed,
after its last `config.applied`. A state `schedule`, the key `paused` alone (§24). A ledger `snapshots`, one
instance per ZFS snapshot the run took, operations `taken` and `removed`, the run removing the instance of one it
destroyed; its cap is the keep counts' sum times two instances per dataset. A state `findings` (§27). The apply,
the run and the event follower declare the bus account that writes them; the tools read them.
27. **The event follower's findings** (ADR 0679 §12; ADR 0564 §2; design 47 G §383 to §388). The event follower
(`zfs-events`, root) is an observer: in its state `findings` it keeps one key `pool.<pool>.<device>` per device
of the pool (and `pool.<pool>.pool` for the pool) not ONLINE, its value the state, deleted when the device is
ONLINE again or leaves the pool; it says each change on its findings stream as design 47 G §383 says, and at its
start it reads `zpool status -P` against its keys and states them (§384). The condition keeper raises and clears
the condition from those events, so a pool fault reaches the operator once and never from a ZED handler. It also
starts the apply on the events of §18. Its process declares the bus account; today it writes the journal only.
28. **Tools** (ADR 0679 §14). `zfs_config`: every owned value with its config value, what runs on the node, its state
(`not managed`, `as set`, `waiting` with on what and since, `refused` with why, `takes effect at the next load of
the kernel module`), and the files of §10. `zfs_config_apply` (§24). `zfs_snapshots_schedule`: the schedule, its
next run, what it would remove next and the ledger. `zfs_snapshots_run_now`, `zfs_snapshots_prune_now`,
`zfs_snapshots_pause` and `zfs_snapshots_resume` (§24). `zfs_module_events`: the module's stream, the last N events
by kind (named so because `zfs_events` already reads ZFS's own events). `zfs_snapshots_check`: compares the ledger
with what ZFS holds and names each entry whose ZFS snapshot is gone and each `mesh-` snapshot not in the ledger,
changing nothing. `zfs_findings`: the follower's findings. The existing read tools stay. Each is in `replaces`
where it replaces a command.
29. **Health** (ADR 0679 §11). `zfs_health` is unhealthy for a `refused` or a failed command in the state `applied`,
naming the key and why. Warnings: a wait, naming on what and since when; a `.pacnew` beside `/etc/default/zfs` or
the package's `zed.rc`; the last scheduled ZFS snapshot older than its cadence; a failed run; `autoreplace` on or a
spare in the pool while `zed` is not `on`; the pool above 80 % with `snapshots` set; scheduled ZFS snapshots kept
after `snapshots` went to `none`; a parameter awaiting a load of the kernel module; `paused` set.
30. **When the module leaves** (ADR 0679 §15; ADR 0303 §2; ADR 0269 §2). The packages stay. `zfs-import-cache`,
`zfs-mount` and `zfs.target` are never stopped or disabled, and the module says so before it leaves (ADR 0679 §15
changes ADR 0269 §10 for them). Nothing of the package's files is given back, since the module never wrote them.
`zfs.conf`, `/etc/zfs/mesh-zed.d` and the drop-in are removed, and before leaving `zfs_config` says that the next
load of the kernel module uses ZFS's defaults. The distribution's scrub and trim timers are put back as the
module's declaration found them, started and enabled again where it stopped or disabled them. Pool and dataset
properties and devices stay as the last apply set them, also on a rollback (§8). `/etc/hostid` stays, and the
module says so before it leaves (`zfs_config`): it is no resource the node-engine removes, and removing it would
change the node's hostid, so that the pool's next import could need `zpool import -f` (ADR 0679 §15).
## How it is checked
31. **Unit tests**, table-driven, one test per area, within the module's 500: no values gives no `zpool` or `zfs` write
but the due scrub; each convergence plan from parsed `zpool status` and `zfs get` output (an add, a kernel name to
its stable name, a removal, an empty list removing nothing, a log removal refused while not healthy, a `log-mirror`
change, a wait during a scrub); every refused form (a newline, quote, `$`, backtick or semicolon in each field; a
parameter or property not on its list; a `sharenfs` value; `-f`; a device with a signature or another pool's label;
a hostid of the wrong form, a label of another id or one that cannot be read, and a hostid file holding another
value than the set one, each unhealthy and healthy again once the label and the file agree or `hostid` is unset);
the call-site test of §19, refusing any other `zgenhostid` or `zdb` form and a `zgenhostid` value not validated; the
lock (a second apply waits, a run with the lock held does nothing); the apply started only by `config-changed`,
never a timer; one scheduled resource; both ARC writes read back; applying from the last config file with the bus
down; the three templates (config file, options file, `zed.rc`); pruning never removes a ZFS snapshot whose name,
property or ledger entry does not fit, nor one with a hold or clone, and removes every surplus in one run; no ZFS
snapshot taken without a ledger record; `snapshots` back to `none` removes nothing; `zfs_snapshot` refuses a `mesh-`
name; a scheduled `snapshot.taken.schedule` does not wake a person's waiter; a parameter awaiting a load is a
warning; the `.pacnew` warning; each change emits its event with before and after; one test per tool; no resource or
template of the manifest for `/etc/hostid`; the hostid converged at the apply's start after `zfs-import.target`; the
leave keeps `/etc/hostid` and says so. The module adds no integration test; §5 is the milestone's, and the main
file's live tests stand for the module whole, said in the pull request.
@@ -2,7 +2,7 @@
layer: to-be
status: in-progress
code: [mesh-catalog, mesh-controller, mesh-host]
updated: 2026-10-22
updated: 2026-11-12
decisions:
- "ADR 0369"
- "ADR 0303"
@@ -29,6 +29,12 @@ decisions:
- "ADR 0507"
- "ADR 0508"
- "ADR 0510"
- "ADR 0679"
- "ADR 0051"
- "ADR 0269"
- "ADR 0348"
- "ADR 0595"
- "ADR 0593"
---
# 42. The machines' modules, in order
@@ -50,7 +56,7 @@ not before. Modules that need none go ahead now.
## How every module moves
1. Written in the catalogue, with its tools and their tests, and checked by the controller's catalogue
1. Written in the catalog, with its tools and their tests, and checked by the controller's catalog
check.
2. Merged, which builds it.
3. Assigned to **the first workstation, the proving machine**, and pushed. Its tools and files are
@@ -80,6 +86,7 @@ In order:
| 9 | `ssh-client` | everything under `~/.ssh` (research 027/03) | a predecessor's entries winning over the mesh's; stale keys |
| 10 | scripts | the operator's own scripts, shared and per role (research 027/03) | under no version control, copied by hand |
| 11 | `kernel` | kernel, microcode, boot entries, the fallback kernel and its boot entry, and the kernel's run-time settings per machine (transparent huge pages first) | two machines without microcode; huge pages left at the distribution's `always` on a machine whose slow disks they flood (issue 746); only one machine with a fallback kernel |
| 12 | `zfs` | on a machine that holds a ZFS pool (today the home server): ZFS's packages, its kernel module's options, its event daemon's handlers, its import, mount, share and trim units, and, for the one pool its config names, that pool's properties, its cache and log devices, its datasets' properties and a schedule of ZFS snapshots, each changed only once its config value is set (ADR 0679) | ZFS installed by hand and declared by no module; no bound on its read cache on a machine that also runs its apps; cache devices named by kernel names that swap between boots; no scheduled ZFS snapshots; the pool left with no scrub schedule by a failed timer |
`systemd`, `pacman` and `docker` hold the three seats that apply resources
(ADR 0207):
@@ -131,6 +138,10 @@ before it passed; the boot-entry script in stand-in trees of both loaders), an i
keepers of two made-up nodes on a real bus, and, after the delivery, the view answering every node and every
boot menu listing the fallback kernel.
`zfs` comes after `kernel` because its kernel module is built by DKMS against the kernels `kernel` and `pacman` put
on the machine; it is placed only on a machine that holds a pool. Its section is below,
[*The zfs module*](#the-zfs-module).
**Added 2026-10-04.** Two more modules for every machine:
- `power` holds `node-power` (ADR 0211). Other modules contribute code for
its moments (after boot, before sleep, after waking, before shutdown, on mains, on battery), and it
@@ -140,6 +151,100 @@ boot menu listing the fallback kernel.
waits for a reboot. A live restart in the middle of a full upgrade took down a workstation's
logins on the day this was written.
## The zfs module
*Added 2026-11-12, ADR 0679; the operator, 2026-11-12: "you could configure the disk by id and make sure the module
handles the rest"; "the zfs module should/must own all zfs-configuration files and options"; "we may absolutely not
lose any data".*
**What it is for.** On a node that holds a ZFS pool, the zfs module owns ZFS: what was installed and set by hand and
seen by nothing becomes the module's, and the operator's choices about it are config values (ADR 0679 §1). It holds
no seat; a seat for it is a later record's. It keeps reading and acting by hand as today: the pools, the datasets, a
named ZFS snapshot, a scrub's start, pause and stop.
**The whole shape.**
- **What it owns** (ADR 0679 §1): the packages `zfs-utils` and `zfs-dkms`; `/etc/modprobe.d/zfs.conf`, placed only
while an ARC or module option is set; `/etc/hostid`; its own folder for ZFS's event daemon (ZED),
`/etc/zfs/mesh-zed.d`, with its `zed.rc` and handlers, and a drop-in for `zfs-zed`, placed only while ZED is on;
the states of ZFS's import, mount, share, volume and target units and of the distribution's scrub and trim timers;
and, for the one pool its config value `pool` names, that pool's cache and log devices, its properties and its
datasets' properties from fixed lists. The node-engine's composer refuses a second module owning one of its paths
(ADR 0593 §7, ADR 0051 §9), all but `/etc/hostid`, which is no resource: the module's apply reads it back instead
(ADR 0679 §1, §17).
- **What it never writes** (ADR 0679 §1, §2): the package's own `/etc/default/zfs` and `/etc/zfs/zed.d` with its
`zed.rc`, and what ZFS writes itself; it reads and shows them. A dataset's `sharenfs` is the seat
`node-nfs-server`'s, `sharesmb` the SMB module's; the pool's files are the operator's (ADR 0051); the weekly
upgrade and a kernel held for DKMS are the package manager's (ADR 0508).
- **An unset or empty value changes nothing** (ADR 0679 §3): every new config value is "not managed" until the
operator sets it through mesh-config, on the Approve for a trusted value (ADR 0348 §4, §8). Placing the new version
on a node runs no `zpool` or `zfs` write but the scrub its schedule already runs, and places none of its new files.
- **Who applies** (ADR 0679 §9, §17): the node-engine places the packages, files and units as the module's resources,
some only while a value holds, all but `/etc/hostid`; the module's root process `zfs-tools apply` converges the pool's
devices and properties at its start and on `config-changed`, never on a timer, and writes `/etc/hostid` with ZFS's own
writer only while `hostid` is set and the pool's label agrees; the module's one scheduled run, under the same lock,
starts the scrub and the trim when due and takes and removes the scheduled ZFS snapshots (ADR 0679 §8).
- **What it says** (ADR 0679 §10, §11, §12): every change, refusal and wait is an event, each kind on a subject of
its own, on an events stream asked for by template; the last change per value, the schedule's pause and every
scheduled ZFS snapshot are kept on the bus in its own states and ledger (ADR 0595). Its health names a refusal or a
failed command as unhealthy and a wait as a warning. Pool faults reach the operator once, from the module's event
follower's findings through the condition keeper.
- **What it never does** (ADR 0679 §7): it converges no data device, spare, special or dedup device, upgrades no
pool, destroys no dataset, rolls back, renames and clears nothing; each is an act of its own, decided separately.
- **When it leaves** (ADR 0679 §15): ZFS keeps running (the import, mount and ZFS target are never stopped), the
packages stay, its own files are removed but `/etc/hostid`, which stays and is said, and the distribution's timers
are put back as found.
- **Its prerequisites** (ADR 0679 §16): three node-engine capabilities, built first in the same milestone under issue
1979, with their readers in mesh-sdk and the manifest validator.
```mermaid
flowchart LR
operator[the operator] -->|sets a config value| config[mesh-config]
config -->|the module's config file| engine[the node-engine]
engine -->|packages, files, units| node[ZFS on the node]
engine -->|restarts on its config file| apply[zfs-tools apply, root]
apply -->|zpool add, remove, set; zfs set, inherit; zgenhostid| node
sched[the scheduled run] -->|scrub, trim, ZFS snapshots and their removal| node
follower[the event follower] -->|scrub ended, device appeared: start| apply
apply -->|config.applied, refused, waiting| stream[(events stream)]
sched -->|snapshot.taken.schedule, removed, skipped| stream
apply --> applied[(state applied)]
sched --> ledger[(ledger snapshots)]
follower -->|findings| keeper[the condition keeper]
tools[the module's tools] -->|read| applied
tools -->|read| ledger
```
**Acceptance tests** (ADR 0679, *How it is checked*), each with its level; every live test is read by a tool call:
1. *Live.* Placing the new version on the home server changes nothing: `zfs_config` shows every value "not managed",
and `zpool history` read before and after shows no command but the scheduled scrub.
2. *Live.* Setting `cache-devices` puts both cache devices in the pool by their stable names; after a reboot that
swaps the drives' kernel names, both stay ONLINE (`zfs_pool_layout`).
3. *Live.* The node-engine's apply report shows the module's run timer active before the distribution's scrub timer
is stopped, and `systemd-analyze calendar` of the generated `OnCalendar` passes with a next run within 15 minutes.
4. *Live.* With `hostid` unset, `/etc/hostid` is not written (`<node>/node-reads.stat`); `zfs_config` shows it "not
managed".
5. *Live.* With `snapshots` set, the next run's ZFS snapshot is in `zfs_snapshots` with `mesh:schedule` and in the
ledger (`zfs_snapshots_schedule`); after its keep count passes, its removal is in both and in `zfs_module_events`.
6. *Live.* A value of a refused form makes `zfs_health` unhealthy naming the key; a declared device that is not
present makes it a warning naming what it waits on.
7. *Live.* With `zed` on, `<node>/node-files.list` of `/etc/zfs/mesh-zed.d` shows its `zed.rc` and only the handlers
set, and `<node>/node-service-manager.status` shows `zfs-zed` running with the drop-in; with `zed` off, stopped.
8. *Live, on a test placement.* After the module leaves, `zfs-import-cache`, `zfs-mount` and `zfs.target` are still
running (`<node>/node-service-manager.status`) and `zfs_pools` shows the pool imported.
9. *Unit and integration.* The milestone part's §31 unit tests pass within the module's budget of 500, and its §5
integration test passes in the node-engine.
## Milestones
| milestone | what it builds | part | state |
|---|---|---|---|
| zfs-owns-zfs | the zfs module's ownership of ZFS (ADR 0679), with its three node-engine prerequisites and their readers, under issue 1979 | [42-milestones/zfs-owns-zfs.md](42-milestones/zfs-owns-zfs.md) | designed |
The modules of the tables above were built before designs were cut into milestone parts; their remaining lines move
into a part of their own at their next amendment (playbook 02, *The shape of a design*).
## Phase 2 — both workstations
In order:
@@ -199,11 +304,11 @@ both places of both bars once. The bar writes no block that depends on the hardw
the power module's state `power.draw`, one value per machine, which its bundle puts from its own root
sampler. The block `shows: state`; the bar's bundle watches it under a read the controller grants the
bar for what the modules on its machine offer, and the bar names no module. The battery block is the
bar's own, reading the kernel. The catalogue's `checks/paths` refuses a module that names another
bar's own, reading the kernel. The catalog's `checks/paths` refuses a module that names another
module's path under `/run` or `/var/lib`.
The node-engine is delivered first, so the machines report the two capabilities. The controller comes
next, because it parses `data`, `if-capability` and `renders`. The catalogue comes last. Left open: the
next, because it parses `data`, `if-capability` and `renders`. The catalog comes last. Left open: the
draw at the wall (a metered plug or a UPS), a GPU-load block from a generic module, and the headsets.
## Phase 3 — one machine model
@@ -213,6 +318,6 @@ and `memory-pressure` (research 027/03).
## How it is checked
Each module's own tests and the catalogue check, at merge. On the proving machine, each tool answered
Each module's own tests and the catalog check, at merge. On the proving machine, each tool answered
through the mesh and each owned file checked in place, before any other machine is assigned. This
document's tables are updated as each module lands.
@@ -146,8 +146,8 @@ a manifest. Not off-site: a lost machine loses its backups with its data, by the
## How it is checked
The catalogue check (`module check`, and the catalogue-wide test beside it) refuses a provider that
The catalog check (`module check`, and the catalog-wide test beside it) refuses a provider that
grants without saying what it keeps for its consumers, an irreplaceable item with neither a backup nor a
redundancy, and any backup line written by hand (ADR 0233). The store rule this design stated before
was held only by a controller test over the catalogue checked out beside it, never by `module check`. The weekly
was held only by a controller test over the catalog checked out beside it, never by `module check`. The weekly
restore test above, and the 48-hour status line, are the running checks.
@@ -2,7 +2,7 @@
layer: to-be
status: in-progress
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
updated: 2026-10-14
updated: 2026-11-04
decisions:
- "ADR 0357"
- "ADR 0325"
@@ -68,7 +68,7 @@ Owning repositories: **mesh-controller** (the condition store, watchdogs, `docto
calls, the hand-act log, the facts snapshot), **`mesh-host`** (the node-engine: the apply queue, report
order, epoch refusal, the `report` verb, rollback witnessing), **mesh-tools** (the tool runner and the
mesh MCP server: their heartbeat, passing every argument, health answers), **mesh-catalog** (the
`operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalogue's
`operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalog's
composition check), **mesh-sdk** (the TypeScript providers' loop and its retirement), **mesh-lab** (the
replays and the induced-failure scenarios).
@@ -680,7 +680,7 @@ runs, and a restore that builds a new store beside the live one for a person to
**The facts snapshot.** The controller exports daily, and after any change of machines, assignments or
seats: every machine (a stable pseudonym of the same length as its name, its role, operating system,
C library, architecture, node-engine and tool runner builds), assignments, settings keys and their
non-secret values, seats and their holders, the catalogue commit, and the versions the mesh runs of
non-secret values, seats and their holders, the catalog commit, and the versions the mesh runs of
the bus server, the store and the node-engine. No secret and no address: an address is replaced by one
from a documentation range. It is kept in the artifact store as `facts/latest`, where the build seat
reads it.
@@ -768,7 +768,7 @@ replay covers what must not be done to it on purpose, and every rule keeps a liv
- **It runs on the build seat**, asked by the controller when the forge's announcer says a pull request's
head moved: the repository's own `merge-check.sh` in the mesh's Go toolchain with no container runtime
socket, beside a throwaway store and bus of the versions the snapshot says run, then mesh-lab's replays.
The verdict is the head commit's status `mesh/merge-gate`, an error never a pass. A catalogue change is
The verdict is the head commit's status `mesh/merge-gate`, an error never a pass. A catalog change is
judged by the controller the mesh runs; a node-engine change by it built with the change's validator.
- **Replays live where their incident is**: a component's logic in its repository as `TestReplay<issue>`,
what the mesh runs (the bus's release, the resolver under musl and glibc) in mesh-lab's `replays/`, all
@@ -868,7 +868,7 @@ the `data` verb; `cleanup` extended to a module's own retired data, deleted by t
holder after a last restore point; the controller's grant names `node-backup.backed-up`. Built on
branches in mesh-controller (migration 0072), `mesh-host` (the installer's user list; the kept-directory
report), mesh-catalog (the holder's measuring and deletion, every module's data, the providers' held
sizes), the media catalogue and the photo application, not yet merged. **Not yet:** the TypeScript
sizes), the media catalog and the photo application, not yet merged. **Not yet:** the TypeScript
providers saying their consumers' sizes (until then a consumer at two of them is `data-held-twice`, a
warning, not compared), and SMART read under an array.
@@ -931,8 +931,8 @@ and "the commit is the build at hand"), on branches, not yet merged:
| Repository | Delivers |
|---|---|
| mesh-controller | the planner's one mapping of a changed file onto modules and its one answer of a merge's reach, asked by the merge handler, the what-if, the gate and the check; a file no build reads touches nothing (issue 280's *left open*); every pull request answered — the gate when it reaches a module, the repository's own check for the mesh's repositories, a pass that says so otherwise; the gate run by the build seat with its judge chosen from the graph, composing the plan's definitions only, failing only a manifest problem the change brings; the delivery plan computed and carried with the verdict; a build of a commit off its module's trunk recorded and never registered; its own manifest naming every verb of its seat |
| mesh-catalog | the forge's announcer saying, with every pull request, the directories holding a module at its head, the files it deletes and whether it has a `merge-check.sh`; both statuses and the delivery plan on the pull request; `gitea_branch_protection_get` and `gitea_branch_protection_set`; the catalogue's own check |
| mesh-controller | the planner's one mapping of a changed file onto modules and its one answer of a merge's reach, asked by the merge handler, the what-if, the gate and the check; a file no build reads touches nothing (issue 280's *left open*); every pull request answered — the gate when it reaches a module, the repository's own check for the mesh's repositories, a pass that says so otherwise; the gate run by the build seat with the program it composes with chosen from the graph, composing the plan's definitions only, failing only a manifest problem the change brings; the delivery plan computed and carried with the verdict; a build of a commit off its module's trunk recorded and never registered; its own manifest naming every verb of its seat |
| mesh-catalog | the forge's announcer saying, with every pull request, the directories holding a module at its head, the files it deletes and whether it has a `merge-check.sh`; both statuses and the delivery plan on the pull request; `gitea_branch_protection_get` and `gitea_branch_protection_set`; the catalog's own check |
| `mesh-host`, mesh-tools, mesh-sdk, mesh-lab, mesh-media-catalog, and the applications built from their own repositories | each its own `merge-check.sh`; mesh-tools' tests each on a bus of their own |
| mesh-tools-go, mesh-tools | a C compiler and Python in the Go toolchain, git in the TypeScript one |
| hq | its own `merge-check.sh`, running `records.py`, `index.py` and `cycle.py` |
@@ -0,0 +1,421 @@
---
layer: to-be
design: 46
milestone: 7-approval-mark
updated: 2026-11-11
code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-host, mesh-catalog modules/zsh, mesh-catalog modules/gitea, mesh-catalog modules/mesh-files]
decisions:
- "ADR 0286"
- "ADR 0259"
- "ADR 0627"
- "ADR 0656"
- "ADR 0666"
- "ADR 0668"
- "ADR 0595"
- "ADR 0597"
- "ADR 0310"
- "ADR 0394"
- "ADR 0619"
- "ADR 0268"
- "ADR 0564"
---
# Design 46, milestone 7 — a call that needs the operator's approval
**What it builds.** A tool or a seat verb marked in its manifest or its seat's definition runs only on the operator's
approval of that one call, with its exact arguments (ADR 0286). The first call asks the operator on the seat
`operator-channel` and runs nothing; the same call made again within 30 minutes of the operator's Approve runs once.
The tool runner (module `node-tools`) asks, judges and claims; it reads the marks from the catalog half on the bus; and
on a machine whose tool runner does not honour the mark the grants part withholds every marked subject. The shape is in
the design's main file,
[§10, *A call that needs the operator's approval*](../46-the-conversation-with-the-operator.md#a-call-that-needs-the-operators-approval);
this part holds every line the milestone builds, cited as "design 46 7-approval-mark §n".
**When.** It is built and delivered in design 47's milestone G, in G's delivery group, with design 34's milestone
every-event, whose marked waits (design 34 every-event §32 to §37) use this gate (the operator, 2026-11-11: "Build the
gate in G").
**Where.** mesh-sdk (the manifest field, the seat definition's field, the per-machine state's keys), mesh-contracts
(the login shell's seat definition), mesh-delivery (the indexer's manifest validator and catalog graph entry, the grants
part's deny), mesh-tools (the tool runner's gate, its state, its tools, and the mesh MCP server's `mesh_describe` in
it) and mesh-catalog (the zsh module, which holds the login shell, and the forge module, §21). Nothing is built in a controller: no controller issues memberships today, bus permissions are the grants part's
(seat `mesh-grants`), the hand-act log is the seat `mesh-hand-acts`, and the asker's logic is operator-channel's client
in mesh-contracts (ADR 0627 §1).
**What changes from ADR 0286 as written.** Each line says in its *Why* where something built since ADR 0286
(2026-10-10) changes how a clause is met: the mark is read from the catalog half and not from a membership (§5), the
digest is Go alone (§8), the claim is kept in the runner's bus state and not a file (§13), `warranted` is a kept call
and not a retry on a timer (§16), and a fourth open ask is offered as G makes it (§17). The controller's own verbs (ADR
0286 §43) are not built: no controller serves verbs.
**The bound of the gate, a known limit.** The gate holds for every call that reaches the tool runner's handler. It does
not hold for a process that reads the tool runner's bus credential (the file `broker` in its state directory,
`own-secrets`) and speaks to the bus itself: that account may subscribe a marked subject, a restricted stream's among
them, on a machine whose runner honours the mark. Read on the workstation on 2026-11-11 with `node-reads.stat`, the
file is mode 0600, owned by the operator's account, in a directory of mode 0755, and the prepare of 2026-11-11 read
the same mode and owner on all four nodes: every process running as the
operator's account, every module on the machine's shared account and every agent session among them, can read it and
hear a restricted stream without a warrant. This is the same known limit as design 47 G §385 (modules running as the
machine's account, issue 1966 note 193, whose fix carries its check), and this milestone does not change it: that a
restricted stream is heard only on a warrant holds against every caller once that fix runs modules and sessions on
accounts that cannot read the file, and until then against callers that go through the tool runner. The
prepare reads the file's mode and owner on every node with `<node>/node-files.stat` and writes them in the milestone
file; a node where the file is readable beyond the operator's account is a finding of the prepare. The forge module's
own credential (§21) lies in a directory of mode 0700, owned by the forge's account, which the reading account cannot
enter; `node-files.stat` answers such a file's mode from this milestone (§21a), and the prepare reads it then.
## The mark
1. **A module marks its tools and verbs in `needs-approval`.** mesh-sdk's `catalogue.Manifest` gains `NeedsApproval`,
the manifest key `needs-approval`: a list of strings, each a tool the module serves (`<tool>`) or a seat verb it
serves (`<seat>.<verb>`), and, under design 34 every-event §32, one of its events. Absent is no mark. *Why:* ADR
0286 §1, §49 (each marking is a manifest change, reviewed like any other). Only the contract's manifest reads
(`ManifestReads`) gain `word:needs-approval`; the baseline of the engines before any stated what it reads
(`manifest_reads_baseline.txt`) is unchanged, since none of them reads the word (Q-BL). *How it is checked:* unit, a
row in `go/catalogue/invokes_test.go`'s `TestAManifestMaySayWhatItInvokes`, the nearest table over raw manifests
(Q-T): a manifest naming a tool, a seat verb and an event in `needs-approval` decodes all three with `UnknownField()`
empty, and one without it has no mark.
2. **A seat's definition marks a verb for every holder.** mesh-sdk's `seatdef` reads a verb's `needs-approval`, a
boolean, in a seat definition (`seats/<seat>/seat.json` in mesh-contracts); the seat definitions the catalog half
carries (design 47 G §194) carry it on the verb. The definition format's own schema in mesh-contracts
(`format/seat-definition.schema.json`) gains a verb's `needs-approval`, a boolean (Q-SC). Every strict reader of a
seat definition reads the word from G's reader release (design 47 G §340), so no reader refuses a definition that
carries it (Q-RR). *Why:* ADR 0286 §2. *How it is checked:* unit, a fixture row in `go/seatdef/seatdef_test.go`'s
`TestADefinitionIsReadStrictlyNamingWhatIsWrong` (Q-T), and the schema validates the login shell's definition of §21.
3. **Marks add up.** A verb is marked when its seat's definition or its holder's `needs-approval` marks it; a holder
cannot unmark what its seat marks. *Why:* ADR 0286 §3. *How it is checked:* unit, a row of §5's table.
4. **The manifest validator refuses a mark it cannot honour.** The indexer's manifest validator (`judge/rules.go`)
refuses, as the checker runs it, and sets aside to wait, as the indexer runs it, a manifest that: marks a tool its
`tools` does not list, or a verb its `claims` do not serve (ADR 0286 §4); marks a tool or a verb, or holds a seat
verb its seat marks, without `operator-channel` in `uses` (§7); marks a tool or a verb while every artifact of kind
`bundle` it builds is run by one of its `process` resources (`artifact`), so its own program answers its tools and
no tool runner loads or gates them (a module such as the forge's, whose tools bundle no process runs, is not
refused); or names in `needs-approval` one name that is both one of its tools and one of its events. A tool mark
is checked against `tools` where the manifest lists its tools; where only its bundle names them, the tool runner
checks each marked tool against the tools the bundle registers at load: a bundle whose marks name a tool it does not
register is not loaded, so no tool of that module is served on that node at all, failing closed rather than serving
unmarked what the manifest meant to mark, and that is its
finding `<node>.<module>.mark-unserved` (said as §16's), cleared when a bundle that serves it, or a manifest without
the mark, is loaded. Each refusal names the entry and the rule. *Why:* a mark
nothing enforces would be a call that runs unasked while saying it is guarded; ADR 0286 §43 had the controller
enforce its own verbs, and no other own-process server has a gate, so such a mark is refused rather than ignored;
and an entry that names two things is read one way by one reader and another way by the next (the design's choices
under ADR 0286 §4, §40, the refusal kept by lead-47 on 2026-11-11). *How it is checked:* unit, one row per refusal
in `judge/rules_test.go`'s `TestRulesManifests` (Q-T), rows that accept the zsh and forge manifests of §21, and a row of §22's mark table for
`mark-unserved`.
## Where the marks are read, and where they are withheld
5. **From the catalog half on the bus, never from a bundle.** The indexer's catalog graph entry of a module (design 47
G §174) carries the validated manifest whole (design 47 G §443), its `needs-approval`, its `emits` and its streams
(their events as the manifest spells them, and whether each carries a `grant`) among it; mesh-sdk's `buscompose`
exports `WirePattern`, which a reader uses to turn a stream's events into the bus's subjects, the grants part's own
copy going (Q-WP). No part but the indexer reads a manifest: every other part reads the entry of the build concerned
from the catalog half (design 47 G §442; the operator, 2026-11-11: "only the indexer uses the manifest to build the
module catalog"; Q-MR), the grants part's `declaredFor` among them for every grant, not only the marks; the seat
definitions of the catalog half carry the verb marks (§2). The tool runner, which from G takes its first view of the
catalog half by `mesh-catalog.revision` and follows `catalog-revision` (design 47 G §196), derives from it, for each
module it serves, its marked tools and verbs, in a package of its own, `node-tools/internal/approval`, again on
every revision it hears. No module's name is written in its code. The marks follow the running version: a bundle's
served version is the fingerprint its composed environment carries (design 47 G §424), whose entry the runner reads
by `mesh-catalog.entry` (design 47 G §443) when it loads the bundle; it applies that entry's marks and, while the
newest revision's entry for that module differs, the union of both, so a mark is never lost before or after the
module moves, and a delivery that drops a mark while the old bundle still runs is still asked. When the served
entry cannot be read, every marked call on that runtime is refused (§19). A mark added while a call waits applies
from the next call, which the answer of §12 says. *Why:* ADR 0286 §5 had the controller issue the mark in a
membership so that a bundle could not drop it; no controller issues memberships now, and the judged catalog half is
the same guarantee: the bundle never carries the mark (ADR 0656 §6, one source for which modules exist). The union
keeps ADR 0286 §3's "marks add up" across a version change. The served version is read from what the bundle runs,
not from a placement, which can change before the bundle does (step 0b). *How it is checked:* unit, a table test in
`approval` derives the marks from fixture catalog halves and served entries, §3's row, a version change and the
union among them, and a served entry that cannot be read.
6. **A machine whose tool runner does not honour the mark serves no marked tool or verb.** The tool runner states
`approval-mark/1` among its call capabilities (`runtime.CallCapabilities`), which its announcement carries with
the tool runner's own fingerprint, and its node's node-engine takes both into the node's record; the assignment
half carries, for each node, its capability readings, each with the fingerprint of the runner that announced it
(design 47 G §194; Q-CR). The grants part, composing a runtime's account, reads that runtime's node's readings and
the fingerprint the node-engine's `running.<node>.node-tools` last said running (design 47 G §309): where the tool
runner does not state `approval-mark/1`, where the node has no readings, or where the readings' fingerprint differs
from the running one or is absent (an older reading, failing closed as §19; no time is compared), it composes a
subscribe deny of every marked subject of every module the runtime carries, the marks read from the catalog half
as §5 has them: `mesh.mod.<module>.tool.<tool>` and the same followed by `.*`; the caller-named subject mesh-sdk's
`process.Call` sends on first, `mesh.mod.<module>.call.<tool>.>`; for a marked verb of a seat held per node
`mesh.seat.<seat>.tool.<verb>`, the same followed by `.<node>`, and `mesh.seat.<seat>.call.<verb>.<node>.>`; and for
a marked verb of a seat held once for the mesh `mesh.seat.<seat>.tool.<verb>` and `mesh.seat.<seat>.call.<verb>.>`,
since its subjects name no node (Q-DL). The bus's deny outranks its allow of `mesh.mod.<module>.tool.>` for each.
Going back to an older tool runner on a node leaves the window until its readings update, in which the old deny or
none stands; that window is accepted. `Permissions` gains `SubscribeDeny`, written into the user list as the
server's subscribe deny, as `PublishDeny` is for publishing; the user list's `User` gains `subscribe_deny` beside
`publish_deny`, in its comparison and in the differences the grants part says (Q-SD). An added `subscribe_deny` is a
narrowing: the user-list guard lets it pass at once, on the first composition after the release, and counts it as
no right lost. The withheld tools and verbs are named in the grants part's read of that account
(`mesh-grants.grants_user_list`, a field `withheld` with each subject and "its tool runner does not state
approval-mark/1"), and in the node-engine's `composition` of the module, which reads them through a verb `withheld`
that the seat `mesh-grants`'s definition gains: class `read`, callers the node-engine, persons and the operator,
input `node`, answer each module's withheld subjects and why, with a fixture in the definition; the node-engine's
manifest gains `mesh-grants` in `uses`; when the verb does not answer, the composition says "withheld unknown: the
grants part does not answer"; never read from the grants part's state (Q-NE). *Why:* ADR 0286 §7, §8, §9: the bus
enforces the mark; the runner's status of ADR 0286 §7 is read from the grants part's account read, since a runner
that does not honour the mark is a version that cannot say so. A reading is tied to the runner that made it, not to
a time, so a clock or a late reading cannot lift the deny (step 0b). A call that reaches no responder because its
subject is withheld is answered by the mesh MCP server naming `withheld` and the grants part's read. *How it is
checked:* unit, the grants part's composition table: readings lacking the capability, no readings, readings with
another runner's fingerprint and readings with none each compose the deny of every subject above, the caller-named
ones, a marked seat verb's `.<node>` form and a mesh-wide seat's marked verb among them, each named in `withheld`;
readings with the capability and the running fingerprint compose no deny; the release's first composition passes
the user-list guard with the deny added; a node gaining `approval-mark/1`, where only its readings change, has the
deny removed; unit, the assigner's table: a reading keeps the announcing runner's fingerprint; whole (node-engine),
`composition` shows the withheld subjects, and with the grants part down says "withheld unknown"; integration, the
grants part's real-bus test: an account composed with the deny is refused delivery of a call on a marked subject
while it holds `mesh.mod.<module>.tool.>`, and so is a call sent through `process.Call` on the caller-named
subject.
7. **The asker's grants are a user's of `operator-channel`.** Because the manifest says `operator-channel` in `uses`
(§4), the grants part composes, on the account of the runtime carrying the module, the publish of
`mesh.seat.operator-channel.accept.ask.<module>` and `.cancel.<module>`, the hearing of its warrants on
`mesh.seat.operator-channel.event.decided.<module>` and the read of the router's record under `<module>.` in the
seat's state `asks`, as for every user (ADR 0259 §3). The runner passes the client its own publish and read on that
runtime's account (`Client.Publish`, `Client.Record`), never the bundle's. *Why:* ADR 0286 §6. *How it is checked:*
unit, a row in the grants part's composition table for a fixture module with a mark.
## The act, and the first call
8. **The act is digested with operator-channel's client.** For a call of a marked tool or verb, the tool runner
builds an `operatorchannelclient.Act` with `address` (as called), `node` (the node that serves it), `module` and
`tool`, or `seat` and `verb`, `build` (the fingerprint the bundle's environment carries, the version it serves, §5; Q-BD), and `arg.<name>` for each
argument given, a string as itself and any other value as its JSON in RFC 8785's canonical form; an argument left
out is not in the act. Its digest is `operatorchannelclient.ActDigest`. *Why:* ADR 0286 §10, §11; the digest is in
mesh-contracts since ADR 0627 §1. The TypeScript twin ADR 0286 named is not built: the tool runner is Go, and no
TypeScript asker computes an act. *How it is checked:* unit, rows in `approval` for the act's fields, an omitted
argument, a number, an object and a changed build each giving another digest.
9. **A call refused without asking.** On a call with no approval for its act, the runner refuses at once, asking
nothing, a call whose arguments hold anything `outward.CheckSecret` refuses (mesh-sdk), or whose arguments are more
than 16 KiB. *Why:* ADR 0286 §12; mesh-sdk's `outward` is the content rule ADR 0310 §4 moved there. *How it is
checked:* unit, two rows of §22's table.
10. **One ask, under the module's name.** Otherwise the runner asks through an `operatorchannelclient.Client` whose
`Module` is the marked module, whose `State` is the runner's state of §13, and whose new field `KeyPrefix`, which
the client in mesh-contracts gains and puts before every key of its own `State` it writes and reads (empty for
every asker today), is `<node>.`; never before the router's record it reads through `Record` and `Where`, whose key
stays `<module>.<id>` in the seat state `asks`; unless an ask for the same act is
open there (§13's `act.` key), in which case it asks nothing new. The ask: `about` the act's digest; `who`
`operator`; `expires` 60 minutes after; `on-expiry` "nothing runs; call again to ask anew", since the client's
`Check` refuses an authorising ask without it, and no option is taken on expiry (ADR 0286 §22; Q-OC2); two options, `approve` (label Approve) and `decline` (label
Decline), both at level `approve`, each binding the act's digest; the headline "Run <tool> on <node>?",
"Run <tool>?" when that is longer than the router allows (`HeadlineLength`, 60), and, when that too is longer,
the names cut with "…" to fit (Q-HL), ending "Part shown" whenever an argument is not shown
whole; the explanation: the first line of the tool's own description, the node, that it was called through the
mesh's tools, which cannot tell who called, every argument one per line as "name: value", and the act's
fingerprint, its digest's first 24 hexadecimal digits in groups of four. *Why:* ADR 0286 §13, §15, §16, §20 to
§23; the client checks the ask (`Check`) before it publishes. *How it is checked:* unit, a row in `approval` holds
the published ask's fields against a fixture call, and a second identical call publishes nothing.
11. **An argument is shown whole only where it may leave the mesh.** Each argument is shown whole where
`outward.Check` passes it, else in its shape with ‹path›, ‹address› or ‹withheld› in place of what it refuses; the
arguments are shown whole when they are at most 2000 bytes and 40 lines, otherwise their first 20 lines and 1200
bytes, marked "NOT SHOWN IN FULL", and the explanation then says that the whole is read with `mesh_approvals`
(§18) on that node, whose fingerprint must be the one on the phone. *Why:* ADR 0286 §17 to §19. *How it is
checked:* unit, rows of §10's test with a path, an address and a long argument.
12. **The first call answers "Not run", or that the router has not taken it.** The runner waits up to 5 seconds for
the router's record of the ask (§14) and answers the call as an error, never as a result. With the record: "Not
run: this needs the operator's approval. Asked on <channel> at <time> (ask <id>, fingerprint …); make the same call
again within 30 minutes of the approval; the ask expires at <time>." With no record within the 5 seconds: "Not
run, asked; not yet taken by the router (ask <id>, fingerprint …); make the same call again later." The ask stays
in the seat's stream of accepts, a work queue the router takes it from when it runs, and the `act.` key stays, so
the next call reads the router's record of the same ask and asks nothing new. A mark added since the caller last
called is named in the answer ("marked since <revision>"). *Why:* ADR 0286 §14, §37; the caller calls again, as
the operator chose on 2026-11-11 ("Call again (as designed)") over a held call that runs on the Approve and returns
its outcome (issue 417, 2026-10-16). *How it is checked:* unit,
rows of §22's table hold both answers; a row with no router holds that a second call asks nothing new; a row holds
a headline whose names are cut with "…" to 60 characters (Q-HL).
## The runner's record, and the same call again
13. **The runner keeps its asks and claims in a state of its own, per machine.** The tool runner, a foundation part,
declares a state `approvals` in its manifest's old `state` section, per machine, with a time to live of 172,800
seconds (48 hours), which the grants part creates as it creates a foundation part's states (ADR 0619 §2, §3; the bus
module refuses a per-machine state in the new form today). Its size is the old form's fixed one, 64 MiB
(`StateMaxBytes`), which §18 reads from the bucket, and a value is at most 256 KiB: an ask whose kept call would pass
it fails closed, naming the cause (ADR 0595 §3; Q-CAP). A runner whose membership binds no `approvals` serves every
unmarked call as before and refuses only marked ones (§19).
Its keys: `<node>.ask.<id>`, the asked call as the client keeps it (the ask, the act, the whole arguments, when
asked, whether acted on, and `recorded` once `warranted` answered, §16; the client's `Kept` gains `act`, `arguments`
and `recorded` for it, Q-KP), and `<node>.act.<digest>`, the id of the
newest ask for that act. The runner on each machine is the one writer of its machine's keys; a key is deleted by
its time to live, never by hand. A per-machine state reaches today only the key named for its machine; mesh-sdk's
per-machine form and the grants part's `stateAccess` gain the keys below it, `<node>.>`, for every per-machine
state, a bundle's membership (`stateIssuedFor`, ADR 0260 §18) too, in the same form: one more subject per
per-machine state on each account, and every module's per-machine keys keep their form; mesh-sdk's rebuild of it
rides in G's group. That size is the bucket's, shared by every machine's keys. A write the bus refuses (the size
reached, or any other) fails closed under §19 and is said in the answer; `mesh_approvals` reports the bucket's fill
against its size read from the bucket, saying that both are every machine's together. A run still unrecorded (§16) an hour before its key's 48 hours end is written in the runner's log
as an error naming the ask; this is written only while the runner runs, so a machine asleep through that hour keeps
only the `call-unanswered` history of §16, which is accepted.
*Why:* ADR 0286 §24, §30 kept the waiting call in the runner's record and claimed a warrant in the node's own file,
kept 48 hours, by one writer per node; the client built since keeps asks and marks a warrant acted on by
compare-and-set in a module's bus state (ADR 0259 §10, ADR 0627 §1), and a per-machine state (ADR 0595) is one
writer per machine, so the file is not built. The keys below the machine's own depart from ADR 0260 §16 (a
per-machine state reaches only its machine's key), the design's choice following the operator's answers of
2026-11-11 (the gate built in G); the key form is the design's choice too. *How it is
checked:* unit, the manifest test of `node-tools` holds the old-form entry (per machine, its time to live) and no
`approvals` in `requires`; a row of §22's table: an ask over 256 KiB refused naming the cause; another: with no
binding, an unmarked call runs and a marked one is refused; the grants part's composition table holds the
`<node>.>` reach and no other node's; integration, §22's whole test reads the keys.
14. **The same call again is judged on the router's record.** When a call of a marked tool comes and `<node>.act.`
names an ask, the runner reads the router's own record of that ask under the module's name (`Client.Where`, the
seat state `asks`, key `<module>.<id>`, whose value in operator-channel's definition gains the `warrant` it already
carries, so the definition names what `Where` reads; Q-OC1), never a record of its own or the caller's; it composes the ask again from
the act and holds the warrant to it with `operatorchannelclient.For` (the same asker and ask, the ask's digest, a
person's choice, an option offered, at its level, before it expired); it requires the option to be `approve` and
the act to be the one it bound (`operatorchannelclient.Performs`: the same node, build and every argument); and it
requires the call within 30 minutes of the choice. It judges the newest ask it keeps for the act. *Why:* ADR 0286
§26 to §29, §32. *How it is checked:* unit, rows of §22's table: approved, approved for other arguments, approved
31 minutes ago, declined, expired, another asker's warrant.
15. **One approval runs one call.** Before it runs, the runner marks `<node>.ask.<id>` acted on by compare-and-set
(`Client.Take`); a warrant marked before is refused, so one approval runs one call however often the call is made,
and a warrant replayed onto the bus runs nothing. After "already run", "expired" or "approved too long ago" the next
identical call asks anew, and the used mark stays. *Why:* ADR 0286 §30, §31, §33, §36. *How it is checked:* unit, a
row of §22's table makes the approved call twice and runs it once; two calls at the same moment run it once.
16. **The call runs, and its warrant is recorded, across a restart too.** Once the checks pass, the runner runs the
call and answers with the tool's own answer and a line naming the approval ("run on the operator's approval via
<channel> at <time>, ask <id>"). It then calls `mesh-hand-acts.warranted` with `asker` (the module) and `ask` (the
id) as a kept call in mesh-sdk's kept-call form (ADR 0668 §1, §2, §7; design 47 G §351, §425: the verb takes it once
per ask), the key that keeps it open being `<node>.ask.<id>` without `recorded`, and no event replacing it; it is not
`busretry`, which stops after 30 seconds (Q-KC). On its answer the runner writes `recorded` on `<node>.ask.<id>`. At each start the runner calls `warranted` again for every acted, unrecorded ask its keys hold,
within their 48 hours, each named in its `findings-stated`'s `pending` until it is made (design 47 G §384). One
unanswered after five minutes is the runner's finding `<node>.<ask id>.call-unanswered` in its state `findings`,
said on its stream `node-tools-findings` (design 47 G §383, §387), which the condition keeper hears; it clears when
`warranted` answers. *Why:* ADR 0286 §34, §35; the hand-act log is the seat `mesh-hand-acts`, whose `warranted`
reads the router's own record, and its "again every ten minutes for a week" is ADR 0668's kept call now, kept
across a restart by the `recorded` mark (ADR 0668 §3, ADR 0564). *How it is checked:* integration, §22's whole test
reads the hand-act recorded once, also when the runner is restarted after the run and before `warranted` answers;
unit, §18's row of `record`.
17. **What a later call is told.** For the same call made again before it can run, the caller is told, as an error:
still waiting; declined (the same act is not asked again for 10 minutes, and the answer says so); expired,
approved too long ago or already run (each asks again, §15); not yet taken by the router (§12); or refused, with the
router's words. An ask the router holds back as the asker's fourth open one is still waiting, and
`operator-channel.asks` lists it (ADR 0666 §12, as G builds it). *Why:* ADR 0286 §38, §39; ADR 0286 §40's refusal
of a fourth open ask was the router's before G, which now offers a fourth and holds the oldest back. *How it is
checked:* unit, rows of §22's table.
18. **The tools read and steer what was asked.** The mesh MCP server on each machine gains the tool `mesh_approvals`:
with no argument, the calls asked on that machine in the last day, newest first, each with its state and
fingerprint, read from §13's state, and the marks it applies, each module's marked tools and verbs with their
source (its module's entry in the catalog half or its seat's definition) and the catalog revision read; given an ask's id, its whole
arguments and the act's digest; given `record` and an ask's id, it asks `warranted` again for that one ask by hand,
in the kept-call form of §16, recorded in the hand-act log first; given `cancel` and an ask's id, it takes that open ask back (`Client.Cancel`),
recorded first in the hand-act log through `mesh-hand-acts.hand-act` with why, the verb taking who from the caller
it proves (Q-HA), and refused when the record
cannot be written. `mesh_describe` answers, for a marked tool or verb, `needs-approval` with its source. *Why:*
ADR 0286 §25 (the runner's `approvals`), named as the mesh MCP server's own tools are; a tool reads everything a
module holds and does by hand what it does by itself (the operator's instruction of 2026-10-25). *How it is
checked:* unit, rows in the mesh MCP server's tool table test: the list, the marks with their sources, an ask read
whole, a `record` that asks `warranted` again for one ask, a cancel recorded and one refused without a record, and
`mesh_describe` of a marked tool.
## Failing closed
19. **Nothing runs when a part of the check is missing, and a stuck gate is said.** A call of a marked tool is
refused, runs nothing and says why when: no module holds `operator-channel` (read from the assigner's first view
`mesh-delivery-assigner.views.seat-holders`, which the tool runner consumes; the seat list names no holders); the
served entry of the module cannot be read (§5), which refuses every marked call on that runtime; the
router refuses the ask; the ask cannot be published; the router's record cannot be read; §13's state cannot be read
or written, in which case no marked call runs on that machine until it can, the failed read or write being made
again in the kept-call form (ADR 0668 §7; design 47 G §351), kept open by the failing key, until it succeeds; or the marks are not yet read. Before
the first view of the catalog half is read, a call waits for it within its 5 seconds and is then judged, asking
nothing while the marks are unread; still unread, it is refused, naming `mesh_first_views`. When §13's state has
been unreadable or unwritable for five minutes, the runner raises its finding `<node>.approvals.approvals-unreadable`
(said on `node-tools-findings` as §16's), and clears it when the kept call succeeds; its `findings-stated` at a start
judges it anew from a read of the state (design 47 G §384). Each refusal is
written in the runner's log with the ask's id and fingerprint, never the arguments. *Why:* ADR 0286 §40, §42; a
gate that refuses every call for a reason nobody hears is a silent fault, and the finding and its five minutes are
the design's choice. *How it is checked:* unit, one row of §22's table per case, the holder read from a fixture
`views.seat-holders` with none and with one; a row where the served entry cannot be read and every marked call on
that runtime is refused; and a row where the state fails, the finding is raised after five
minutes, the state recovers with no further call, and the finding clears.
20. **The terminal is not exempt.** A marked tool called through `mesh-cli` or any caller asks as every call does: the
runner cannot tell who called. *Why:* ADR 0286 §41. The gate wraps every handler the runner serves, a bundle's
tools and seat verbs and the runner's own tools beside them (the ledger repairs) alike (Q-LR); the act, the ask,
the check, the claim, the recording and the refusals of §8 to §19 are all in `node-tools/internal/approval` (Q-PK).
*How it is checked:* the code review reads that the gate sits on every handler, before the bundle, and that no path
to a bundle or a runner's own tool passes around it; unit, a row of §22's table: a runner's own tool marked in a
fixture is refused unasked, never run.
## Marked first
21. **`node-login-shell.execute` is marked in its seat's definition.** mesh-contracts' `seats/node-login-shell/seat.json`
marks the verb `execute` with `needs-approval`; the zsh module's manifest (mesh-catalog `modules/zsh`), which holds
the seat, gains `operator-channel` in `uses`. The control-node keeps withholding `execute`, and whether it serves
it again under the mark stays the operator's setting. *Why:* ADR 0286 §44, §45: the reads it carried have their
tools on every node (the seat `node-reads` of issue 418, resolved 2026-10-12, which `node-files` replaces whole, ADR
0394 §9). *How it is checked:* live, as the acceptance says.
- **The forge's two acts on a trunk's protection are marked.** The forge module (mesh-catalog `modules/gitea`)
gains a tool `gitea_branch_protection_loosen`, marked in its `needs-approval`, taking `owner`, `repo`, `branch`,
`act` (`remove-protection` or `allow-force-push`) and `why` (Q-FG), which alone performs the two acts
`gitea_branch_protection_set_full` refuses today: removing a trunk's protection, and allowing a force-push onto
a trunk; `gitea_branch_protection_set_full` keeps refusing both and its refusal names the new tool. The manifest
gains `operator-channel` in `uses`. No other tool or verb is marked in this milestone. *Why:* the forge's own
refusal says these two acts are the operator's and waited for this mark (issue 417, 2026-10-28); a mark is on a
whole tool (ADR 0286 §1), so the acts move to a tool of their own, as ADR 0286 §48 splits a tool into an unmarked
and a marked form, and tightening a protection stays unasked; the operator, 2026-11-11: "Mark them in G". The
principle: loosening a trunk's protection runs only on the operator's own warrant for that one act, never on a
session's word. Its bound: a process that reads the forge module's own credential speaks to the forge directly
and goes around it, the same known limit as issue 1966 note 193; the prepare reads that credential's mode and
owner on the forge's node with `<node>/node-files.stat` (§21a) and writes them in the milestone file. *How it
is checked:* the forge module's tool test: `set_full` refuses both acts naming the new tool; the new tool
performs each; and live, as the acceptance says.
21a. **`node-files.stat` answers a mode under a directory the account cannot enter.** The seat `node-files`' holder
(mesh-catalog `modules/mesh-files`) answers `stat` of a path its account cannot reach through `sudo -n` with its
fixed `/usr/bin/stat`, as its writes already go (ADR 0394 §2), answering the path's mode, owner, group and kind and
nothing of its content; the secret places ADR 0394 §7 names are stated, never read. Its README's "not yet through
sudo" loses `stat`. *Why:* the prepare could not read the forge credential's mode (step 0b); ADR 0394 §2 has the
holder use sudo where it must. The lead's handover named the retired `node-reads` (ADR 0394 §9), whose successor this
is. *How it is checked:* unit, a row in mesh-files' `Stat` test: a file under a directory of mode 0700 owned by
another account answers its mode and owner through sudo, with no content.
## Tests, delivery and going back
22. **The tests.** The tool runner's tests in `node-tools/internal/approval` are one table-driven unit test of the
refusals and answers (§9, §12, §14, §15, §17, §19) and one of the marks (§5), on the tool runner's test bus
(`meshtest`), the router's record and the warrant written there as the router writes them; and one integration
test that starts the runner against the real bus, with no store, with a fixture marked module: a call, the
operator's Approve written as the router's record, the call again, the run, the hand-act, `mesh_approvals`, and a
restart between the run and `warranted`'s answer. The client's `KeyPrefix` (§10) has a row in mesh-contracts'
client test: every key of its own `State` written and read carries it, an empty prefix writes today's keys, and
with a prefix set `Where` still reads `<module>.<id>`. The mesh MCP server's `TestMeshWaitIsAnnouncedAmongSeven` counts eight
tools with `mesh_approvals` (Q-T). In milestone G a module ends with no more tests than it starts with (design 47 G §419, §626): these tests replace
existing ones, and every test deleted counts. *Why:* the operator's test
budget (one whole test over many small ones). *How it is checked:* the check counts each module's tests against
its budget.
23. **In G's delivery group, ordered by the module graph.** The members are pushed and checked in design 47 milestone
G's group, with design 34's milestone every-event. The orders are the module graph's: the indexer's manifest
validator and catalog graph entry that read `needs-approval` before any manifest or seat definition carrying it (a
reader edge, design 47 G §89, whose list covers `needs-approval`; Q-RE); and the grants part that composes the deny of §6 before the seat definition marking
`execute` (an edge of the seat definition's readers), so no machine serves `execute` unasked between the two; and
the grants part serving `withheld` before a node-engine calls it (a served-verb edge, §6).
*Why:* the operator, 2026-11-11, "Build the gate in G"; the operator, 2026-11-03, a change is designed as it should
be and its delivery is the delivery system's. *How it is checked:* the delivery plan names the three edges.
24. **Going back is going forward.** A fault met after the delivery is fixed by a new change delivered the same way.
Reverting instead reverts G's whole group in the reverse of §23's order: the seat definition's mark first, then the
grants part, then the validator. The `approvals` state is then declared by no running module: it is an orphan
`orphans` lists, its keys aging out within 48 hours, dropped through ADR 0595's `drop` on the operator's word.
*Why:* design 47 G's revert rule; ADR 0595 §4, §5. *How it is checked:* the delivery plan of a revert names the
order.
## Acceptance
- **A marked call is asked on the phone, does nothing, and runs once on the Approve** (live, read by tool call):
`node-login-shell.execute` on a node other than the control-node answers "Not run", the ask is on
`operator-channel.asks`, and after the operator's Approve the same call runs once, a third identical call is refused
as already run, and `mesh-hand-acts.hand-acts` names the warranted act (§10, §12, §14 to §16, §21).
- **The forge's loosening is asked, and the protection is put back** (live, read by tool call): on a scratch repository
`acceptance-approval-mark`, created by the acceptance through the forge's `gitea_create_repo` and kept afterwards,
never deleted, with full protection set on its trunk by `gitea_branch_protection_set_full`,
`gitea_branch_protection_loosen` removing the trunk's protection answers "Not run" and asks on the phone; after the
Approve the same call runs once; then `gitea_branch_protection_set_full` sets the full protection again and
`gitea_branch_protection_get` reads it back as it was (§21).
- **A dropped mark still asks while the old bundle runs** (live, read by tool call): a delivery that drops a module's
mark asks a call of that tool until the node runs the new bundle, read with `mesh_approvals` (§5).
- **No machine serves `execute` unasked** (live, read by tool call): before this acceptance passes, on every node
`execute` is either asked as above or absent, read with each node's `node-engine.composition` and
`mesh-grants.grants_user_list`'s `withheld` (§6).
- **A marked wait as the operator sees it** (live): design 34 every-event's acceptance of a wait on a mail event.
- **The mark cannot be lost on the way** (unit): §5's and §8's tables pass; a bundle carries no mark.
- **It fails closed** (unit): §19's rows pass.
- **The validator refuses a mark nothing enforces** (unit): §4's rows pass.
@@ -1,9 +1,12 @@
---
layer: to-be
status: in-progress
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog]
updated: 2026-10-13
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog, mesh-contracts, mesh-delivery]
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0347"
- "ADR 0345"
- "ADR 0344"
@@ -15,6 +18,11 @@ decisions:
- "ADR 0253"
- "ADR 0234"
- "ADR 0227"
- "ADR 0627"
- "ADR 0597"
- "ADR 0595"
- "ADR 0310"
- "ADR 0268"
---
# 46 — The conversation with the operator
@@ -221,7 +229,20 @@ when the title and the note alone do not fit, and says that too.
### Limits, batching, history
- An asker holds **at most three open asks**; a fourth is refused in words.
- An asker holds **at most three open asks**; a fourth is refused in words. *Amended 2026-11-10, ADR 0666 §12, from the
delivery of design 47's milestone G:* **no ask is hidden, and the newest is never held back.** An asker still has at
most three asks offered at once, but a fourth is offered, not refused: the oldest of its offered asks is held back
instead, kept open with its answer tickets withdrawn, its message edited to say it waits behind newer asks of the
same asker, and offered again, with new tickets, as soon as one of the three ends. An ask with the same `about` as an
open one of the same asker replaces it (§10), so a held-back ask is never one a newer ask already stands for. `asks`
lists each asker's held-back asks with since when, and the router's tool `ask-again` offers one now, holding back the
oldest offered one in its place. A Decline ends an ask like any answer, and its asker may ask again at once: the
pipeline runner's verb `ask-again` asks a declined `approval` step so (design 47, *Approval is a step*). Why the
oldest: the newest is what the asker needs now, and the oldest has had the operator's eye longest; holding it back
edits its message rather than removing it, so the operator still sees it waits. *How it is checked:* the router's
whole-module test offers a fourth ask and holds back the oldest, offers the held-back one again when one ends,
replaces an open ask by a newer one with the same `about`, and offers an ask after a Decline; live, a fourth ask and
a declined approval each reach the operator (design 47, test 71).
- Asks count against the router's hourly cap, as messages do; answers do not.
- Asks to one channel within the burst window go out together under a heading ("3 questions waiting"),
each as its own message, answerable and editable alone.
@@ -424,7 +445,8 @@ what it does in plain words, its assurance level (`acknowledge`, `approve`, `des
answer (`operator`); an expiry; what happens on expiry, in words; and optionally what it is about (a
condition's key), so a newer ask about it replaces the older. It is refused at once, on its asker's
`decided` subject, when its words break a rule, when no ready channel can carry one of its options' levels,
or when the asker already has three open.
or, until the delivery of design 47's milestone G, when the asker already has three open; from that delivery a fourth
is offered and the asker's oldest held back (§6, *Limits, batching, history*; ADR 0666 §12).
**A warrant** carries the ask's id and asker, the option, `by` ("the operator, via telegram, user id
verified"), the channel module, the proofs, the level met and when. No secret. An ask that ends without one
@@ -545,43 +567,27 @@ said on the away channel, with its why.
### A call that needs the operator's approval
*Added 2026-10-10, ADR 0286.*
A module's manifest marks a tool, or a seat verb it serves, as **`needs-approval`**; a seat's definition may mark
a verb for every holder. The controller issues the mark in the runner's membership, never from the bundle, and
withholds a marked tool from a runner that does not declare `approval-mark/1`, by leaving its subject out of that
runner's bus permissions.
*Added 2026-10-10, ADR 0286; amended 2026-11-11 to the mesh as built and as design 47's milestone G leaves it.*
A module's manifest marks a tool, or a seat verb it serves, in **`needs-approval`**; a seat's definition may mark
a verb for every holder. The tool runner reads the marks from the catalog half on the bus (seat `mesh-catalog`), never from
the bundle, and on a machine whose tool runner does not state that it honours the mark the grants part withholds every
marked subject from its account.
```
agent ──call──► tool runner (serving node) ──ask, under the module's name──► router ──► Telegram
agent ──call──► tool runner (serving node) ──ask, under the module's name──► seat operator-channel ──► Telegram
▲ │ "Not run: asked, ask <id>" │ Approve
│ ▼ ▼
└──same call──► runner reads the router's record ◄────────────── warrant (decided.<module>)
│ Warrant.For + Option.Performs on the act; within 30 min; claimed once
│ For + Performs on the act; within 30 min; claimed once in its own state
▼
runs the call once ──► mesh-controller.warranted (hand-act log)
runs the call once ──► seat mesh-hand-acts, warranted
```
- **The act** is the address, the node, the module and tool, the bundle's build and every argument given, digested
with the shared library's `ActDigest` (in Go and TypeScript, one set of vectors).
- **The first call asks and does nothing.** One ask per act (its `about` is the act's digest): "Run <tool> on
<node>?", the tool's first line, every argument whole where it may leave the mesh or in its shape with
‹path›, ‹address› or ‹withheld› and "Part shown" (whole when at most 2000 bytes and 40 lines, else the
first 20 lines and 1200 bytes), the fingerprint, and where the whole is read (the runner's
`approvals` on that node). Approve and Decline, both at approve; expiry 60 minutes; never a default. The call
answers as an error: "Not run", with the ask's id, that the call runs within 30 minutes of the approval, and
when the ask expires.
- **The same call again** runs once on Approve, read from the router's record, within 30 minutes of the choice,
claimed in the node's own file (kept 48 hours, one writer per node); the runner judges the newest ask it keeps
for the act, and after "already run", "expired" or "approved too long ago" the next identical call asks anew;
then `warranted` records the hand-act. Anything else is
refused and said: waiting, declined (not asked again for 10 minutes), expired, already run, or the router's
refusal.
- **Fail closed:** a secret's shape in an argument or more than 16 KiB of arguments, no operator channel, a
fourth open ask, an unpublished ask, an unreadable router record, node file or mark: nothing runs.
- **The controller** enforces the same mark on its own verbs, asking as `mesh-controller`.
- **Marked first:** `node-login-shell.execute` on every node once the reads it carried have tools (issue 418).
`mesh-delivery.release` waits until a warrant the controller holds passes the mark of a verb it calls (issue
417), so a Release on a condition is not asked twice.
The first call asks and does nothing; the same call made again within 30 minutes of the operator's Approve runs once;
everything else is refused and said, and anything the check cannot read runs nothing. `node-login-shell.execute` is
marked first. Every line of it is in milestone 7's part,
[`46-milestones/7-approval-mark.md`](46-milestones/7-approval-mark.md), built and delivered with design 47's milestone
G (the operator, 2026-11-11).
### The hand-act
@@ -696,12 +702,13 @@ Each phase ends at its own *done when*. Owning repositories from [`repos.md`](..
|---|---|---|---|
| **1 — Fix the first holder in place** | D1–D4 in the output seat's holder and the watcher, then D5–D10; no change of shape. The operator then makes the two bots and the mesh is given their values through the controller | mesh-catalog | the holder's tests for D1–D4 pass; a live test message reaches the phone from both the holder and the watcher |
| **2 — The desk's actions** | `node-notifier.send` gains actions; the dunst holder emits the chosen one; the launcher's prompt returns typed text; the screen-lock holder emits lock and idle changes | mesh-catalog | a notification with two actions returns the chosen token as an event; locking emits an event |
| **3 — The seats, and the router** | the kinded bench in the seat set; `kind` and `capabilities` on a claim; `channel-capabilities/1` and its contract tests; publishing on a seat's event subjects and its grant; `channel` and `intake` seats; the output seat's holder becomes the router and holds `channel/desktop` and `intake/desktop`; presence as current state; the operator's identity list and identity check in the controller, the router's `trusted` stamping and untrusted input (§4); the content rule allowing machine names, references and `detail`, refusals naming the offending part (§9) | mesh-controller (seat set, claim fields, registration refusals, grants, the identity list and check); mesh-sdk and mesh-tools (seat-event publishing in the shared library and the tool runner); mesh-catalog (the seats' definitions, the router) | the catalogue refuses an unknown capability and a second holder of one kind; a message is routed to the desk by capability and context; an envelope from an identity not on the list is re-emitted untrusted; a path is refused to its sender by name, and travels as a reference the desk opens |
| **3 — The seats, and the router** | the kinded bench in the seat list; `kind` and `capabilities` on a claim; `channel-capabilities/1` and its contract tests; publishing on a seat's event subjects and its grant; `channel` and `intake` seats; the output seat's holder becomes the router and holds `channel/desktop` and `intake/desktop`; presence as current state; the operator's identity list and identity check in the controller, the router's `trusted` stamping and untrusted input (§4); the content rule allowing machine names, references and `detail`, refusals naming the offending part (§9) | mesh-controller (seat list, claim fields, registration refusals, grants, the identity list and check); mesh-sdk and mesh-tools (seat-event publishing in the shared library and the tool runner); mesh-catalog (the seats' definitions, the router) | the catalog refuses an unknown capability and a second holder of one kind; a message is routed to the desk by capability and context; an envelope from an identity not on the list is re-emitted untrusted; a path is refused to its sender by name, and travels as a reference the desk opens |
| **4 — Asks, and operator messages** | `ask`, `ask cancel`, `asks`, `answer`; the events; kinds, life, limits, batching, history; escalation; addressing, the register of agents with kept messages, the responder; the untrusted-input rule in every agent module's instructions (§5) | mesh-catalog | the router tests of ADR 0234 pass; live: an agent's question answered at the desk, and with the desk locked, on the phone; `@mesh status` answered; a message to a stopped agent kept and said |
| **5a — Asks on the bus** (ADR 0259) | the kinded bench, caller-named verbs, `proof` subjects and the records users read (controller); a bundle's seat traffic, compare-and-set state and its own account (tool runner); the asker's and channel's types (shared library); the router's asks, tokens, identities, judging, warrants, expiry and linking with its hour (messenger); `telegram` and `desk-channel`; the controller asking for its conditions' actions and acting on warrants | mesh-controller, mesh-tools, mesh-sdk, mesh-catalog | a held delivery released from the phone; the hand-act reads "the operator, via telegram, user id verified"; a desk Silence goes through an ask |
| **5 — Authorise, with TOTP** | `authorises` in the verb table; the router's checks for the code; direct calls refused, break-glass; `retire approve` takes `expect`; the hand-act fields; the TOTP seed as the router's state, enrolled at a terminal with ten recovery codes; `factor recover`, `factor status`, the local-only break-glass enrolment; no P2 tier before recovery codes exist; optional key enrolment; the self-check probe for the away channel; agents' grants lose the authorising verbs | mesh-controller; mesh-catalog (agents' grants, the desk's code prompt) | one controller test per refusal passes; at the desk, approve needs a code; a recovery drill re-enrols with a recovery code, and is announced |
| **6 — Telegram live** | the `telegram` module (built in 5a) with replies and forced-reply codes, under its own account on the control node; the operator's two bots configured; the watcher assigned off the control node; the dead-man ping | mesh-catalog | the self-check says the away channel carries every tier; rehearsals: an approve and a destroy answered on the phone, with the hand-acts read |
| **7 — A call that needs approval** (ADR 0286) | `needs-approval` in the manifest and the seat set, issued in the membership, withheld from a runner without `approval-mark/1` in its bus permissions (controller); the act's digest in TypeScript beside Go, with shared vectors (shared library); the check, the ask, the runner's `approvals`, the node's used file and `warranted` (tool runner); the controller's own verbs; `execute` marked (the controller's seat set) once issue 418's reads exist | mesh-controller, mesh-sdk, mesh-tools, mesh-catalog | a marked call on a node is asked on the phone, does nothing, runs once when made again after Approve, is refused the third time and asked again, and `hand-acts` shows the warrant |
| **7 — A call that needs approval** (ADR 0286), delivered with design 47's milestone G | the mark in the manifest and the seat definition, read by the tool runner from the bus; the manifest validator's refusals; the capability `approval-mark/1`; the act, the ask, the check, the claim in the runner's own state, `warranted` and `mesh_approvals`; `execute` marked. Its part is [`46-milestones/7-approval-mark.md`](46-milestones/7-approval-mark.md) | mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog | its part's acceptance |
| **8 — No ask hidden** (ADR 0666 §12), delivered with design 47's milestone G | a fourth ask offered and the asker's oldest held back, offered again when one ends; `asks` lists the held-back asks; the router's tool `ask-again`; the pipeline runner's `approval` asks and their `about` | mesh-catalog (the router) | a fourth ask and a declined approval each reach the operator; `asks` shows the held-back one |
**Switching phase 5a on: the operator's steps, in order** (amended 2026-10-09, ADR 0259). Each line is typed with
`nox` (`mesh-cli`) on the control-node, from the operator's own account, which is the controller's terminal
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,419 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0664"
- "ADR 0661"
- "ADR 0656"
- "ADR 0633"
- "ADR 0619"
- "ADR 0614"
- "ADR 0596"
- "ADR 0595"
- "ADR 0593"
- "ADR 0584"
- "ADR 0573"
- "ADR 0567"
- "ADR 0564"
- "ADR 0548"
- "ADR 0448"
- "ADR 0429"
- "ADR 0398"
- "ADR 0363"
- "ADR 0283"
---
# 47, milestone G: pipelines per module: registration, watched branches, steps and policy, what holds a delivery, approval, groups, dependencies, a publish
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
## Pipelines per module
*ADR 0666, ADR 0667; ADR 0595, ADR 0596, ADR 0448 §2, §4, ADR 0548 §5, ADR 0564 §2, §4, ADR 0283, ADR 0656 §4, §6, ADR 0633 §5,
ADR 0664 §4, ADR 0398 §2, ADR 0593 §9; research 086, Direction.*
§1 From milestone G's delivery. A change of any module, the delivery system's own parts included, goes from a commit to
the machines through that module's **pipeline**: the pipeline steps its **policy** names, run for each commit or group
on each of the module's **watched branches** by the seat `mesh-pipeline-runner`, ordered by the catalog graph's edges
between artifacts, with no hand act (ADR 0666 §1). Milestone G is the one milestone that builds it, beside the two
graphs, the revision view and the delivery record (ADR 0667 §1). Until G's delivery the sections this one replaces
(*Checks*, *A try*, *The Approve and the merge*, the state table and the trunk-only want) hold as written, and they
check and deliver milestone G itself (ADR 0667 §2); each of them says so in a line of its own.
```
a session ──merge-pull (seat git)──► forge ──commit on a watched branch──► forge bridge ──► indexer (mesh-catalog)
│ registrations; a catalog graph
│ per watched branch; the revision
│ validated before take-in
▼ take-in.taken
pipeline runner (mesh-pipeline-runner) ── asks each pipeline step on the serving seat's work queue, hears step-ended:
│ lint, unit tests, integration tests ──► the build seats (node-build-go, node-build-node, node-build-image)
│ composition ──────────────────────────► the assigner (mesh-wants)
│ code review ──────────────────────────► the runner's own verb `review`, from the reviewer's bus account
│ approval ─────────────────────────────► operator-channel: accepted kind ask, then decided
▼ pipeline-ended (every step's result), keyed by source revision
assigner ──► wants from a revision whose required steps passed, on the watched branch a placement chose
gate ──► the delivery record keeps the step and pipeline events
```
### A module's registration
- §2 **What it holds.** The seat `mesh-catalog` keeps each module's **registration**: its git origin, the path of its
manifest, its watched branches and its policy, on the bus, every version with who changed it and the warrant of the
operator's Approve (ADR 0666 §2). The indexer is a foundation part, so the registrations are kept in its old sections
(ADR 0619 §1 to §3, ADR 0666 §4): one key per module in its old `state` section `registrations`, and every version on
its old `streams` section's stream `registration-history`, kept with no age limit.
The catalog graph is derived from the registrations and the manifests they point to; the mesh knows modules, and a
repository only as a module's git origin.
- §3 **Changing one is a trusted act.** The seat's verbs `register` (a module whole), `policy` (one module's step
statuses, per artifact), `watch` and `unwatch` (add or remove a watched branch) each raise an authorising ask of tier
approve through the seat `operator-channel` (design 46 §10), naming every value the change moves, one line each
("`mesh-grants:program` unit tests: `required` → `bypass`"), and write the version only on its warrant; a Decline or
an expiry writes nothing (ADR 0666 §2). Until a record binds each forge account to its bus identity, `register` and
`policy` refuse code review as a `required` step, naming why, so no review gates while its author cannot be told
(ADR 0666 §22; issue 1966, note 157). A manifest moved to another path is the one manifest change that changes the
registration; any other manifest change is a commit, judged at its revision.
- §4 **Said and read.** Each written change is said as the seat's event `registration-changed` (the module, the version,
what moved); the verb `registrations` answers every registration, or one with its history. The indexer derives the
modules it keeps from them, and the forge bridge its watched branches, each by a first view of `registrations` and
then that event, never by reading the ledger (ADR 0666 §3, ADR 0596).
- §5 **An unprotected branch is refused.** `register` and `watch` read a branch's protection through the seat `git`'s read
verb `branch` (its head, and whether a rule refuses a direct push and admits only merges), and refuse a watched branch
the forge does not protect, naming it (ADR 0666 §3).
- §6 **The foundation parts are the manifest validator's list.** The manifest validator's list (ADR 0619 §3) holds
exactly the foundation parts, ADR 0619 §1's, the runner, which joins them (ADR 0666 §4), and the forge and image
registry modules (§584); the checker leaves the
list when it is removed (*How milestone G itself is checked and delivered*). A registration holds no foundation
value. How it is checked: a unit test of the manifest validator refuses a template in the manifest of every part on
its list, the runner's included, and judges every other part's manifest as any part's.
- §7 **No part fixes a name: a guideline the code review reads.** (§507) Every branch, repository and module name a part
needs is read from the registrations and the catalog graph, never fixed in its code. This is a guideline, not a
step: the code review reads it, and no lint step scans for names (the operator, 2026-11-11 about 00:05 CET: the scan was a flaky check; where
guidelines live is research 087's). It departs from ADR 0666 §5's refusal by the lint step, by the operator's choice,
and a later record brings it in line. The names G's own work removes (about 39 in 9 parts, found on 2026-11-09,
research 086, Findings) are removed by its subtasks, each naming the name it removes.
- §8 **The first registrations are one ask.** At its first start on G's build the indexer, holding no registration,
proposes one registration for every module its trunk revision holds, from what it took in (the origin and path of
each), its watched branch the repository's default branch read through the seat `git`, and the statuses the check
judged until then: lint, unit tests, integration tests and composition `required`, code review `optional`, approval
`bypass`. Each step the artifact's build seat answers `steps` with none is `bypass` instead, so no module is held by
a `required` step it has nothing for; the indexer asks `steps` of each build seat as a kept call (ADR 0668 §1, §2),
and the seed waits until every build seat has answered, never reading an unanswered seat as "none": a seat still
unanswered after five minutes is the indexer's `call-unanswered`, naming the seat (ADR 0668 §3), and the seat's new
build serving `steps` is an edge of kind 6 before the seed. A module whose default branch the forge does not protect
is left out of the seed and named in the ask, as `register` would refuse it (ADR 0666 §3). It raises the rest as one
ask naming every registration, as the seat `mesh-module-assigner`'s `adopt` proposes placement whole (ADR 0567).
While the ask is open the indexer's finding `seed-waits`, naming the ask, is raised by the condition keeper as
waiting on the operator (ADR 0283) and cleared by the warrant; a Decline or an expiry leaves the seed unwritten, says
so and keeps `seed-waits`, naming `indexer_seed_again`, which raises the ask again, recorded in the hand-act log.
Until that Approve the runner starts no delivering pipeline and says why, so the operator is told of this one ask
before G's delivery starts. Why: the design's choices under ADR 0666 §3, §7 and ADR 0668 (round 1 of the review by
architectural domain, 2026-11-10). How it is checked: whole (indexer), a build seat that never answers `steps`
holds the seed and raises `call-unanswered`, one that answers none gives `bypass`; an unprotected default branch is
left out and named; `seed-waits` is raised while the ask is open and cleared by the warrant.
- §9 **Kept for the life of the mesh**, never derived again (research 086, choice 25): the history stream's cap is 64 MiB.
About 120 registrations of about 1 KiB, and an estimated change a week each (a guess, not a measure), grow about 6 MiB
a year, so the cap holds about ten years; past 80 % the indexer says `filling`, as every store of *Two graphs, one
source of truth* does.
- §10 **How it is checked.** Whole (indexer): an unprotected watched branch is refused, naming it; a policy change without
the warrant writes nothing, and a declined one writes nothing; a change from `required` to `bypass` asks, showing
that line; `registration-changed` is said and `registrations` answers the history; the first start proposes the seed
as one ask, and `indexer_seed_again` asks it again after a Decline and after an expiry; a policy making code review
`required` is refused. Tests 75, 68 and 83.
### Watched branches and the catalog graph per branch
- §11 **Only a watched branch starts a pipeline.** (§509) The forge bridge hears every commit of every watched branch the
registrations name, through the seat `git`'s event `branch-moved` (a branch's new head, from the forge's push
webhook, which a merge and an admitted push alike say), and takes the commit from the forge's own record by the seat's
verb `branch`, never from the event's body (ADR 0666 §6). One event for every way a branch moves, because a merge is
not the only one. `branch-moved` is kept on the seat's stream `SEAT_git_branch-moved`, which the bridge hears through
a durable consumer, so a move said while the bridge is down is heard when it starts; at its start it also reads each
watched branch's head the same way.
- §12 **A head with no pipeline is a finding.** (§497) A watched branch's head that starts no pipeline within five minutes is the
watchman's finding `pipeline-stands`, naming the branch and its head, raised by the condition keeper (ADR 0564 §2),
unless it waits as progress not owed: a member of a gathered group is governed by ADR 0633 §5's 30-minute bound, and
a merge behind a pending revision of the same branch by its 60-minute bound (ADR 0666 §24). The finding clears on the
runner's `pipeline-started` for that head.
- §13 **The catalog graph per watched branch.** The indexer keeps the catalog graph once per watched branch, from that
branch's manifests at each source revision (ADR 0666 §6): each revision names its branch, and the stores, caps and
pins of *Two graphs, one source of truth* hold per branch.
- §14 **A placement names the branch.** Each module of a placement key names the watched branch its node runs, by default the
registration's first, set by a placement proposal like every other placement change (ADR 0567, ADR 0666 §6). The
assigner computes each node's entries from the catalog graph of the branch its placement names, so a module watched on
`main` and `beta` runs `beta` only where a placement chose it.
- §15 **A source revision per watched branch.** The gate keeps one source revision per watched branch, `source.<branch>`,
in place of `source.main` (ADR 0429 §11 to §13 as ADR 0666 §6 changes them), in the old `state` section `source`
already holds, its key's layout being the only change. The gate is no foundation part: the bus module's delivery
waits on no act of the gate's, since the indexer takes a revision in from the forge bridge's stream and the assigner
writes wants from the runner's events, so the gate's kept data are as §613 has them, state by state (ADR 0666 §4), and serves it by the seat
`mesh-delivery`'s verb `source` with the branch, saying `source-moved` on each new revision; the indexer and the
node-engine's catch-up read it there, never the gate's state (ADR 0596). Each want names a build-input fingerprint
computed at one such revision.
- §16 **Validated before any take-in, on every branch.** Before it takes a revision in, on every watched branch, the running
indexer asks the manifest validator built from the revision's heads to judge it in the build seat's sandbox (the
ask `run`, *What ADR 0656 leaves to this design*). A revision it refuses stays pending under ADR 0633 §5's
60-minute bound, its hand acts and its condition `take-in-waits`; the way back is a revert, an ordinary merge
(ADR 0666 §6, §26). A revision on the branch the indexer's own placement chose is taken in only by the indexer it
names (ADR 0633 §1). This validation is no pipeline step and waits on no pipeline, so every delivering pipeline runs
on a revision already taken in.
- §17 **A branch whose protection was removed starts nothing.** (§497, §509) On every `branch-moved` the forge bridge reads the
branch's protection again; a move of a watched branch whose protection was removed starts no delivering pipeline, and
the forge bridge writes the finding `branch-unprotected`, naming the branch, which the condition keeper raises and
clears once the protection is back (ADR 0666 §6).
- §18 **A pending revision holds its own branch only.** A merge waits behind a pending revision only on the same watched
branch; other branches move on (ADR 0666 §6, which changes ADR 0633 §3).
- §19 **A refused new indexer holds its branch until a revert lands.** A merge of a new indexer whose own validator refuses
its revision has already landed when it is refused, since the validation runs before take-in and after the merge
(ADR 0666 §6). It stays pending, and every later merge on that watched branch waits behind it, said by
`take-in-waits` at the 60-minute bound, until a revert, an ordinary merge through its pipeline (ADR 0666 §26), lands
and is taken in; nothing takes a later merge in past it. This is the cost of validating every revision with the
validator built from its own heads, the design's reading of ADR 0666 §6, and the operator is told it with the
condition, which names the pending revision and the revert as the way on.
- §20 **How it is checked.** Whole (forge bridge): a commit of a branch no registration names starts nothing; a commit on a
watched branch is taken from the seat's `branch` read, not the event; a head missed while the bridge was stopped is
read at its start. Whole (indexer, assigner): a commit on `beta` gives a revision of the `beta` graph only and moves
only the want of the node whose placement chose `beta`; a revision whose own validator refuses it stays pending and
holds `beta` while `main` moves on. Whole (watchman): a head with no pipeline for five minutes raises
`pipeline-stands`, cleared by `pipeline-started` for that head, and a gathered member does not; a move said while the
bridge was stopped is heard from the seat's stream. Tests 64, 68, 76, 78 and 82.
### Pipeline steps and the policy
- §21 **The runner.** The seat `mesh-pipeline-runner`, held once for the mesh by the module `mesh-delivery-pipeline`,
runs each pipeline (ADR 0666 §7). It is a foundation part, since the bus module's own delivery waits on its pipeline
(ADR 0666 §4): its kept data are in old sections (ADR 0619 §2), it waits on no bus module process to start, and it
joins the manifest validator's list of foundation parts, which refuses its manifest asking a template (ADR 0666 §4,
which changes ADR 0619 §1 and §3). It reads the policy from the registrations, asks each step that is not `bypass` of
the seat that serves it, and says each step's start and end and the pipeline's end (*Health and going back*, below).
- §22 **The steps and who serves them.** Lint, unit tests and integration tests are the build seats' (`node-build-go`,
`node-build-node`, `node-build-image`): each gains the verb `steps`, which answers which pipeline steps an artifact
can run by its language's convention (as *The check from release 4* says the tests are found), and the accepted kind
`step` on its stream `check`, which runs one. Composition is the assigner's, an accepted kind `step` of the seat
`mesh-wants`. Code review comes in through the runner's own verb `review`. Approval is asked as the seat
`operator-channel`'s accepted kind `ask`, on its work queue by the runner's name (design 46 §10, *Asks, warrants and answers on the bus*;
the seat has no verb `ask`), and heard on its event `decided` (ADR 0666 §7, §10). A step with nothing to do ends at once as
`nothing to run`, never as passed.
- §23 **The policy.** (§553) Each step's status, per module and per artifact, is `required` (runs; holds the delivery until it
passes), `optional` (runs; a failure is shown and may be accepted) or `bypass` (nothing runs; said as bypassed),
kept in the registration and never in a manifest: the manifest validator refuses a manifest that names a status
(ADR 0666 §7; research 086, R3). A `required` step that ends `nothing to run` holds the delivery and names the step.
- §24 **Asked on a work queue, ended by an event.** The runner asks each step on the serving seat's work queue and hears
its end as that seat's event `step-ended` (the record's `step ended`), on
`mesh.seat.<seat>.event.step-ended.<node>`, the node that ran it in the subject and each holder's account granted only
its own node's subject, through a durable consumer of its own, so a step outlives a restart of either side (ADR 0666
§7, ADR 0363 §2). A build seat's holder restarted mid-step gets the step again from its work queue and finishes it
(ADR 0664 §1). A unit or integration test step says its catalog module's test counts in `step-ended`; `pipeline-ended` carries each
catalog module's counts against the test budget, which is counted per catalog module (at most 50 integration and
500 unit tests each), and `pipeline-ended` names any catalog module over it (ADR 0666 §8; the test budget, per
module, the operator 2026-11-11). A step's outcome is one of `passed`, `failed`, `nothing to run`,
`bypassed`, `carried` (with the pipeline it was carried from), `could not run` (naming the part not heard),
`waiting on <person>`, `accepted` (a failed `optional` step a person accepted, with who and why), `not asked` (an
`approval` step after a failed `required` step), `superseded` and `cancelled`.
- §25 **Carried, not run.** A step whose build-input fingerprint and policy did not change is carried by its package
digests, as ADR 0548 §5 holds, the runner making the carry instead of the checker: a test step runs only the packages
with no kept result for their digest, and a run on request's results are reused when the merged commit's inputs match
(ADR 0666 §8; research 086, choice 24). An `approval` step is never carried.
- §26 **Approval comes last.** The runner asks the `approval` step only once every other `required` step of the pipeline
passed, so the operator approves a commit the mesh already judged, the one place ADR 0661 §3 had the check gate the
ask. A failure after an approval is said, as a failed `optional` step is, and the approval stays.
- §27 **Kept on the bus, in old sections.** The runner keeps each pipeline's state in its old `state` section `pipelines`,
one key per pipeline keyed by its watched branch, source revision and module, written by compare-and-set so a holder
replaced mid-pipeline and its successor never both write one; its step log on its old `streams` section's stream
`step-log`; each step's output in its old `objects` section `step-outputs`, named in the log; and each package's
passing result per level in its old `state` section `kept`, under the digest of what its tests read (ADR 0666 §4, §8;
*The runner's kept data*, below). `kept` starts empty: the runner takes no verdict of the checker's, since a step's
result is taken only from the bus account of that step's seat holder (ADR 0666 §21), so the first pipelines after G
run each package's tests once and carry from then on (the design's choice, 2026-11-10).
- §28 **How it is checked.** Whole (runner): a step with unchanged fingerprint and policy is carried and says from where,
and an `approval` step is not; a module with no integration tests shows `nothing to run`, and a `required` unit-test
step on a module with no tests holds its delivery; a bypassed step is said; a runner restarted mid-step hears the
step's end and finishes the pipeline; two runners writing one pipeline key, one loses by compare-and-set; a test step's
counts are in `step-ended` and a catalog module over budget is named in `pipeline-ended` (§419). Whole (Go build module): `steps`
answers the steps of a fixture artifact, a `step` ask ends with `step-ended` on its own node's subject, one on another
node's subject is refused by the bus, and a build module restarted mid-step finishes it. Unit (manifest validator): a manifest naming a status is refused. Tests 65, 66, 73
and 88.
### What holds a delivery
- §29 **The assigner writes a want only from a passed pipeline.** The runner says each pipeline's end as `pipeline-ended`
(the record's `pipeline ended`), keyed by source revision, with every step's result. The assigner consumes it, and
writes a want only from a source revision whose every `required` step passed for the modules it moves, on the watched
branch a placement chose, by compare-and-set on a newer revision, so an older revision ending after a newer one
writes nothing (ADR 0666 §9). The compare-and-set is keyed per module and watched branch: it compares a revision only
with the last one written for the same module on the same watched branch, so a revision of `beta` ending after a
newer one of `main` writes its own want and refuses neither; a later `pipeline-ended` of the same revision (after a
passing `rerun`, below) is taken, since its end count is higher (the design's choice under ADR 0666 §9: source
revisions are kept per watched branch, `source.<branch>`, and two branches' revisions have no order between them). This is one more rule beside the assigner's others (ADR 0429 §6), checked per module.
From G's delivery it replaces the trunk-only want (ADR 0656 §2 as ADR 0666 §9 changes it). For a group's source
revision the runner says one `group-ended` once every member's modules have ended, and the assigner writes the
group's wants only on it: a member's own `pipeline-ended` writes nothing (ADR 0666 §9).
- §30 **A failed `optional` step holds nothing.** It is shown in `pipeline-ended`, in the delivery record and in the runner's
`show`; the operator or a session may accept it by the runner's verb `accept` (§394, §610), recorded in the hand-act log with who and why.
- §31 **A newer commit supersedes an older pipeline of a module whose fingerprint it changed.** On a newer commit of the
same watched branch the runner ends a module's older running pipeline as `superseded` only when the newer commit
changed that module's build-input fingerprint (ADR 0666 §9); a module the commit leaves as it was finishes its
pipeline. A newer publish of a package likewise supersedes the rebuild pipelines an older publish of it started. A
superseded pipeline's waiting asks are dropped by each build seat's `drop` (ADR 0664 §4), naming
the newer commit as the cause, so a cancelled pipeline leaves no ask on a stream (a gap G's prepare found: a cancelled
build withdrew only the builder's wait).
- §32 **A step nothing starts ends its pipeline.** A step whose prerequisite nothing starts (a seat with no holder, a
prerequisite step `bypass`) ends the pipeline refused within five minutes, naming the step and what it waits for,
never as a silent wait (research 086, R5); a step whose seat has no holder ends its pipeline failed, naming the seat
(ADR 0666 §17).
- §33 **A failed head is said to its author, then to the operator.** (§540) A `required` step that failed, or ended `could not
run`, on a watched branch's head is the runner's finding `pipeline-failed`, naming the module, the branch, the commit
and the step; the condition keeper raises it as the commit's author's for two hours and then the operator's, as ADR
0505 §5 and §6 have a delivery whose cure is its author's new head, and clears it when a newer revision of that branch
passes that step (ADR 0666 §24). The author is the forge's record of the commit, read through the seat `git`.
- §34 **The delivery record keeps every step.** The gate's delivery record consumes the step and pipeline events and keeps
them as long as the record (ADR 0666 §9; *The delivery record*).
- §35 **How it is checked.** Whole (assigner): a failed `required` step writes no want; a failed `optional` step holds
nothing; an older revision's `pipeline-ended` after a newer one's writes nothing, and a revision of one watched branch
ending after a newer one of another writes its own want; a group with one member passed and
another still running writes no want until `group-ended`. Whole (runner): a newer commit that changed a module's
fingerprint supersedes its older pipeline and drops its asks, and one that left it unchanged lets it finish; a step
with no holder ends the pipeline naming the seat within five minutes; a failed `required` step on a head raises
`pipeline-failed`, the author's for two hours and then the operator's, cleared by a newer passing revision. Tests 66,
67, 87, 89 and 98.
### Approval is a step, and a session merges a pull request
- §36 **No Approve per merge.** The operator's approval is the pipeline step `approval`, its status in each module's policy;
no Approve is asked per merge, and the delivery gate's merge turns and its Approve per merge go (ADR 0666 §10, which
changes ADR 0661 §3 to §5). The operator is asked only where a module's policy says `approval`, and for registrations
and policy changes.
- §37 **The approval is an ask of the runner's.** The runner raises an authorising ask of tier approve as the seat
`operator-channel`'s accepted kind `ask`, under its own name, its `about` the module and the watched branch only, so a newer commit's ask
replaces the older one's (design 46 §10). The warrant ends the step passed; an ask that is declined, expires or is
refused ends it `declined`, and one that is replaced ends it `superseded`. Only a declined `approval` step is asked
again, by the runner's verb `ask-again` (ADR 0666 §10). While it waits the runner
says `wait-held` waiting on the operator, never a stall (ADR 0283).
- §38 **A session merges a pull request.** Any session the tool runner proves, and the operator, merges by calling the seat
`git`'s verb `merge-pull` over the mesh, which merges with the forge tools' account (ADR 0398 §2) at the head the
caller names. It judges that this is still the pull request's head and that the branch's protection refuses a direct
push, the protection read again on every `branch-moved` of the branch, and it judges the head's runs by its modules'
policies: it refuses a head when any run at its build-input fingerprint failed a step a policy marks `required`, and
merges a head whose `required` steps passed or that has no run yet, a failed `optional` or a `bypass` step refusing
nothing; it never waits for a run and never asks an Approve (ADR 0666 §11). The forge keeps only its branch protection.
- §39 **Judged from its own state, never by a call.** (§487) The holder of the seat `git` consumes the runner's `step-ended`
through a durable consumer of its own and keeps, in its own state `failed-steps`, each failed `required` step by
module and build-input fingerprint, with the commits it was seen at and the run that failed it, until none of those
commits is a watched branch's head or the head an open pull request names (*The runner's kept data*); so a head
merges while the runner is down, judged on what it already heard (ADR 0666 §11, ADR 0596). A head's fingerprints are
those its runs named for its commit (kept as §475 says); a step that failed at that fingerprint in any run, a group's or another commit's
of the same content included, refuses it, unless a later run of that same step at that fingerprint, asked again by
the operator or the healers (the runner's `rerun`), passed: that pass clears the failure from `failed-steps` (ADR 0666
§11). The runner's `step-ended` says that the step was a rerun and the caller the runtime proved, and the forge module
clears a failure only for a rerun whose caller is the operator or a healer; a plain pass, or a rerun asked by anyone
else, clears nothing. A run still running is judged by the steps it has ended, since the merge never waits. A refusal names the run, the module and the step, so the session knows what to fix. The gate merges nothing, and its forge merge token is
removed with the checker's buckets and bus account, after the first pipeline after G's delivery has delivered a change
end to end and on the operator's word (ADR 0667 §12; *How milestone G itself is checked and delivered*); the forge bridge hears the landed commit as it hears every commit of a watched
branch.
- §40 **The operator-channel hides no ask.** It keeps design 46's three open asks per asker, never holds the newest back,
and replaces an open ask by the same asker's ask with the same `about` (ADR 0666 §12; design 46 §6).
- §41 **How it is checked.** Whole (forge module, forge bridge): a session's `merge-pull` at its head merges with no run yet,
and with a passed one, and asks no Approve; with the runner stopped a head merges; a head at whose fingerprint any
run failed a `required` step, a head that moved and a branch whose protection was removed since its last move are
refused, each naming why; a failed `optional` step refuses nothing. Whole (runner): an `approval` step is asked last,
a Decline and an expiry each end it `declined` and `ask-again` asks it once more, and a newer commit's ask replaces the
older and ends its step `superseded`.
Live: a declined approval and a fourth ask each reach the operator. Tests 71 and 91.
### Groups
- §42 **Formed from the branch name.** The gate forms a group from a shared branch name carrying its issue number, as it
does today without the fixed list of trunk names (ADR 0666 §13).
- §43 **A member reaches its group by landing.** A member has reached its group when its commit lands on its modules'
watched branch. The forge bridge sets the group on each landed member's commit, reading the members from the seat
`mesh-delivery`'s verb `groups`, and publishes `forge.group.merged` when that verb says every member has (ADR 0666 §13,
which changes ADR 0633 §4).
- §44 **One source revision, one pipeline per branch.** The indexer keeps landed members pending until the group's end and
takes them in together as one source revision of that watched branch, saying `take-in.taken`; the runner starts the
group's one pipeline per watched branch on that event, and a member of a gathered group starts no pipeline of its own
(ADR 0666 §13).
When one member's `required` step fails, none of the group's modules is delivered (research 086, R5).
- §45 **A group that never completes is said.** By ADR 0633 §5's 30-minute bound and its condition; `bridge_group_end_again`
says a group's end again from the gate's group verb, never the gate's `merges` state (ADR 0666 §13, which changes ADR
0633 §5).
- §46 **How it is checked.** Whole (indexer, gate, forge bridge): a group of three repositories, its own packages included,
is one source revision and one pipeline; one member's failed `required` step delivers none; a group with a member that
never lands raises `take-in-waits` at 30 minutes. Tests 67 and 81.
### Dependencies between artifacts
- §47 **Six kinds of edge between artifact references.** The catalog graph's edges run between **artifact references**,
`<module>:<artifact>`, such as `mesh-grants:program`, of six kinds (ADR 0666 §14):
| Kind | B before A | Derived from |
|---|---|---|
| 1 | B published before A builds | a package A's build source imports (ADR 0573 §3) |
| 2 | B published before A delivers | an artifact A's manifest fetches by reference at its start |
| 3 | B delivered before A builds | a build module A is built by, and the toolchain its agreed version names (ADR 0584 §1) |
| 4 | B delivered before A delivers | a provision A requires and B provides, bus state among them (ADR 0614 §5), and a format's reader (below) |
| 5 | B has acted for A's new version | a seat's acting rule (*What ADR 0656 leaves to this design*): a grant, a credential, a verdict, bus state |
| 6 | B runs a version that serves A | a verb or a manifest word A uses and B serves or reads (ADR 0614 §2, §4), the build seats' `kept-artifact` among them |
Each kind is derived from what manifests and seat definitions already say, and declared per artifact in the manifest's
artifact entry, its field `needs`, only where nothing else says it. Today's edges run between modules, and only a
package edge names an artifact; at module level the tool runner and the grants part looked like a cycle, per artifact
they are a chain (`mesh-grants:program`, then `node-tools:runtime`, then `mesh-grants:tools`).
- §48 **Removal runs the other way.** When something goes away the order is reversed: a seat moved to a new holder has its
old holder released before the new one claims (ADR 0666 §14).
- §49 **The edges alone order.** (§569) The build and the delivery order by these edges alone; a cycle of delivery edges
(kinds 2, 4, 5, 6) alone is delivered as one step, and a cycle with any build edge (kind 1 or 3), whatever its other
edges, is refused, naming every artifact on it (ADR 0666 §14; research 086, R17). The cycle rule is the indexer's, in the catalog graph it keeps, so the checking
program's cycle rule goes with the checker.
- §50 **A format's reader first.** A format a running reader cannot read is preceded by its reader, an edge of kind 4 on
every watched branch (ADR 0666 §14): the indexer's manifest validator for a new field of a seat definition or a
manifest, and on each node the node-engine that reads a catalog graph entry's new fields before any manifest that writes them
(ADR 0667 §4).
- §51 **How it is checked.** Unit (indexer's graph): a chain per artifact where modules would cycle; two programs needing
each other's new verb are one step; two artifacts each needing the other's new package to build are refused, naming
both; a seat moved to a new holder releases the old first. Live: the tool runner and the grants part delivered in
the order `mesh-grants:program`, `node-tools:runtime`, `mesh-grants:tools`. Test 79.
### A publish rebuilds
- §52 **A version is reserved before the build and released when every artifact is published.** A module's version
is its manifest's `version`, `MAJOR.MINOR`, and a patch the build system gives: the next after the highest version
the catalog holds released for that `MAJOR.MINOR`, 0 for one it holds none of. The builder reserves it when the
module's build starts and passes it to the build of every artifact of the module (§370), so code and packed files
carry it; each artifact published is said as the event `package-published` of the builder's seat `mesh-builds`
(ADR 0666 §15), and the version is released only once every artifact of the module at it is published (§349); only the first watched branch releases (§371). A
failed publish leaves the version reserved, and `builder_publish_again` publishes the missing artifacts under the
same version. The seat `mesh-builds` is new, held once for the mesh, defined in `mesh-delivery-contracts`; the
builder held none until now, and its waits stay on its own stream in this milestone. Why: the operator, 2026-11-10
about 21:40 CET, a design choice of G's part (gaps items 22, 23 and 43). This reserves the number when the build
starts, where ADR 0666 §15 numbers a package at its publication and design 51 publishes a held digest under a new
number: a packed file carries its version, so it must be known before the build; the operator's choice, which a
later research brings the records in line with. How it is checked: test 102.
- §53 **Every dependent behind is rebuilt against it, on `package-published`.** The builder says `package-published`
for every package build of a watched branch: with its released version on the registration's first watched branch,
and on another watched branch by its digest alone, the build stored by digest with no version (§371). The runner,
which consumes it, asks the catalog's verb `outdated` for that package on that branch, a kept call (ADR 0668 §1,
§2), and starts a pipeline for each dependent by kind 1 whose recorded part digest differs from the published one:
the catalog compares (§375), the runner starts, and no other part rebuilds anything. A `package-published` whose
version is lower than the highest the catalog holds released for that module starts nothing (§382). Nothing else is
rebuilt, and a change to tests alone publishes nothing (ADR 0666 §15; research 086, R18). Why: ADR 0666 §15 has the
runner consume `package-published`; the digest comparison is the operator's (2026-11-10), and one event for both
branches is the design's choice, round 2 of the review by architectural domain, 2026-11-10.
- §54 **A second start writes nothing.** The runner starts a pipeline on two events, the indexer's `take-in.taken` (ADR
0666 §13) and `package-published` (ADR 0666 §15). When both reach it for the same source revision and module, the
second start writes nothing: the runner writes a pipeline's key (watched branch, source revision and module, *The
runner's kept data*) by compare-and-set on its absence, so one revision and module has one pipeline. The design's
choice under ADR 0666 §13 and §15: the key the runner already keeps is the one place a start is judged, so neither
event's producer needs to know of the other.
- §55 **Unread packages refuse.** A build that cannot read the published packages refuses and names the unread state
(ADR 0666 §15).
- §56 **How it is checked.** Whole (builder, indexer): a mesh-sdk merge changing one package publishes one version, and only
the artifacts importing that package rebuild; a merge changing only tests rebuilds nothing; a build whose packages
cannot be read refuses naming the state. Whole (runner): a `take-in.taken` and a `package-published` for the same
source revision and module start one pipeline, the second writing nothing. Tests 70, 80 and 101.
@@ -0,0 +1,275 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0668"
- "ADR 0666"
- "ADR 0660"
- "ADR 0635"
- "ADR 0625"
- "ADR 0596"
- "ADR 0595"
- "ADR 0584"
- "ADR 0564"
- "ADR 0363"
- "ADR 0240"
---
# 47, milestone G: the answers to develop's second batch of questions
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
## From develop, the second batch (2026-11-11)
*The operator, 2026-11-11 about 11:15 CET at lead-47's terminal: a design question that changes nothing the operator
sees, nobody's permissions (who may call, publish or read what) and no data kept or removed (an internal wire form, a
function's input, a helper's API, a contract field's name or shape, a package's place) is answered as a design line by
the design writer, from the design and its records, read by the four reviewers by architectural domain in one round,
and told to the operator in one line each; every other question goes to the operator (playbook 07, step 0b). §456 to
§458 are the operator's answers of 2026-11-11 at the terminal; §459 to §467 answer questions of that internal form,
each the design's choice with its reason, §468 to §479 those of the runner's, the build's, the indexer's, the contracts' and the forge's developers the same way. §480 and §481 answer what §463 and §467 left to the operator, and [G-11](G-11-develop-answers-review.md) the review of these lines.*
- §456 **A verb that names no `class` is a `read`, and nothing refuses one.** A verb whose definition names no `class`
takes the default of `read` (§391), whether it is on main or G adds it: the seat-definition reader, the seat
generator and the manifest validator refuse no verb for naming no `class`, and the validator says the default each
verb took, as §391 has it. The verbs whose class this design names (§394, §405, §422, §446, §447) name it in their
definitions. This amends §391, its "a verb G adds must name its `class`" and its check's refusing row. Why: the
operator, 2026-11-11 at the terminal (develop question D-12): a refusal would be "hard-coded logic for no good
reason". How it is checked: unit (definition reader), a row where a verb G adds with no `class` takes `read`'s
callers; unit (seat generator), a definition with a verb naming no `class` is generated.
- §457 **The put session's wire form.** (§489, §490, §518) §345's put session runs on the subject `<bundle call subject>.put.<call id>`.
Each chunk is one message whose body is the value's raw bytes, at most half the bus's maximum payload as the
connection reads it from the server, with the header `Mesh-Put-Seq` (a decimal from 1, one more each chunk) and, on
the last chunk alone, `Mesh-Put-Last: true`. The tool runner answers each chunk with `{seq}`, the sequence it took,
or `{error}`, and the bundle sends the next chunk only after a `{seq}`; the tool runner's answer to the last chunk is
the call's answer. A chunk whose `Mesh-Put-Seq` is not one more than the last taken is answered `{error}` naming
both, and the put is dropped as one not sealed (§345). A get of a value over the limit (§407) runs the same form on
`<bundle call subject>.get.<call id>`, the tool runner sending the chunks with the same headers and the bundle
answering each. Why: the operator, 2026-11-11 at the terminal (develop question D-13); the out-of-order refusal and
the get's mirror are the design's choice, one form both ways. How it is checked: whole (mesh-sdk, tool runner), as
§345, and a chunk sent out of order fails the put naming both sequences; unit (tool runner), a chunk over half the
maximum payload refused.
- §458 **How each artifact carries its version.** An image is tagged with the version `MAJOR.MINOR.PATCH` and labelled
`org.opencontainers.image.version` with it; a Go program is linked with `-ldflags -X main.meshVersion=<version>`
beside the existing `-X main.builtFor`; a Node program reads its version from its package.json `version`, which is
the version (§370). A build that carries no version (§371, §373) stamps none: the Go variable stays empty, and the
image and the package.json keep what they carry today. This completes §370's "its tag and label" and "reads it as
its own version". Why: the operator, 2026-11-11 at the terminal (develop question D-19); a build without a version
is §371's and §373's. How it is checked: whole (Go, TypeScript and image build modules), a built image's tag and
label, a Go program's `main.meshVersion` and a Node program's own version are the reserved version.
- §459 **`SeatsToPassOn`'s input.** (§536) mesh-sdk's `catalogue.SeatsToPassOn` takes the holdings on record, the
assignments (each a node, a module and its placement's watched branch), what the caller heard of each node and
module from the node-engine seat's `running` (a fingerprint, `stopped`, or nothing heard since its consumer began),
and a reader of entries the caller passes over its own view of the catalog half, which answers an entry by its
fingerprint (§443) and a module's entry on a watched branch's newest revision. The function resolves each
assignment's entry itself: by the heard fingerprint; with nothing heard, the module's entry on its placement's
branch, §199's one fallback, which no caller applies; a `stopped` module claims nothing on that node (§404). Where
the reader answers no entry for an assignment, no seat that assignment could claim passes and the holder on record
stays, as ADR 0660 §1 has it with no single claimant, and the answer says it, naming the node, the module and the
fingerprint. It answers the passes and what it says, as ADR 0660 §3. The function reads no bus. This amends §199
and §404. Why: ADR 0660 §3 has one rule both parts apply, and §199 the fallback in that one function; with the
entry read through a reader each part passes, the node-engine and the grants part feed it from their own views
(ADR 0596 §2), and the function stays a pure rule (develop question D-14). How it is checked: unit
(`SeatsToPassOn`), a table with a row per case: a heard fingerprint, nothing heard (the branch's entry taken), a
`stopped` holder passing no seat, and an entry not answered keeping the holder on record and saying it.
- §460 **An asker says whether its key is still open.** (§531) mesh-sdk's kept-call form takes, beside the ask's key, a
function `open` the asker passes, which reads the asker's own state and answers whether that key is still open, and,
where one exists, the event that replaces the ask; it asks again only while `open` answers yes and no replacing
event was heard. An error of `open` is written in the asker's journal naming the key, and the form asks again: a
kept call is one the callee takes once by a key (ADR 0668 §2), so asking again is safe, and not asking would be
silent. This amends §425: the replacing event is optional, `open` always given. Why: an ask with no replacing event
must still stop when its asker closed it (§351; develop question D-15). How it is checked: unit (kept-call form), a
key `open` answers closed is not asked again, a replaced one is not, and an `open` error is logged and asked again.
- §461 **mesh-sdk's finding writer.** (§485, §503) The writer is mesh-sdk's `go/views` writer and its TypeScript twin, mesh-sdk's
`src/views`, exported from its index as `views`, the same functions in both (§452). An observer makes it with: the
binding of its own state `findings`, its bus connection, and the subjects of `finding-written`, `finding-cleared`,
`findings-stated` and `findings-asked`, which the generated `mesh-conditions` contract's subject functions make from
the observer's module name and node (§452, §383), so the writer names no seat and no module; and the observer's
`observe` function. Its acts:
- `write`, a finding: writes the key `<scope>.<id>.<kind>` in `findings`, keeping the key's `since` where it
exists (§384), and says `finding-written` only when what the finding says changed (§383).
- `clear`, a finding's `scope`, `id` and `kind` and `why`: deletes the key and says `finding-cleared`.
- Every event is published awaiting the bus's acknowledgement, sent again for one minute when unacknowledged, then
written in the observer's journal as an error naming the finding (§383).
- `observe` is a function the observer passes, which answers the findings it observes now and its pending keys
(each with its `since` and its bound), or an error. The writer calls it at its start and on each ask, writes each
stated finding's key, deletes each key neither stated nor pending, and says `findings-stated` (§384), putting a
set larger than one bus message in the observer's sent store (§59, §407). An error of `observe` is written in the
journal and no set is said, so the keeper's `findings-unstated` follows (§390).
- The asks: the writer makes a consumer of its own on `findings-asked` that delivers only the asks published after
it is made, the statement at start answering any before (§390), and answers an ask that names no `module` or
names its own.
- Between the seat's contract and the writer pass only the subjects: an observer calls the generated
`mesh-conditions` contract's subject functions and hands their strings to the writer. mesh-contracts' bucket
adapter `meshconditionsclient/findings` goes in G, its callers in mesh-delivery making mesh-sdk's writer instead
in the same change; a client package of `mesh-conditions` keeps only what two callers would each write beside the
contract (ADR 0625 §4). A client package of any seat, `meshwatchdogsclient` among them, is an artifact of its
contracts module, as every package that module publishes is, so `go-meshwatchdogsclient` is one of the health and
repair contracts module's artifacts.
Why: §383, §384 and §390 decide the events and their order; the writer's form is the design's choice (develop
questions D-16 and C15), every observer stating through one writer. How it is checked: unit (writer, both languages), the
acts above each say their event with §439's fields, an ask naming another module is not answered, an error of `observe`
says no set; §383's and §384's checks.
- §462 **Where the bus state calls and their capabilities land.** (§488) The calls' names and shapes are in mesh-sdk's
`go/buscalls`, which holds only those (its package's own rule): §408's ledger calls are `mesh/ledger.fill` (one
ledger's history's fill against its cap, as `buscalls.Fill`, §587) and `mesh/ledger.remove` (one instance removed with its
operations and its snapshot, §155, its request and answer §587's), and §407's `mesh/objects.sent-get`. The capability each belongs to is listed
where the capabilities are today, in the tool runner in mesh-tools (its families of calls per capability): the two
ledger calls under `ledger-calls`, `mesh/objects.sent-get` under `bus-state-calls`. mesh-sdk gains no capability
list of families; it names the words (§622). This amends §347 ("mesh-sdk lists") and §408. Why: the tool runner states the capabilities of the calls it
answers (ADR 0595 §9) and the shapes alone are shared, so a module importing the calls rebuilds nothing of the tool
runner's (develop question D-17). How it is checked: unit (tool runner), its table of families names each new call
under its capability; unit (`buscalls`), each request and answer read.
- §463 **The input of the build seats' `steps`.** (§480) `steps` takes `artifact`, the key of `detected-steps` (§414), and
`fetch` to detect the artifact's steps, `again` (yes or no) to detect again, or `drop` (yes) with `why` (a non-empty
text) to delete the key; `fetch` is required unless `drop` is given, and `why` with `drop`. A drop answers `dropped`,
the artifact, or `error`, and is written in the hand-act log of the seat `mesh-hand-acts` with the caller the bus
proved for the call (never a field of the input), the artifact and `why`. It is defined once, in
`mesh-delivery-contracts`' part `build-step`, for the three build seats. It is not the verb `drop` of §338, which
drops asks. This completes §326. Why: the design's choice (develop question D-18). How it is checked: unit
(`build-step` contract), a `drop` without `why` and an ask without `fetch` or `drop` refused; whole (Go build
module), a drop deletes the key and is in the hand-act log with its caller. Its callers are §480's.
- §464 **The delivery SDK's two step packages, and where the build modules' shared code goes.** In
`modules/mesh-delivery-sdk` (after §427's move), `buildstep` holds what every delivery part reads of a step: the step
ask of a pipeline and its end, `step-ended` (§24), and the kept-artifact judgement of §271, as develop built them;
`steprun` (§420) holds what only the build modules run: the step run and the fetch and release (§327), the
regenerate-and-compare step and its out-of-step answer, and the per-node CPU share as `steprun/buildcpu`. The seat
generator moves with them, as the package `seatgen` of `mesh-delivery-sdk` with its command `cmd/seatgen`, and the
command `mesh-pack` as `cmd/mesh-pack` there. So all of mesh-build-go's `buildstep` Go module leaves mesh-catalog,
the TypeScript and image build modules import only the delivery SDK, and the delivery SDK requires nothing of
mesh-catalog. This amends §327's "a build-step package" and the main file's "`mesh-pack`, in mesh-sdk". Why: code
the build modules share is the delivery SDK's (ADR 0635 §4; §327), a module importing another module's package is a
seam no mesh path carries, and mesh-sdk takes only the runtime every module uses (ADR 0625 §9), which a generator is
not; `buildstep` stays light for the parts that only read the step's contract (develop questions D-20 and SD1). How
it is checked: a search of mesh-catalog finds no `buildstep`, `seatgen`, `buildcpu` or `mesh-pack` under
mesh-build-go; the delivery SDK's go.mod requires no mesh-catalog module; §327's search.
- §465 **A group run's build ask names each package member's build, and the build module reads it from the store.**
The ask's `fetch` (the part `build-step`'s Fetch) gains `builds`: a list of `{package, module, artifact, digest}`,
one per package member of §373, `package` the name in its ecosystem (the Go module path, the npm name), `module` and
`artifact` the member's catalog names and `digest` its build's `sha256:` digest; a package `builds` names is named in
neither `packages` nor `package`. The build module reads each from the artifact store through the provision it
already requires, `artifact-store`, at the reference it stores at,
`artifact-store://<module>/<artifact>/blobs/<digest>`, checks the bytes against the digest and lays them as §373
says. No registry seat gains a verb, and no grant changes: the store is read as every machine reads it. This
completes §373. Why: the digest is the builder's own fact (§373) and the store is addressed by digest already
(develop question BLD12). How it is checked: test 103; unit (`build-step` contract), `builds` read; whole (Go build
module), a fetched build whose bytes do not match its digest fails the step naming the member.
- §466 **A build names what it resolved.** Each built artifact of the build answer (BuiltArtifact, in the step's
result) gains `resolved`, a list of `{package, version, digest}`, one per mesh package the build resolved (one
that the catalog graph entry's packages name, the edges of kind 1), `digest` the `sha256:` digest of the package's
bytes as the build fetched them; ToolchainUsed gains `digest`, the building node's digest of its toolchain program
(§472), kind 3's entry, never the build module's own program. The builder carries both, per artifact,
in `package-published` as `resolved` and `toolchain`, from which the indexer writes §374's entries of kinds 1 and 3.
This completes §374. Why: §374 has the build module name each dependency with its version and digest in its step
key, and the indexer read no other part's state (ADR 0596 §2; develop question BLD13). How it is checked: unit
(`build-step` contract), both fields read; whole (Go build module, builder, indexer), a released version's kind-1
and kind-3 entries carry the digests the build named.
- §467 **A step key starts with its asker.** (§481, §483, §484) The build seats' state `steps` (in `node-build-go`, `node-build-node`
and `node-build-image`) keys a step `<asker>.<node>.<id>`: `<asker>` the asker the bus proves (§481: `runner`, `indexer`, `assigner`, `builder`), `<node>` the node that runs it, `<id>` the ask's id, so §206's read grant of each
asker is its own prefix and each holder writes only its node's keys. Keys of main's form `<node>.<id>` are §483's and §484's. This amends the three
seats' `steps` key form on main. Why: §206 and §125 decide that each asker reads only its own step keys (ADR 0363
§2); the key's form is what makes that grant a fixed prefix (develop question BLD14). How it is checked: the seats'
fixtures; whole (Go build module), a step of each asker keyed under its name.
- §468 **The runner's health check is its tool `runner_health`.** (§495) §76's check, run outside the process, is of kind
`tool` (ADR 0240) and names the runner's own tool `runner_health`, named as the runner's other tools are
(`runner_release`, `runner_catch_up`); it answers as every `tool` health check answers, healthy when the runner
answers `show` and each durable consumer §76 names is less than five minutes behind, and otherwise naming each one
behind. It is called as every module's health tool is, with no caller added. Why: the design's choice (develop
question Q-RN1); one prefix per module's tools. How it is checked: §79's check; the runner's manifest names it.
- §469 **An old `state` section declares its cap.** An old `state` section's entry takes `max-mib`, as its old
`objects` section's does, and mesh-sdk's `buscompose` makes the bucket with that many MiB as its maximum size; an
entry naming none has no cap, as on main, so no bucket of today changes. The runner's `pipelines` names 256 and its
`kept` 128 (§85); a write the bus refuses at the cap is said as the runner's `full` (§181), its read tools answering
each fill against the cap (§83). Why: §85's caps are the bus's, so a full store refuses loudly (§181) rather than
trusting the owner's own count; the field is the one the `objects` section has (develop question Q-RN2). How it is
checked: unit (manifest reader), `max-mib` on a state read and a value out of the objects section's bounds
refused; whole (runner), a write past a fixture's small cap says `full`.
- §470 **The step log is an old `logs` section, kept 100 days.** (§486) The runner's `step-log` is declared in its manifest
as an old `logs` section, with `max-age-days` 100 and `max-mib` 128, and is written and read onward by sequence
through mesh-sdk's kept log (its `Read` from a sequence), as §330 reads it; the seat `mesh-pipeline-runner`'s
definition keeps listing it, keyed by `<pipeline>`. A log already takes an age up to 3650 days and refuses no
entry it holds, so the design's 100 days stand, and mesh-sdk's bound on a stream's age (30 days) and the indexer's
rule that repeats it stay as on main. This amends §85's "old `streams` section" for `step-log`. Why: §330 reads the
log onward by sequence, which is what a kept log is, and the log section declares the age and cap §85 names with no
new word (develop questions Q-RN3 and Q-RN6). How it is checked: the catalog's validation at G's heads accepts the
runner's manifest; whole (runner), as §330.
- §471 **A second reader release carries §469's word.** (§494) The reader release of §340 and §409 is merged; a second
release of mesh-sdk's manifest reader, reading `max-mib` on an old `state` section, is merged ahead of G's check in
the same way, so the running checker's validator reads the runner's manifest (§340). Why: readers first (ADR 0667
§4); the running checker makes G's own check (ADR 0667 §2). How it is checked: unit rows in the release for the
word; G's check reads the runner's manifest.
- §472 **The toolchain part's digest.** (§492) Each build module states, in its `toolchains` key of each language beside
the version and the recipe, `digest`: the `sha256:` digest of the language's toolchain program as it runs it, the
program of the module that provides that language's toolchain (for Go, the module providing `go-toolchain`), never
the build module's own program. A build's ToolchainUsed carries its node's (§466). The builder's `toolchain-agreed`
carries `digests`, each digest the nodes reporting the agreed version report, with those nodes, in place of §376's
one digest; it is said only when the agreed version changes (ADR 0584 §1), and kind 3 is stale (§375) when the
agreed version differs from the one the release recorded, so a digest that differs between nodes of one version
rebuilds nothing and is only recorded. This amends §376 and §441 for kind 3. Why: ADR 0584 agrees a version, not a
digest, and a rebuild of every module of a language on a difference no record judges would spend the build
capacity for nothing (develop question BL8). How it is checked: unit (build-step contract), `digest` read; whole
(builder), a change of the agreed version says `toolchain-agreed` with the digests and a differing digest at one
version says nothing.
- §473 **The builder takes a candidate's heads from its revision view.** In place of the gate's `source` history,
which it reads today to find the trunk heads a candidate's base revision names, the builder takes each build's
commits from the catalog half of the revision view the build is for (§348), by `mesh-catalog`'s `revision`; a G
pipeline builds a commit as it is, so the builder asks no merge onto a base and sets no `base` in a fetch. Its watch
of the gate's `source` and the `reads` of it in its manifest go in the same change (§431). Why: ADR 0596 §2; one
source of a revision's commits (develop questions BL15 and BL9). How it is checked: the builder's account holds no
read of the gate's state; whole (builder), a run on request builds the commits its revision view names.
- §474 **A `*` entry before an event no contract defines keeps its meaning on main.** §386's form, one durable
consumer on each declaring module's stream, is taken only for `*.<event>` where `<event>` is defined by a seat's
definition or a contract in mesh-sdk; any other `*` entry the validator accepts on main, such as `model-usage`'s
`*.usage.*`, keeps its meaning of ADR 0363 §5, the shared stream EVENTS and never a module's own stream, and is
accepted as on main; `*.>` and `*` in part of an emitter's name stay refused. G's change does not rewrite
`model-usage`'s `consumes`, so it hears what it hears today. This amends §386's "it refuses `*` before any other
event". Why: ADR 0363 §5 stays in force for what §386 does not take over, and a refusal would end what a module
hears with no answer of the operator's (develop question IX21). How it is checked: unit (manifest validator),
`*.usage.*` accepted as the shared form; the grants part's table test makes no per-module consumer for it.
- §475 **The forge module learns a head's fingerprint from every `step-ended`.** (§487) The state `failed-steps` of §39 gains
a second key form, `seen.<commit>.<module>`, holding the build-input fingerprint the runs named for that commit
and module, written from every `step-ended` of any outcome whose commit is then a watched branch's head or the
head an open pull request names, and deleted by §135's rule; and each failure entry's commits take every commit a
later `step-ended` names at its module and fingerprint. `merge-pull` reads a head's fingerprint from its `seen`
key, so a head whose runs all ended before another commit of the same content failed is refused, as §39 says. This
completes §39 and §135. Why: §39 decides the refusal and that it is judged from the module's own state, and
`step-ended` names one commit and fingerprint at a time (develop questions GIT7 and GIT8). How it is checked:
whole (forge module), a head that passed, then another commit at its fingerprint failed a `required` step, is
refused; its `seen` key goes when the pull request closes.
- §476 **`take-in.taken`'s body and its type.** (§493) The indexer's own `take-in.taken` carries, beside what it carries on
main, `branch` (the watched branch), `source` (the source revision taken in) and `entries`, one `{module, commit,
fingerprint}` per module of that revision. Its body is defined in the seat `mesh-catalog`'s definition, in its
section `bodies` (§439, §454), so the generated contract carries the type the indexer writes and the runner reads,
and neither imports the other (§333). Why: a body said on a module's own stream and read by others is a `bodies`
entry (§439), never a client's hand-written type (develop question Q-RN4). How it is checked: unit (seat generator),
the type generated; whole (runner), a pipeline started from a fixture `take-in.taken`.
- §477 **A sent store's cap.** (§495) The bus module plans each `<module>_sent-objects` with a cap of 256 MiB, and a
foundation part's old `objects` section `sent-objects` names `max-mib` 256; a send refused at the cap fails its
reply or event loudly, naming the store, as §181 has a full store refuse. This completes §59's "the objects
template's", which has no default. Why: an object sent is transport kept 7 days (§59), most modules send none, and
the largest value the design moves whole is §345's hundred times the message limit, which 256 MiB holds twice
(develop question Q-RN5). How it is checked: unit (bus module's plan), every planned sent store has its cap; whole
(mesh-sdk), a send past a fixture's cap fails naming the store.
- §478 **`reserved` takes a package's name.** (§491, §504) The seat `mesh-builds`' verb `reserved` takes either `module` or `name`
(a package's or an image's name in its ecosystem: the Go module path, the npm name, the image name), with
`version`; given `name`, the builder finds the module from the catalog half's entries, whose packages carry each
name (§348, §412), and answers as for `module`, the artifacts with their digests. A registry holder asks it with the
name, version and digest its publish carries, so it reads no catalog and its publish gains no field; a name no entry
carries is answered `error`, naming it, and the holder refuses the publish (§381). Its callers stay §381's. This
completes §381. Why: the builder already reads each package's name from the catalog half (§348), so the mapping is
its own fact (ADR 0596 §2) (develop questions GIT12 and REG1). How it is checked: unit (`mesh-builds` contract),
`reserved` with `name` read; whole (builder, registry holder), a Go publish named by its module path is checked
against the module's reservation, and an unknown name refused.
- §479 **A branch's catalog view is named with its branch.** (§495, §521, §532) In mesh-sdk's `go/views` reader and process reader, a
first view of a per-branch state is named `<owner>.<state>.<branch>`, the branch escaped as a key's name is (§73),
so `mesh-catalog.catalog-revisions.<branch>` names one view per watched branch, a part reads several in one reader,
and `<module>_first_views` lists one row per branch (§358, §365). A view of a state that is not per branch keeps
its name `<owner>.<state>`. Why: the condition keeper reads every watched branch's newest revision (§365), and the
state's keys are `<branch>.<revision>` already (§343) (develop question CT10). How it is checked: unit (`go/views`),
two branches' views of one state in one reader, each its own row; whole (keeper), a module watched only on a
second branch is a source.
@@ -0,0 +1,315 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0668"
- "ADR 0666"
- "ADR 0660"
- "ADR 0656"
- "ADR 0633"
- "ADR 0603"
- "ADR 0596"
- "ADR 0595"
- "ADR 0584"
- "ADR 0579"
- "ADR 0564"
- "ADR 0563"
- "ADR 0363"
- "ADR 0240"
---
# 47, milestone G: the operator's answers of 2026-11-11 noon, the review of §456 to §479, and develop's next questions
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§480 to §482 are the operator's answers of 2026-11-11 about 11:50 CET at lead-47's terminal. §483 to §495 answer
what the four reviewers by architectural domain found in round 1 of [G-10](G-10-develop-answers.md) (§456 to §479).
§496 on answer develop's next questions of internal form (playbook 07, step 0b), each the design's choice with its
reason. What a question of another kind leaves open waits for the operator and is said in the line.*
## The operator's answers
- §480 **Who may drop a build seat's detected steps.** The build seats' `steps` with `drop` (§463) is called by the
runner and the indexer, as §338 has `steps`, and by the operator and agent sessions; every drop is written in the
hand-act log of the seat `mesh-hand-acts` with the caller the bus proved. The log is written first: a drop whose
log write fails deletes nothing and answers `error`, naming the log. This amends §338 and completes §463. Why: the
operator, 2026-11-11 at the terminal: §326 has a drop happen on a person's word. How it is checked: whole (Go
build module), a drop by a session is in the hand-act log with its caller and deletes the key; a drop by any other
account is refused by the bus; with the hand-act log refusing, nothing is deleted.
- §481 **A step key's asker is the account the bus proves.** (§600) The `<asker>` of a step key `<asker>.<node>.<id>` (§467)
is the account the bus proves sent the ask, never a field of the ask. An ask is published on
`mesh.seat.<seat>.accept.<queue>.<asker>` (§600), the asker's name the last token, and the grants part grants each asker of
§338 publish on its own name alone, so the subject the holder took the ask from names the asker; an ask's own
`asker` field, where one is sent, is not read. Each build seat's holder on a node is granted writes only of that
node's keys, `<asker>.<node>.<id>` (and main's `<node>.<id>`, §483). The stream's subjects are §600's. This amends §467 and §206. Why: the operator, 2026-11-11 at the terminal; the name in
the subject is how the bus proves who published (ADR 0363 §2). How it is checked: whole (grants part, Go build
module), an ask published under another asker's name is refused by the bus; a step of each asker is keyed under
its name; a holder's write of another node's key is refused.
- §482 **In G a code review refuses nothing on authorship.** The runner's `review` takes a review's result from any
of its callers (§394), refuses none for being the commit's author, and records with the result the caller the bus
proved, in the step's result, `step-ended` and `show`. Code review is never a `required` step in G: `register` and
`policy` refuse it, as §2 says. This amends §70, §72, §394, test 86 ("an author's own review is refused") and the
verb table's `review` row, and answers the runner's question of who the author of a commit the runner makes is: no
line of G reads a commit's author. Why: the operator, 2026-11-11 at the terminal (develop question RN13); no record
binds a forge account to a bus identity (*What is not decided here*). How it is checked: whole (runner), a review
by a session passes an `optional` review step and its result names the proven caller; `policy` naming code review
`required` is refused, naming why.
## The review of §456 to §479, round 1
- §483 **Main's step keys stay readable while main's askers run.** (§600, §601, §544) A build seat's holder of G keys a step whose ask
came on main's subject `mesh.seat.<seat>.accept.<kind>`, with no asker's name, as main does, `<node>.<id>`, and a
step whose ask names its asker as §481 says; so a builder or checker of before G, which asks on main's subject,
finds its answer under `<node>.<id>` through the rollout one node first and after a revert. Every asker of G asks
under its name, and the holders of the three build seats are delivered before it by a served-verb edge (kind 6)
the delivery plan derives from the asker's `invokes` of the queue (§601); an ask under a name that reaches a holder of before G waits on the work queue, until the grants part's worker filters both forms (§600, §601), and is said by `build-capacity-waits` after 60 minutes. The checker asks on main's
subject until it goes (§97). main's subject and its key form are served until the first change after the checker
is removed and no builder of before G runs, as §372 has `builder_versions`. Why: a reader is delivered first (ADR
0667 §4), and a step key is the answer its asker reads (review round 1, running and testing 2, security and upgrade
1). How it is checked: whole (Go build module of G, builder of main), main's builder's build is answered under
`<node>.<id>`; whole (runner of G), its steps are keyed under `runner.`; the delivery plan of G names the edge from
each build seat's holders to the runner, the indexer and the assigner.
- §484 **Every reader of a step key moves to one parser.** The delivery SDK's `buildstep.ParseStepKey` reads both
forms, `<asker>.<node>.<id>` and main's `<node>.<id>` (answering no asker), and its readers move to it in G's
change: the builder's `buildrun/steps.go` and `internal/build/stuck.go` (its own `builder.` keys and main's form),
the delivery SDK's `buildrun/stale.go`, the status view's `usage.go`, and each build module's read tools, which
list every key of the state, main's form included, until the state's two-day age takes them. The checker's
`bounded.go` stays as on main, since the checker asks on main's subject (§483). The watchman's `mirror.go`, which
read the build modules' `steps` (ADR 0596 §2 forbids it), goes: the watchman times a running ask from the build
seat's `step-progress` (§496). A key either form does not parse is written in the reader's journal naming it,
never passed over in silence. Why: one key form, read once (review round 1, health and events 1). How it is
checked: unit (`buildstep`), both forms parsed and a third refused; whole (runner, Go build module, watchman), a
step under `runner.` whose worker stops raises `pipeline-stands` within five minutes.
- §485 **mesh-sdk's finding writer runs one act at a time, and recovers.** (§609) The writer of §461 runs its acts, `write`,
`clear` and a statement, one at a time in the order they are asked (in Go behind one lock, in TypeScript on one
chain of promises), so a statement never deletes a key a `write` made meanwhile. It makes its consumer of
`findings-asked` before its start statement. When the bus connection is made again, it makes the consumer again,
from the newest ask, and states its findings again. When `observe` answers an error, the writer calls it again
once a minute until a set is said, each error in the journal. `clear` of a key that does not exist deletes nothing
and says `finding-cleared`, so a keeper holding the finding clears it. This completes §461. Why: §384 has the
keeper's view rebuilt from statements alone, so a statement that races a write, a consumer lost at a restart of
the bus or one error at start would leave a finding wrong until the next ask (review round 1, running and testing
3 and 4, health and events 2). How it is checked: unit (writer, both languages), a `write` during a statement is
kept; after a reconnect an ask is answered and the findings are stated again; an `observe` failing at start then
answering says its set within a minute.
- §486 **A full step log is said and refuses no step's end.** (§606) The runner's `step-log` (§470) joins the stores of §366:
its fill in `show` and `kept`, `filling` past 80 %, `full` when the bus refuses an append (ADR 0595 §3), `room`
below 70 %. A step's end is written in `pipelines` and said as `step-ended` before its log entry is appended, so a
refused append ends no step: the runner keeps the entry in that pipeline's key as `unlogged`, starts no new pipeline
while the log is full (§366), and appends the kept entries in their order on `room`, from which §330's ageing reads
them. Why: a full store is said and refuses loudly (§181), and a step's result is the pipeline's, not the log's
(review round 1, running and testing 5, health and events 3). How it is checked: whole (runner), with a fixture's
small cap a step ends and is said, `full` is said and a start refused, and on `room` the entry is appended and the
start taken.
- §487 **The forge module learns a commit's fingerprint from every step end.** (§548) §475's `seen.<commit>.<module>` is
written from every `step-ended`, whatever its commit, a run on request's included, so a commit that failed in a run
on request before its pull request was opened is known at its merge. A `seen` key is deleted by §135's rule once
its commit has been a watched branch's head or the head an open pull request names and is neither, and otherwise
30 days after the last `step-ended` that wrote it. `merge-pull` judges a head that has a `seen` key by it; a head
with none was named by no run, and merges, its pipeline after the merge judging it (§29), so `merge-pull` refuses
nothing it did not refuse before. `git_failed_steps` answers the `seen` keys too, a commit's fingerprints with
`commit`, and `git_failed_steps_drop` drops a `seen` key, with why. The kept-data row of `failed-steps` counts them:
one key of about 0.2 KiB per commit and module a run named, an estimated 500 a day, under 3 MiB at 30 days. This
amends §475 and §135. Why: §39 judges a head by any run at its fingerprint (review round 1, running and testing 1,
seams and tools 3). How it is checked: whole (forge module), a run on request of a commit fails a `required` step,
a pull request opened at that commit is refused, naming the step; a head that no run named merges; a later commit
of the same content is refused too.
- §488 **A call family G adds has its own capability word.** (§605) The tool runner states, beside `ledger-calls` and
`bus-state-calls`, `ledger-upkeep-calls` (§462's `mesh/ledger.fill` and `mesh/ledger.remove`), `sent-objects-calls`
(`mesh/objects.sent-get`) and `put-sessions` (§457's put and get sessions), each only once it answers that family.
mesh-sdk's bundle reads the words its node's tool runner states when it connects, and a call of a family whose word
is not stated fails at once, naming the word and the node, never a timeout. On each node a module is delivered after G's tool runner by §605's edge.
This amends §408 ("no capability word is added") and §462. Why: an older tool runner states the old words and does
not answer the new calls (ADR 0595 §9; review round 1, security and upgrade 2). How it is checked: unit (tool
runner), each word stated with its family; whole (mesh-sdk), against a tool runner that does not state
`put-sessions` a large put fails naming it; the delivery plan of G orders the modules after the tool runner.
- §489 **Who publishes on a put or get session.** (§518) On `<bundle call subject>.put.<call id>` only the module's own
account publishes the chunks and only the tool runner of its node answers them; on `<bundle call subject>.get.<call
id>` only the tool runner publishes the chunks and only the module's account answers. The grants part grants them
so, as it grants a module's call subject today (ADR 0363 §2). Why: §457's subjects lie under a subject the module
already owns, so the grant follows the record (review round 1, security and upgrade 3). How it is checked: whole
(grants part, tool runner), a chunk published by another module's account is refused by the bus.
- §490 **The get side of a session.** (§518) A get of a value over the limit (§457) runs so: the bundle subscribes to
`<bundle call subject>.get.<call id>` before it makes the call; the tool runner's answer to the call is
`{chunked: <count>, size, digest}`, the value's `sha256:` digest; the tool runner then publishes each chunk as a
request on that subject, with `Mesh-Put-Seq` and, on the last, `Mesh-Put-Last: true`, and the bundle answers each
with `{seq}` or `{error}`, the next sent only after a `{seq}`; the bundle checks the whole against `digest`. A get
not completed within the call's bound fails, naming the object. This completes §457. Why: one form both ways
(review round 1, seams and tools 2). How it is checked: whole (mesh-sdk, tool runner), a value of a hundred times
the limit is got back whole and its digest checked; a chunk lost fails the get naming the object.
- §491 **A package's name belongs to one module.** The manifest validator refuses a revision in which two modules'
entries carry the same package name in one ecosystem, naming both. `reserved` by `name` (§478) answers `error` when
more than one entry carries the name, naming them. Why: a name read as a module must name one (review round 1,
security and upgrade 4). How it is checked: unit (manifest validator), two modules naming one Go module path
refused; unit (builder), a name two entries carry answered `error`.
- §492 **The builder hears the toolchains as an event.** (§604) Each build seat says `toolchain-reported` on
`mesh.seat.<seat>.event.toolchain-reported.<node>` (`node`, `language`, `version`, `recipe`, `digest`), declared
in the three build seats' definitions in `mesh-delivery-contracts`, at its start and each time its `toolchains` key
of a language changes; the builder agrees the version from these events through a durable consumer of its own, and
its manifest's `reads` of the build modules' `toolchains` go. `digest` is the digest of the program of the
provision the build module requires for that language's toolchain, never the build module's own. This amends §472.
Why: ADR 0596 §2 (review round 1, seams and tools 1). How it is checked: whole (builder, Go build module), a changed
toolchain is agreed from the event; the builder's account holds no read of a build module's state.
- §493 **What `take-in.taken` and `take-in.pending` carry, from every source.** A take-in by hand
(`indexer_take_in_pending`) and a revert, which is an ordinary merge, say `take-in.taken` with `entries` as any
take-in does, and start pipelines; a hand drop (`indexer_drop_pending`) says it with `dropped` and no `entries`,
and starts nothing. A `take-in.taken` with no `entries`, such as one said before G and read again, starts nothing
and is written in the runner's journal. `take-in.pending` carries `branch` beside its fields on main, and both
bodies are in the seat `mesh-catalog`'s `bodies`; the watchman and the runner read the generated types. This
completes §476. Why: §476 named one source (review round 1, health and events 4; security and upgrade's note).
How it is checked: unit (runner), a `take-in.taken` with `dropped`, and one with no `entries`, start nothing; whole
(indexer), a hand take-in's event carries the entries.
- §494 **The second reader release carries the node-engine's claim and reaches the grants part.** §471's release,
merged and delivered before G's merge, carries `max-mib` on an old `state` section (§469) and the validator's rule
of §508; the grants part, which reads manifests through that reader to make the buckets, is rebuilt with it by the
release's publish (kind 1), so the runner's manifest is read by every part that takes it in. Why: readers first
(ADR 0667 §4; review round 1, security and upgrade's note). How it is checked: G's check reads the runner's and the
node-engine's manifests; whole (grants part), the runner's `pipelines` bucket has its cap.
- §495 **Small corrections from the review.** (§530) §472's "kind 3 is stale (§441)" reads §375. §472's `toolchain-agreed` carries its digests as they stood when the version was agreed; a digest that changes later at that version is read from `toolchain-reported` (§492) and starts nothing. A sent store already over its cap when §477's
cap is planned keeps no cap until its seven-day age brings it under, the bus module's plan naming it with its fill
meanwhile; a sent store is planned only for a module §344 names, and the plan refuses caps whose sum passes the
bus's storage limit, naming the sum. §468's "behind" is the age of the oldest message the consumer has not
acknowledged. §479's view of a branch with no revision yet is ready and empty, as `revision` answers `none yet`, and
raises no `view-waits`; its `view-waits` names the view `<owner>.<state>.<branch>`. A build seat's read tool lists
the keys of both forms (§484). The seat generator sits in the delivery SDK as a tool run at build time by the
contracts modules, imported by no running module, so no running part depends on the delivery SDK through it
(§464). How it is checked: §475's test takes a commit named by a later run's step; §465's case of the artifact
store not answering fails the step naming the member; the rest as each line it corrects.
## Develop's next questions
- §496 **A running step says its progress.** (§601 to §603) The holder of a build seat says `step-progress` on
`mesh.seat.<seat>.event.step-progress.<node>` (`asker`, `ask`, `phase`: `fetch`, `build`, `run` or `report`, and
`at`) once a minute while it runs an ask, declared as §603 says; the assigner says the same on `mesh-wants` for a composition step. `step-started` gains `ask`.
The watchman's five minutes (§75) run from a step's `step-started` or its latest `step-progress`, so a step that
runs longer but says its progress raises nothing; it times every ask of a build seat so, a builder's included, and
raises for it what its mirror raised on main. Why: §75 times owed progress, never run length, and names no event
for it (develop question WM1). How it is checked: whole (watchman, Go build module), a 20-minute step saying its
progress raises nothing, and one whose holder stops raises `pipeline-stands` within five minutes.
- §497 **The watchman knows a pending revision from `take-in.pending`.** A watched branch's head is behind a pending
revision (§12) while the watchman holds an open take-in wait whose `take-in.pending` names that branch (§493); its
head is then governed by `take-in-waits`' 60-minute bound and raises no `pipeline-stands`. A head on a branch whose
`branch-unprotected` stands (§17) raises none either. Why: the watchman already times each take-in wait (develop
question on §12). How it is checked: whole (watchman), a head behind a pending revision raises nothing for 60
minutes, then `take-in-waits`.
- §498 **A wait names its clock and its kind.** (§534, §608, §542) `meshwants.Wait`, the runner's wait body and the node-engine seat's
gain `clock-from` (the time the bound counts from, §266, §269) beside `bound`, and `step`, one of the acting steps
of §203 or `put`, `delivery`, `kept-build`, `backup`; a put's wait (main file, *A put without hold that stands*)
is `step: put` with the node, the module and `fingerprint`; a dependent's delivery wait (§376) is `step: delivery`
with `part` the dependency, its version and `machines`. `assigner_puts` answers each put with its node, module,
fingerprint, `held`, since and bound, from which the watchman seeds. Why: one wait form, told apart by `step`
(develop questions WM6 and WM10). How it is checked: unit (contracts), each field read; whole (watchman), a put's
wait raises `put-stands` and a pipeline's wait raises nothing for it.
- §499 **The node-engine says a kept build's wait, and its version is read from the catalog.** (§541, §609) A node-engine of G
waiting for a kept build says `wait-held` on its seat with `step: kept-build`, `node` the build node, and why
(`node away` or `build no longer kept`), and `wait-ended` when it applies; its have's value carries the same as
`waits`. The watchman takes the keeping node from the builder's `build.ended` (§379), never its done key. Each
machine's node-engine version, named by `delivery-waits` and `kept-build-waits`, is the version of the catalog
entry (`entry`, §443) whose fingerprint `running.<node>.<node-engine's module>` names, or that fingerprint when no
entry answers. Why: ADR 0596 §2 (develop questions WM4 and WM5). How it is checked: whole (watchman, node-engine),
a kept build's node away raises `kept-build-waits` after 60 minutes naming the node-engine's version.
- §500 **The manifest findings from the indexer's events.** From G the watchman raises `manifest-refused` from the
indexer's `manifest-refused`, and `manifest-outgrown` from its `manifest-accepted`, which gains `problems`, what a
stricter validator finds in a manifest it still accepts (ADR 0579 §1), cleared by a `manifest-accepted` with none; it
seeds both from the indexer's `verdicts`. `manifest-waits` is no longer written: every revision is judged by the
validator built from its own heads (§16), so its validator knows every field, and a reader that does not is a
reader edge said by its own wait (§94). Why: the indexer's `modules` goes (§196; develop question WM9). How it is
checked: whole (watchman, indexer), an outgrown manifest raises `manifest-outgrown` and its mending clears it.
- §501 **`watchman_views` answers per part and view.** (§609) It answers, per module and node, each first view the part
names: the view (`<owner>.<state>`, or with its branch, §479), `ready` or `waiting` with since when, and how far
behind (revisions not applied, and the age of the newest applied), read from each part's `<module>_first_views`
tool through the tool runner and from the `view-waits` findings the keeper holds. Why: §83's row names the sources,
not the form (develop question WM8). How it is checked: whole (watchman), a part waiting for its view is answered
`waiting` with its since.
- §502 **A record is tied to its source revision's entries.** The watchman ties a delivery record to the entries of
its source revision's `take-in.taken` (§493), and leaves `record-open` unwritten for a record only while one of
those entries is held by a wait that raised `acting-part-late`, or a store that holds it says `full`; a record whose
revision is not taken in is covered by `take-in-waits`. `record-opened` gains nothing. Why: §148 judges a record by
its own entries, never every record at once (develop question WM7). How it is checked: whole (watchman), a record
of two entries, one held by `acting-part-late` and one not, raises `record-open` at 24 hours.
- §503 **The finding writer is mesh-sdk's, in Go and TypeScript, built in G.** The writer every observer uses is
§461's and §485's, built in G in mesh-sdk's `go/views` and `src/views` (exported as `views`), by mesh-sdk's
developer; the TypeScript one is built in G, since the forge module and the registry holders are observers in
TypeScript. `meshconditionsclient/findings` goes (§461). Why: develop questions GIT14, REG2 and WM11. How it is
checked: §461's.
- §504 **The builder maps a package's name as §478 does.** Every builder answer asked by a package's name in its
ecosystem, `builder_versions` and the registry holders' check of a publish by name, version and digest included,
finds the entry carrying that name (§478, §491) and the artifact whose package it is, `<module>:<artifact>`, and
answers for that artifact. Why: one mapping, the builder's own fact (develop question BL7). How it is checked: unit
(builder), a Go module path and an npm name each answered for their artifact.
- §505 **A manifest `version` that is not `MAJOR.MINOR` reserves nothing.** A build of a module whose catalog entry's
`version` is not `MAJOR.MINOR` (a manifest from before G, `"1"`) reserves no version and is built and delivered as a
build without a version (§371, §458), the builder writing it in its journal naming the module; at G's heads the
validator refuses such a manifest (§411). Why: such an entry is only read from a revision before G (develop
question of the builder). How it is checked: unit (builder), an entry of version `"1"` reserves nothing.
- §506 **The entry carries its manifest's digest.** (§609) The catalog graph entry gains `manifest-digest`, the `sha256:`
digest of the manifest's bytes as the forge answered them, set by the indexer at take-in (in `EntryInputs`) and
never computed again from the manifest whole, whose encoding may differ; the module's value and §445's
`manifests` key are that digest. Why: the bytes, not the map, are what the node-engines before G read (develop
question IX16). How it is checked: unit (indexer), the entry's digest is the digest of the bytes read.
- §507 **The watched branches before the first registrations.** (§532) Every reader of a trunk's source revision reads, per
watched branch the registrations name, `source.<branch>` through the gate's verb `source`; before the first
registrations (§95, §353), the watched branches are the branches the gate's `source` answers, which G's switch
carries in from `source.main` as the branch `main`, read from the key and never fixed in code (§7). The assigner's
trunk-only want, the status view and the forge bridge read so, and the bridge watches the same branches. Why: §7,
and one source of the branches for every reader (develop questions Q18 and VW0, and the bridge's FB9). How it is
checked: whole (assigner, bridge), before the first registrations a want is written from `source.main`, and no
part's code holds `main`.
- §508 **The node-engine claims its seat as one module.** (§527, §609) The seat `node-engine`'s definition in mesh-contracts no
longer marks it held by the engine; the manifest validator accepts one module of a revision claiming it and
refuses a second, naming both, in place of refusing every claim, and §494's release carries the rule. The
node-engine's guard reads the holder from the seat list (§364). Why: §364 has the node-engine's manifest claim the
seat (ADR 0563 §1), and the mark and the rule refused it (develop question on §364). How it is checked: unit
(manifest validator), one claim accepted and two refused; unit (definition), no mark.
- §509 **What the forge bridge writes on `forge`.** For each move of a watched branch, after reading the commit by
`git.branch` (§11), the bridge writes one message: `merged` when the forge names the commit a pull request's
merge, with that pull request, as on main; `pushed` when it names none, with the repository, the branch, the
commit and the head before; the indexer takes both in alike. For a move of a branch whose protection was removed
(§17) it writes nothing and says `branch-unprotected`; the next move after the protection is back is written as any.
Why: one message per move, the pull request where there is one (develop questions FB1, FB2 and FB7). How it is
checked: whole (bridge), an admitted push writes `pushed`, a merge `merged`, and a move of an unprotected branch
writes nothing.
- §510 **A merge the switch carries is performed.** (§608, §542) A `merges` key the old bridge had not performed, an Approve the
operator gave before G, becomes an act (§169) that the bridge performs through `merge-pull`, judged as every merge;
while `merge-pull` refuses it for missing registrations (§95) the act stays unanswered and is performed again on
`registration-changed`. No other merge act is made after G (§167). Why: §169 drops nothing the operator approved
(develop question on §167 and §169). How it is checked: whole (gate, bridge), a carried merge lands once after the
first registrations.
- §511 **The assigner's `written` key.** §352's key holds the newest passed `pipeline-ended` heard for that module and
watched branch, its source revision and end count, and `written`, whether its wants were written; a newer end
replaces it by compare-and-set, and the wants are written from it once they can be. Why: a restart loses no end heard
and not yet used (develop question on §352). How it is checked: whole (assigner), an end heard, then a restart,
writes its wants.
- §512 **The status view's tiers from the difference.** A tier's module is `{module, build, version, after, waits,
left-out}`: `build` the fingerprint, `after` the modules it is ordered after, each with the edge's kind (§378),
from `difference`; a wait is `{owner, step, node, on, bound, since, words}`: the seat holding it, its `step`
(§498), what it waits on, from the owners' `wait-held` and their read tools, and a pending revision from
`indexer_pending` with `owner: mesh-catalog`, `step: take-in`. The view's manifest drops its `reads` of the
teller's `answers` and the gate's `asks` and `source`: the Approve per merge goes (§431), its reach is the
catalog half's (§196) and its source `source-moved` (§507). A dependent's delivery wait is read from the assigner's
`wait-held` with `step: delivery` (§498) and `version-delivered`'s machines away. Why: §186 has the tiers worked out
from the difference and the owners' waits (develop questions VW2, VW3 and on §431). How it is checked: test 45; test
106 for the delivery wait.
- §513 **A state's objects store has a cap, and the binding is mesh-sdk's.** (§607, §547, §591: the cap is §591's) The bus module plans each
`<state>-objects` (§58) with the cap §591 names, unless the state's `requires` names `objects-max-mib`. The binding's `objects` and `sent-objects` (§399) are fields of mesh-sdk's
`membership.BusBinding`, built in mesh-sdk. Why: the objects template has no default (develop questions NATS1 and
on §399). How it is checked: unit (bus module's plan), each planned objects store has its cap; unit (membership),
both fields read.
- §514 **The teller tells through `notify`, on every channel.** (§522) For `widens` (§71, §335), `recovery-merged` (§81)
and a `forge-made` record (§170) the teller calls the seat `operator-channel`'s verb `notify`, named in its
manifest's `invokes`, with `every-channel: true`, which the router sends on every channel's `send`, as design 46
announces a break-glass act. The words are the operator's to approve and wait for them (develop questions TL1 to
TL3). How it is checked: whole (teller, router), each of the three reaches every channel once.
- §515 **The condition keeper's sources are its consumers' bindings** (corrected by §663: the bus module's `created`). The keeper learns that the grants part made a
source module's durable consumer (§386) from the seat `mesh-broker`'s binding of that consumer to the keeper (§399),
heard as its event and read by `list` at the keeper's start, and asks `findings-asked` naming the module on it. At a
start with no kept sources and no first view of the catalog half, its sources are the modules its consumers'
bindings name, so no list of observers is fixed in its code (§7); the catalog half then adds and removes sources
(§365, §388). Why: the binding is the fact that the consumer exists (develop question COND2). How it is checked:
whole (keeper, grants part), a module declaring the finding events is asked once its binding is said; started with
the catalog's holder stopped and no kept sources, the keeper hears every bound observer.
@@ -0,0 +1,587 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0500"
- "ADR 0573"
- "ADR 0672"
- "ADR 0667"
- "ADR 0666"
- "ADR 0660"
- "ADR 0656"
- "ADR 0636"
- "ADR 0626"
- "ADR 0619"
- "ADR 0618"
- "ADR 0614"
- "ADR 0596"
- "ADR 0595"
- "ADR 0564"
- "ADR 0505"
- "ADR 0404"
- "ADR 0363"
- "ADR 0240"
- "ADR 0235"
---
# 47, milestone G: the operator's words of 2026-11-11 and develop's fourth batch of questions
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§522, §542 and §551 to §553 are the operator's answers of 2026-11-11 at 12:11, 12:33 and 12:41 CET at lead-47's terminal. §543 to §550
answer what the four reviewers found in round 3 of [G-13](G-13-review-round-3.md) (§600 to §609), which has no round
of its own after it. Every other line answers a question
develop met after [G-11](G-11-develop-answers-review.md), of internal form only (playbook 07, step 0b), each the
design's choice with its reason. What a question of another kind leaves open waits for the operator and is said in the
line. The operator's answer of 12:55 CET (the recovery module, §554 and §555) and the
review of §516 to §553, round 1 (§556 to §558), are in [G-14](G-14-recovery-module-and-review.md); the findings of
that round are written into the lines they name, here. Questions that an earlier line already answers have no line here: develop reads that line.*
## Kept data on the bus: restore points and what is set aside
- §516 **A restore point's bytes, and the forms of `snapshot-kept` and `restore-kept`.** (§551, §554) The seat
`mesh-recovery`'s two verbs (§554) take and answer the forms mesh-contracts' definition gave the seat `mesh-broker`'s,
which move to `mesh-recovery` and which this line makes the design's: `snapshot-kept` takes `module`, `kept` (the
kept data's local name), `form`, `fingerprint`, `why` and `catch-up` as `{tool, arguments}` (§451); its point
(the module, the kept data, its form, the fingerprint, `snapshot`, `consumers` with each durable consumer's
stream, name and acknowledged sequence, `size` in bytes, `at` and `catch-up`) comes on its event `ended` (§554); `restore-kept` takes
`module`, `kept`, `form`, `fingerprint`, `why`, `from-backup` (yes or no) and `snapshot` (the restore point's name),
and refuses `aside-only` and `discard-aside`, answering `error` naming them: setting aside without restoring and
discarding an aside are the operator's hand tools alone (§554); its result (`restored`, `aside`, `missing`, true when the
point is not in the recovery module's directory, and `error`) comes on its event `restored`. Both verbs answer only
`accepted`, `busy` or `error` (§554). No argument `local-name` or `from` is taken, and a
catch-up is never text.
- **Where a point lives.** The recovery module takes a point through the bus's stream snapshot under its own account
(§554), one bus object of the kept data at a time (its stream, or the stream under its bucket or object store),
with ADR 0235 §10's pacing and §11's bounds, and writes it with the point's record under a temporary name, renamed to its own name only once the point is
whole (as ADR 0235 §2 writes the dump), in its own data directory, `points/<module>/<kept>/<form>-<fingerprint>`;
at its start it removes every point left under a temporary name, so a point cut off by a restart, the bus away or
its bound is never listed nor restored; each token escaped as a key's name is (§73) and a token that is
empty, `.` or `..` refused; that name is the point's `snapshot`.
- **Its copy.** That directory is an item of the recovery module's manifest `data`, backed up by the holder of
`node-backup` on its node as every item is (ADR 0404), which stores the copy and nothing else. When
`restore-kept` answers `missing`, the node agent calls it again with `from-backup: yes`: the recovery module asks
the seat `node-backup`'s `restore` of its item for that directory, which brings it back beside its path as that
verb always does, takes the point from there and restores it once its digest is checked (§567); when no
repository snapshot holds it, `restored` says `failed` with `restore point missing`, and §300 holds the revert.
- **The aside, and a restore that fails.** `restore-kept` sets the live kept data aside by taking a stream snapshot
of each of its bus objects into `aside/<module>/<kept>/<at>` in the same directory, reads the aside back to check it (`at` the UTC time written
`20261111T121200Z`, with `-2`, `-3` and on added when that name exists), only then removes the live objects, which needs no person's warrant, the data staying in the aside (the operator,
13:06 CET, §554),
restores the point under their names and sets each consumer to its recorded sequence. Each step is written first
in the aside's record (`set aside`, `removed`, `restored`): when a step after the removal fails, `restore-kept`
restores the aside under the live names and sets the consumers back before it answers `failed`, and at its start
the recovery module does the same for every aside whose record ends at `removed`, so a failed or stopped restore
never leaves the kept data gone. The aside's `id` is that directory's name, its `name` the bus name it was live
under, its `size` its bytes on disk; nothing of an aside stays on the bus. `recovery_discard_aside` (§554) removes that directory
and nothing else, an `id` holding `.`, `..` or `/` refused. A restore point is removed only as §551 says.
This completes §405 and §406. Why: the operator's answer of 12:55 CET (§554) puts the taking and restoring of a
module's kept data on the live bus on a module of its own; the server restores a stream only where no stream of that
name exists, so the live data is set aside as a snapshot before the point takes its name (develop questions BKP1,
BKP3 and NATS4's argument names; review round 1, running and testing 1, seams and tools' notes). How it is checked:
unit (mesh-recovery's contract), each verb's input and answer read, an input naming `from` refused; a node-engine's
`restore-kept` with `aside-only` or `discard-aside` refused, naming them; whole (recovery
module, backup holder, bus), a point is taken, its directory removed, brought back with `from-backup` and restored,
the live data's aside listed with its size and the consumer at its recorded sequence; a point missing from both says
`restore point missing`; a restore made to fail after the removal, and one stopped there and started again, leave
the live data as before; a point cut off half way is removed at start and never listed.
- §517 **The recovery module lists what it holds.** The seat `mesh-recovery`'s read verb `kept-points` (class `read`,
§391's default callers) lists every restore point and aside in its directory with its module, kept data, form,
fingerprint, size and `at`, and the act that runs, with since when and what it waits on (§554); its tools are `recovery_kept_points`, which reads the same, and
`recovery_expire_points`, which runs §551's removal now (class `act`, §391's default callers, written in the
hand-act log). The directory is the one place they are kept, so no copy can list less: the backup holder keeps no
state of them (its `backed-up` lists the recovery module's item as any item, §302), and no `bus-points` state is
made. Why: a module's tools read everything it holds (*Tools are how a module is spoken to*), and the list is the
holder's own fact (ADR 0596 §2; develop question BKP2; review round 1, seams and tools 4). How it is checked: whole
(recovery module), `kept-points` lists a point and an aside with their sizes, an aside is gone after
`recovery_discard_aside`, and a restart lists the same.
- §523 **A seat event whose subject has parts.** A seat definition's `subject` with `<part>` tokens (§397), such as
`mesh-recovery`'s `ended.<module>.<kept>` and `node-backup`'s `item-started.<node>.<module>.<item>` (§554), is said on
`mesh.seat.<seat>.event.` followed by the subject, each part filled in the definition's order: the seat
generator's subject function for the event takes each part as a string in that order and fills each as one token,
escaped as a key's name is (§73), refusing an empty part, `.` and `..`. A reader hears one module's events by the subject with
that module's tokens and `>` after them, and every module's by `*` for each part. Such an event has no key of its
own and is kept as its definition's `keep` says, or on EVENTS when it names no `keep` (§396). The holder's account
publishes it as a seat's holder publishes its seat's events today; no grant changes. Why: §397 fixes the subject's
words, not how a part is filled (develop question NATS4's wire). How it is checked: unit (seat generator), a subject
of three parts made in order and an empty part refused; whole (recovery module), `ended` heard by a reader of one
module's subject.
## The tool runner and its bundles
- §518 **A bundle's put and get sessions run on its channel to the tool runner.** A tools bundle reaches the tool runner
only over the channel the tool runner starts it with (its standard input and output) and holds no bus account, so
§345's put session and §490's get session run on that channel, as every `mesh/` call of a bundle does (ADR 0595
§9); `<bundle call subject>` in §457, §489 and §490 names that call, and no bus subject. A value at or under the
limit is put and got by one call, as today. Over it:
- **Whose object.** A session's `name` names an object of the bundle's own module alone: the tool runner resolves
it in that module's binding (its state's `<state>-objects` or its sent store), and refuses a name of another
module's, naming it. A put's whole size is bounded by that store's cap, and a put whose chunks pass it is refused
at the chunk that does, naming the store.
- **Put.** The bundle sends each chunk as the call `mesh/objects.put-chunk` with `name` (the object), `call` (the
bundle's id for this put), `seq` (from 1, one more each chunk), `last` (true on the last chunk alone) and `data`
(the chunk's bytes in base64, at most half the bus's maximum payload before encoding, §457). The tool runner
answers each with `{seq}` or `{error}`, the bundle sends the next only after a `{seq}`, and the answer to the last
is the put's answer; a `seq` that is not one more than the last taken is answered `{error}` naming both, and a put
not sealed within the call's bound is dropped (§345). The tool runner writes each chunk into the store as it takes it, through the
object store's own chunking, holding no more than one chunk in memory, and seals the object on the last. A put dropped at its bound has its written chunks purged by the tool runner,
which deletes the unsealed object; until then they count against the store's cap.
- **Get.** The tool runner answers the get call `{chunked: <count>, size, digest, call}` (§490), and the bundle asks
each chunk in order by the call `mesh/objects.get-chunk` with `call` and `seq`, answered `{seq, last, data}` or
`{error}`; the bundle checks the whole against `digest`, and a get not completed within the call's bound fails,
naming the object. The tool runner drops a get session it holds when the call's bound passes, or the bundle's
channel closes, whichever comes first.
- **The bound.** The call's bound counts once for the whole session, from its first chunk, never per chunk.
The headers `Mesh-Put-Seq` and `Mesh-Put-Last` of §457 are these fields `seq` and `last`. Nothing of a session is
on the bus, so §489's grants are not made and the grants part grants nothing for it. A module's process, which
holds a bus account of its own, writes and reads its objects on the bus through the object store package directly,
with the store's own chunking, and runs no session. The capability word `put-sessions` (§488) names these two
calls. This amends §345, §457, §489 and §490; in the get the bundle asks each chunk, since on that channel every
`mesh/` call is the bundle's. Why: the operator's answer to develop question D-13, chunks in order, each
acknowledged before the next, is kept whole on the one channel a bundle has (develop questions T-5 and T-7). How it
is checked: whole (mesh-sdk, tool runner), a value of a hundred times the limit, into a fixture state whose
`objects-max-mib` holds it, is put and got back whole by a bundle, its digest checked, a put naming another
module's object is refused, and a chunk out of order fails the put naming both sequences; the grants part's table
test grants no session subject.
- §519 **A bundle's fingerprint is in the environment the node-engine composes.** The node-engine writes
`MESH_FINGERPRINT`, the fingerprint of the want it applies for the bundle's module, into each tools bundle's
composed environment, as it hands it to each process (§205); the tool runner starts each bundle with its composed
environment whole, and reads no catalog entry and no want for it. A bundle started with no value says `own build
unknown` (§424). This completes §424. Why: the want the node-engine applies is the one fact of which build a node
runs (§205; develop question T-12). How it is checked: whole (node-engine, tool runner), a bundle reads the
fingerprint of its module's want.
## The node-engine and the want
- §520 **`running` says a build's health, and the assigner reads health from it alone.** The node-engine seat's
`running.<node>.<module>` gains `healthy` (true or false), the health of the build that runs as the node-engine's
have holds it from the module's declared check (ADR 0240), and the node-engine says `running` again on the same
subject each time `healthy` changes, and for every module its have holds at its own start, so the stream, kept last per subject, holds each build's newest health.
`stopped` (§365, §404) is said only when the have no longer holds the module, never for an unhealthy build. A `running` with no
`healthy`, said by a node-engine of before G, reads as healthy while it stands, as `running` meant on main. The
assigner reads a provider's health for rule 5, the canary's for rules 3 and 4, and a put's end from these events
alone, read as §556 has it, and reads no have: a
provider of which nothing was heard counts as not healthy, so its consumers' new wants
are held as rule 5 holds any, and nothing falls back to the have. `SeatsToPassOn` reads no `healthy` (§459): an
unhealthy holder still holds its seat. The assigner's `reads` of the node-engine's have go. This amends §309 and
§404. Why: the assigner reads no other module's state (ADR 0596 §2), and a stopped build and an unhealthy one are
told apart (develop questions AS15 and AS16). How it is checked: unit (the node-engine seat's fixtures), `running`
with `healthy` read; whole (assigner, node-engine), a provider turning unhealthy holds a consumer's new want and its
recovery lets the want go, with the assigner's account holding no read of the have; whole (assigner, a
node-engine of before G), its provider's `running` with no `healthy` holds no consumer's want; a restarted assigner
holds nothing it did not hold before.
- §524 **The node-engine checks a want's parts by their digests.** A composition's archive, process and container
resources name a source and a digest, not an artifact, so the node-engine matches no resource to a `parts` key. A want
with no `parts` (from an assigner of before G, or for a node whose record does not state `want-parts`, §525) is
applied as today, its resources fetched by their sources and checked by their own digests. A want with `parts` is
applied only when the digest of every resource it fetched is among the values of the want's `parts`, and
every value of `parts` is the digest of a resource it fetched; otherwise it applies nothing of that module, its
have says the phase `held` (one of the seat's phases, which gains none) with its detail opening `parts-differ:` and
naming each digest that differs, and it says `wait-held` with the reason `parts-differ` (§541). `parts`
stays keyed by `<module>:<artifact>` for people and
the delivery record. This completes §379. Why: the digest is what is fetched and checked (develop question H-8); a
hold is the phase `held` with its reason, as every hold of §300, §421 and §499 is, so the seat's phases stay as they
are (develop question of 2026-11-11 on H-8's phase, batch 5). How
it is checked: unit (node-engine), a want whose parts and fetched digests agree applies, and one with a fetched
digest outside `parts` or a part not fetched applies nothing, naming it; whole (node-engine of G), a want written by
an assigner of before G is applied.
- §525 **A node-engine says it reads the want's new fields.** A node-engine of G states the capability `want-parts` in
its node record's capabilities (ADR 0614 §4, ADR 0618 §2), and the assigner writes `version`, `parts` and `kept-on`
(§526) into a node's want only while that node's record states it; that is §379's "once that node runs G's
node-engine". Why: ADR 0614 §4 has each node-engine state what it reads (develop question H-7). How it is checked:
unit (node-engine), the word stated; whole (assigner), a node whose record lacks it gets a want without the fields.
- §526 **A want names the node that keeps a kept build.** For a kept build (§271) the assigner writes `kept-on`, the
build node the builder's `build.ended` names as keeping it (§379), into the want beside `parts`; the node-engine
fetches that build through the seat verb `kept-artifact` of that node, and fetches a want with no `kept-on` from the
store. Nothing else names the keeping node, the builder's `builds` going (§421). This amends §271's "the builder's
done key names it so with its digest". Why: ADR 0596 §2 (develop question H-9). How it is checked: whole (assigner,
node-engine), a kept build's want names its node and is fetched from it.
- §527 **The node-engine's own module is read from the catalog half.** The seat list answers seat definitions alone
(ADR 0636), never who holds a seat. The node-engine's apply guard and its compose read their own module's name from
the catalog half of their view: the module whose entry claims the seat `node-engine`, which §508 makes one per
revision; the manifest validator, beside §508's refusal of a second claim, refuses a revision in which no module
claims it, naming the seat. §364's, §365's and §508's "from the seat list" read so, the condition keeper's too. With
no entry claiming the seat, which only a revision from before G shows, as at a revert of G, the node-engine's own
module is the module of the catalog graph entry its own build's fingerprint names (§443, §205), so a revert of the
node-engine applies. Why: §364 and §508 have the node-engine claim its seat
as one module, so the claim names it (develop question H-15). How it is checked: unit (manifest validator), a
revision with no claim of `node-engine` refused; whole (node-engine), its guard protects the module its catalog
half names, and with a revision of before G the module its own fingerprint's entry names, and the code review finds no fixed name (§428).
## Findings and the condition keeper
- §529 **A part's finding state and stream are declared in its kept data's form.** A part's state `findings` and its
finding stream (§383) are declared as ADR 0666 §4 has a part declare its kept data:
- **A foundation part** (the manifest validator's list: ADR 0619 §1's parts as ADR 0666 §4 changes them, the runner
from G's delivery and the checker until its removal) declares `findings` in its old `state` section and the
stream `<module>-findings` in its old `streams` section, with the events `finding-written.*`, `finding-cleared.*`
and `findings-stated.*`, 7 days and 16 MiB, its oldest dropped (§383), published on
`mesh.stream.<module>.event.<event>.<node>` (ADR 0363 §2).
- **Every other part** asks both in its `requires`: the state `findings` by the template `state`, keeping its local
name and carrying its settings as options (ADR 0595 §11), and the stream by the template `events` under the local
name `finding-events`, with `max-age-days` 7 and `max-mib` 16, which the template drops oldest when full (ADR 0595
§3), its entry listing the three finding events in its field `events`, which the catalog graph entry carries
(§365), published on its binding's subject, `mesh.bus.<module>.finding-events.<event>.<node>`.
- **`mesh-healers`**, whose old `streams` declares `mesh-healers-findings` on main, moves it so in G's change, as
ADR 0595 §11 moves a module G touches. Its old stream stays on the bus as an orphan that `orphans` lists, removed
only by the operator's `drop` (ADR 0595 §4, §5); the grants part deletes the keeper's consumer on it once the
healers' entry no longer declares it (§386), and the healers say `findings-stated` at their start on the new
stream (§384), so the keeper loses no finding.
The manifest validator applies ADR 0595 §11's count of old sections to no part of its foundation list (ADR 0619
§2), so the tool runner's and the checker's manifests are not refused for it, and accepts in a foundation part's
old `streams` the finding stream in the one form above, as it accepts a stream on its list of streams kept until
moved; every other old `streams` entry is judged as today. A part whose list entry is missing is a fault of the
list, fixed in the validator (ADR 0619 §3). The validator refuses a manifest whose state `findings` has no finding
stream in its part's form, or no `consumes` of `mesh-conditions.findings-asked` (§387). The writer (§461) is handed
the subjects from the part's binding or its old stream, as it is handed every subject. The catalog graph entry
carries the finding stream in either form (§365); the grants part makes the keeper's consumer of §386 on an
old stream at the revision that declares it, and on a template stream when it hears the bus module's `created` for
`finding-events` and grants each part publish on its own finding stream's subjects of its own node only, in either form; the
keeper takes the observer and the node from either subject. This amends §383, §386 and §387, whose "into the
manifest's `streams`" holds for a foundation part alone. Why: ADR 0666 §4 and ADR 0595 §11 put a part's kept data
in the templates unless it is a foundation part, and ADR 0619 §2 keeps the foundation's old sections whole; the
local name `finding-events` is the design's choice, since `findings` names the state and ADR 0595 §8 takes one
name per module (develop questions on §387, both validator refusals). How it is checked: unit (manifest validator),
a foundation part's old `findings` and finding stream accepted, a non-foundation part's old sections refused and its
template form accepted; the catalog's validation at G's heads refuses none of §387's manifests; whole (keeper, a
template observer and the tool runner), both forms' events raise a condition; whole (keeper, healers), a healers'
finding raised before their move is still raised after it.
- §540 **The keeper hands a failed head to the operator after two hours.** The condition keeper raises
`pipeline-failed` (§33) with `owner` the commit's author, as the runner's finding names it, and two hours after the
finding's `since` sets the condition's `owner` (a field of the condition, §685) to `operator-channel`, the seat the operator is asked through (a
seat's name, §451), and says that change as it says any change of a condition; the runner's finding keeps the
author. §451's `owner` and `waiting-on` gain a third form, `forge:<account>`, the commit's author as the forge
names it. A `pipeline-failed` for a newer failing head is a finding of its own, its id naming that commit, so it is
its new author's with two hours of its own. The keeper's tool `conditions_owner` sets a condition's `owner` now, of
class `act` with §391's default callers, written in the hand-act log. The keeper times the two hours on its own conditions from `since`, so after its restart it times them from
`since` again. Why: §33 has the keeper raise it as the author's, then the operator's (ADR 0505 §5, §6; develop
question COND7). How it is checked: whole (keeper), a `pipeline-failed` is the operator's two hours after its
`since`, and so after a restart of the keeper; unit (mesh-conditions' contract), `forge:<account>` read.
- §541 **The keeper hears what a node runs and the healers' attempts as events.** The keeper's `reads` of
`node-engine.have` and `mesh-healers.attempts` go (§434).
- **What a node runs.** The keeper consumes the node-engine seat's `composed`, `running` (with `healthy`, §520, and
`stopped`) and `wait-held` and `wait-ended` (§499), read as §556 has it, and compares them with the wants it hears
from the assigner, to hold a module's findings while it is delivered (ADR 0564 §13), as it did from the haves.
The node-engine says `wait-held` on `mesh.seat.node-engine.event.wait-held.<node>.<module>`, with `step: compose`
and the reason, for each hold its have writes (§300's `restore point missing`, §421's `entry-missing`, §524's
`parts-differ`, §499's kept build), and `wait-ended` on `.wait-ended.<node>.<module>` when the hold ends. The
keeper raises no condition of its own from them: the watchman writes `delivery-waits` for a `wait-held` of the
node-engine standing past composing's 30 minutes (§266), naming the node, the module and the reason, and it clears
on `wait-ended`; a `composed` whose fingerprint is not the want's is covered by that same wait or by the put's
wait (§498).
- **The healers' attempts.** The seat `mesh-healers` says `attempt-changed` on
`mesh.seat.mesh-healers.event.attempt-changed.<condition>.<attempt>` (§665), each token escaped as a key's name is (§73),
carrying the attempt's value whole as its key in `attempts` holds it and `until`, the end of that key's
lifetime, or `removed: true` when the healer ends the attempt; the seat's definition, in its contracts module,
keeps it last per subject, 7 days and 16 MiB (§396). The healers say it again for every open attempt at their start. The
keeper reads it as §556 has it, and drops an attempt at its `until` on its own clock.
Why: the keeper reads no other module's state (ADR 0596 §2; develop question COND8). How it is checked: whole
(keeper, healers, node-engine), an attempt said is shown on its condition and dropped at its `until` or its
removal, a node-engine's hold is heard from `wait-held` and past 30 minutes raises `delivery-waits`, a restarted keeper
holds the attempts it held, and the keeper's manifest declares no `reads`.
## Views of the catalog half, per watched branch
- §521 **An owner's view event names its state, and the key names the branch.** An event an owner says for a per-branch
state (`catalog-revision` for `catalog-revisions`) names the state as the owner's manifest names it,
`catalog-revisions`, and the key, `<branch>.<revision>` (§343); mesh-sdk's `go/views` reader and process reader
route it to the view `<owner>.<state>.<branch>` (§479) by the key's first token, the branch escaped as a key's name
is (§73), which the subject's last token repeats. No event names a state `<state>.<branch>`; mesh-sdk's code at
eed96854, which names it so, follows this line. Why: a state is named once, as its owner declares it, and the key
already carries the branch (develop question on §479 and §495). How it is checked: unit (`go/views`), one
subscription of a per-branch event routes two branches' events to their two views.
- §530 **`revision` says `none yet` in a field.** The seat `mesh-catalog`'s `revision` for a watched branch with no
revision yet answers `{none-yet: true}`, with no revision and no `error`; mesh-sdk's client reads `none-yet` as a
ready, empty view (§495) and never reads an error's text for it; `views.NoneYet` is the client's name for that
answer. Why: an answer is told by its fields, never its words (develop question on §495). How it is checked: unit
(mesh-catalog's contract), the answer read; unit (client), `none-yet` gives a ready, empty view.
- §532 **Every reader of the catalog half follows every watched branch, and adopts a branch registered while it runs.**
Every part that reads the catalog half per watched branch (among them the bus module, the status view, the
condition keeper, the tool runner, the assigner, the grants part and the runner) follows one view per watched
branch: the branches the registrations name, read by `registrations` at its start and followed by
`registration-changed`, and before the first registrations the branches the gate's `source` answers (§507); no part
names a branch in its code, the view's `main` included.
- **Adopted after the start.** mesh-sdk's views reader adopts a view after its start and drops one: on
`registration-changed` adding a watched branch the part adopts `<owner>.<state>.<branch>`, which takes its first
view at once and then follows; on one removing a branch the part drops the view and its row of
`<module>_first_views`.
- **One subscription per event.** The reader subscribes once per event, `<event>.>`
(`mesh.seat.mesh-catalog.event.catalog-revision.>`), and routes each event to its branch's view by §521, so two
branches never overwrite each other's handler; `meshcatalogclient` wires its views on that one reader, and the
runner's own wiring of `catalog-revision.>` goes for it.
- **The bus module across branches.** The bus module plans a module's kept data from the newest revision of each
watched branch that lists the module: each local name once, from the first of the module's watched branches, in
its registration's order, whose revision lists it. A registration's watched branches are a list in that order: the
branch `register` names first (the repository's default branch, a placement's default, ADR 0666 §6), then each
branch in the order `watch` added it; `unwatch` removes one and keeps the others' order; another branch asking that local name with other settings is
said as the bus module's `drifted`, naming the branch (ADR 0595 §6), and plans nothing.
This amends §358, §365, §421, §433 and §507. Why: no part fixes a branch (§7, ADR 0666 §5), and a registration may
add a watched branch while a part runs (develop questions NATS3, VW1 and COND6, and the client's lost per-branch
events). How it is checked: unit (`go/views`), a view adopted after the start takes its first view and follows,
and two branches' events reach their own views; whole (keeper, bus module), a branch registered while they run is
followed with no restart; whole (bus module), a feature branch asking a local name with other settings than the
default branch's is said `drifted` while the default branch's are planned; the code review finds no branch's name
in any part.
## The bus module's plans
- §533 **A module of the new form.** "A module of the new form" (§59, §344) is a module that is no foundation part.
The bus module tells it by its catalog graph entry declaring no old `objects` section `sent-objects`: a foundation
part whose entry claims a seat whose definition has a verb or an event, or declares an event of its own, declares
it (§59), and the manifest validator refuses such a foundation part's manifest without it, naming it. Why: the bus
module reads no list of foundation parts, which is the validator's alone (ADR 0619 §3; develop question on §59 and
§344). How it is checked: unit (manifest validator), a foundation part saying an event without `sent-objects`
refused; whole (bus module), a module of the new form gets a sent store and a foundation part none.
- §537 **A definition's included parts are resolved before they are counted.** The bus module counts a seat
definition's verbs and events after resolving the parts it includes: it reads each definition that includes parts
by the seat list's answer for that one seat, which resolves them (ADR 0636), and plans a sent store (§344) for the
seat's holders when the resolved definition has a verb or an event. Including parts alone counts for nothing.
Why: an included part may hold only shared bodies (develop question on §344). How it is checked: whole (bus module),
a seat whose verbs come only from an included part gives its holder a sent store, and one including only bodies
gives none.
- §538 **The bus module plans again when the bus-state entries change.** The bus module compares, for each module,
the bus-state entries the catalog graph entry carries with those it last planned for that module, kept in its old
`state` section `plans` (the bus module is a foundation part), one key `<module>` holding the entries as planned
and their digest, written after each plan and deleted when the module is in no watched branch's newest revision, its
entries then orphans that `orphans` lists; its tool `nats_plans` reads it. It plans again only when they differ; it never compares the manifest's digest (§506), which changes with
any field. Why: what it plans is those entries alone (develop question on §433 with §506). How it is checked: whole
(bus module), a manifest changed outside `requires` plans nothing, and a changed option plans the difference.
## The runner, the checker and mesh-sdk's helpers
- §528 **The checker built from G's heads asks no group build.** From G's delivery the pipeline is the check and the
run on request the try (§66), so the checker built from G's heads makes no group check: its group build through
tagged versions (its check's group path, `judge.go`'s, and `tagwanted.go`) goes with `tag-wanted` (§373), nothing replaces it in
the checker, and a group's builds against its package members' digests are the runner's (§373, §465). The
checker's old Node form goes with it: it no longer packs a group member's package for a Node fetch (ADR 0500 §5),
for any group, since a group's builds against its members' heads are the runner's (ADR 0573 §4). The checker's
manifest of G's heads consumes neither package registry's `published` and its asks stream carries no `tag-wanted`;
its `go.mod` requires nothing that no code of it imports; and the checker's seat definition in mesh-delivery's contracts (§645)
defines no `tag-wanted` holder event, which goes as §321 retires `tag-wanted`. Until G's
delivery the running checker makes G's own check with main's code (ADR 0667 §2). Why: §66 and §373 (develop
questions CK10 and the checker developer's three after it, batch 5). How it is checked: the code review finds no
group build and no Node packing in the checker of G's heads; unit (manifest validator), the checker's manifest of
G's heads accepted without the two `consumes`; unit (mesh-contracts), the checker's seat definition holds no
`tag-wanted`.
- §534 **The wait's `step` in the runner's body.** The runner's wait body keeps `step` as the pipeline step a wait inside
a pipeline is in (`lint`, `unit`, `integration`, `composition`, `review`, `approval`), and gains the value
`pipeline`, the acting step of §203, for a wait of the entries the runner holds for its new version (§266,
`runner_waits`); `clock-from` stands beside it (§498). The watchman tells them apart by the value: `pipeline` and
every value §498 lists are acting or delivery waits, timed by their bound (§269); a pipeline step's value is a
step's wait on a person, a build node or the revision view (§73), timed as §75 has it, a wait on a person owing no
progress (ADR 0666 §24). This completes §498 for the runner. Why: one field, told apart by its value (develop
question on §498). How it is checked: unit (runner's contract), both kinds read; whole (watchman), a runner's
`pipeline` wait raises `acting-part-late` past its 30 minutes and an `approval` wait raises no `pipeline-stands`.
- §535 **A missing build seat and a seat with no holder, each read where it shows.** The runner reads that no build
seat serves an artifact's language from the catalog half of its revision view: no entry whose `acts`, the acting
rules of the seats it claims, hold a rule `build` with `on` naming that language (§323); the pipeline then ends
failed at once, naming the language and the missing build seat. It reads that a seat has no holder from the step
itself: an ask on that seat's work queue whose `step-started` is not heard within five minutes ends the pipeline
failed, naming the seat (§32). It reads neither from the seat list, which answers definitions alone (§527), nor
from any holding. Why: §32 already ends a pipeline whose step nothing starts, and the catalog half is the runner's
one view (develop question Q-RN7). How it is checked: whole (runner), a fixture language with no build seat ends at
once, naming it; a seat whose holder is stopped ends within five minutes, naming the seat.
- §539 **The runner's tools are named `runner_`.** The runner's module, `mesh-delivery-pipeline`, names its tools with
the prefix `runner_`, so the design's `runner_catch_up`, `runner_waits`, `runner_release` and `runner_health`
stand, and its first-view tool is `runner_first_views`; `<module>_first_views` (§205) carries the prefix the
module's tools carry. Why: the design names the runner's tools so throughout (develop question RN1). How it is
checked: the runner's manifest, and `mesh_search` with "runner first views" finds `runner_first_views`.
- §531 **The kept-call form in Go.** §460's form is mesh-sdk's `busretry.DoKept`, which takes the ask's key, `open` (of
the type `busretry.Open`), an optional `replaced` and the function that writes the journal, and a process's call
through it is `process.Conn.CallKeptWhileOpen`, as mesh-sdk 19fb1dce has them. Why: a helper's API is the design's
(develop question R-5). How it is checked: §460's.
- §536 **`SeatsToPassOn`'s types.** §459's function in mesh-sdk's `catalogue` takes `holdings`, `assignments` (a list
of `Assignment` with `Node`, `Module` and `Branch`), `heard` (a function of a node and a module answering a `Heard`
with `Fingerprint` and `Stopped`, and whether anything was heard) and `entries` (an `EntryReader` with
`ByFingerprint` and `OnBranch`), as mesh-sdk e0f9356b has them. Why: a helper's API is the design's (develop
question R-4). How it is checked: §459's.
## The operator's words
- §522 **The words the teller says.** The teller says, through `notify` on every channel (§514), in the operator's
words:
- `widens`, one line per entry of the pipeline's end (§335): "Widening: `<module>` `<commit7>` on `<branch>` widens
`<kind>` `<name>`.", `<kind>` and `<name>` the entry's.
- `recovery-merged` (§81): "Recovery merged: `<module>` `<commit7>` on `<branch>`, named by `<caller>`: `<why>`."
- a `forge-made` record (§170): "Merged at the forge, not through the mesh: `<repository>` pull request #`<n>` at
`<commit7>` on `<branch>`; delivered like any merge."
`<commit7>` is the commit's first seven hexadecimal characters. This completes §514. Why: the operator, 2026-11-11
12:11 CET at lead-47's terminal, approved the words as the design writer proposed them. How it is checked: unit
(teller), each of the three said in these words with its fields filled; whole (teller, router), as §514.
- §542 **An Approve given before G is that pull request's approval step.** The operator, 2026-11-11 12:33 CET at
lead-47's terminal: approval is a step whose status each module's registration sets, never a gate of its own, so
an Approve given before G counts, once G is delivered, as that pull request's `approval` step and nothing else is
special. At G's switch the gate says, for each `merges` key the old bridge had not performed, `approval-carried` on
`mesh.seat.mesh-delivery.event.approval-carried.<repository>.<pull>` (`repository`, `pull`, `commit` the head the
Approve named, `approver` the operator, `at`), declared in the seat's definition with `keep` 90 days (§396). The gate
keeps its `merges` keys, read no more, until §97's removal, so a revert's gate of before G finds them. The runner
hears the event through a durable consumer made to start from the stream's first message, so a runner started
after the switch hears every one, and keeps each in its old `state` section `pipelines` as
`carried-approval.<repository>.<pull>`, holding the commit. It takes it as the result `passed`, caller the
operator, only of the `approval` step of the delivering pipeline whose source revision takes in that pull
request's merge, as `take-in.taken`'s entries name it (each entry carrying the repository and the pull request of a
`merged` message, §493, §509), and only when the pull request's head merged is that commit; never in a run on
request and never in another pipeline at the same commit; then it deletes the key, and deletes it unused when the
seat `git` says the pull request `pull-closed`. Where the module's policy makes `approval` `required`, a pull request whose head
moved after the Approve asks a new approval, as any step whose input changed does (an `approval` step is never
carried, ADR 0666 §8); where it is `optional` or `bypass`, the carried Approve holds nothing. The merge itself is
any merge (`merge-pull`, ADR 0666 §11): the bridge performs no carried act, the gate says no `wait-held` with
`step: merge`, and the watchman writes no `merge-waits`. Every `wait-held` the runner says for an `approval` step
has its `wait-ended`, said when the step ends in any of §24's outcomes (`passed`, `declined`, `superseded`,
`cancelled`, `not asked`) or its pipeline is superseded or cancelled, so no approval wait stands without an end.
This replaces §510 and §608, and `merge` leaves §498's list. Why: the operator's answer ("you're mixing in step and
approval gates, this should be configurable"); the event, the key and the deletion are the design's (round 3,
health and events' R7). How it is checked: whole (gate, runner), a carried Approve of a head passes the `approval` step
of the pipeline taking in that pull request's merge, with the operator as caller, and no run on request at that
commit; a runner started after the switch takes it all the same; with `approval` `required`, a head moved after the Approve asks a new
approval; a pull request closed deletes its carried key; every `approval` wait-held in the whole test has its
`wait-ended`; the code review finds no `merge-waits`.
## Round 3 of the review of §480 to §515
- §543 **A build seat's holder refuses a kind its asker may not ask.** Callers stay per accepted kind (§338), and the
builder joins them as the one caller, beside the checker until §483's removal, of the kind `build`, as §601 has it
ask under its name. The grants part grants each asker publish on `mesh.seat.<seat>.accept.<queue>.<asker>` (§600)
for every queue of the seat on which a kind it is a caller of is taken, and the holder, which takes the asker from
the subject (§481), refuses an ask whose body names a kind that asker is not a caller of, answering `error` naming
the kind and the asker, and ends it with `step-ended` `failed`. On main's subject `accept.<queue>`, which names no asker and is granted to
the checker and the builder alone (§600), the holder takes only the kinds `build` and `run` and refuses any other,
`step` among them, as above; the builder's `run` there is taken until the checker's removal (§97), after which
main's subject goes (§483). This amends §338 and §600. Why: §600 puts the queue
in the subject and the kind in the body, so a subject's grant alone cannot hold a caller to its kinds (round 3,
security and upgrade, seams and tools: R1). How it is checked: unit (Go build module), a `step` published from the
indexer's account is answered `error` naming `step` and `indexer`; unit (grants part), each asker's grants are the
queues of its kinds; unit (Go, Node and image build modules), a `step` on main's subject is answered `error`.
- §544 **Named asks have a worker of their own.** For each queue the grants part makes, beside main's worker
`SEAT_<SEAT>_<QUEUE>_worker` filtered to main's `mesh.seat.<seat>.accept.<queue>` alone, a second worker
`SEAT_<SEAT>_<QUEUE>_named_worker` filtered to `mesh.seat.<seat>.accept.<queue>.*`; the two filters do not overlap,
so the work-queue stream takes both. Only a holder of G pulls the named worker, and a holder of before G knows only
main's, so no filter is ever switched and a holder of before G that comes back from away (a laptop) after the grants
part runs takes no named ask. This replaces §601's "until every node's holder of a build seat is of G the grants
part's worker keeps main's one filter", and with it the edge from the seats' holders to the grants part, which no
manifest fact gives: the order is the asker's `invokes` of `<seat>.accept.<queue>` (kind 6, from the seat's holders
to the asker, §601), and the grants part comes before every asker as the acting part of the step `grant` on
`every` (§203): each entry of a revision waits for the grants part's new version to act for it (§266), so no asker
of G runs before a grants part of G has made its named worker and its grant. A holder of G pulls main's worker
alone until its membership names the named worker, which the grants part writes as it binds every consumer
(§386), and says meanwhile, in its journal and its read tool, `named worker not bound`. A named ask waits on its worker while no holder of G
runs, said by `build-capacity-waits` after 60 minutes, which corrects §483's "waits on the work queue, whose
consumer of before G takes only main's subject". At a revert, the grants part of G takes its view of the revert's catalog revision before
it is itself reverted, since it acts for every entry of it (§203); at the first revision in which no entry `invokes`
any `<seat>.accept.<queue>`, it purges the subjects `accept.<queue>.*` of the stream and deletes the named workers,
within its granting bound of 10 minutes (§266), saying each in its journal. Where that did not run (the grants part
stopped first), its tool `grants_named_purge`, of class `act` with §391's default callers and written in the
hand-act log, does it by hand; an ask is transport,
not kept data (as §59 has a sent object). Why: one consumer per filter, and a filter that never moves cannot be
pulled by a holder that does not read it (round 3, R2, the security reviewer's form). How it is checked: whole
(grants part, Go build module), a holder of before G started after the grants part of G takes main's asks and no
named one; a revert leaves no named worker and no named ask; a holder of G before its binding pulls main's worker
and says `named worker not bound`.
- §545 **The tool runner's artifact declares the words it states.** The artifact entry of the tool runner's manifest
takes `states`, the capability words (§488) it states; the manifest validator refuses a package artifact's `calls`
word (§605) that no artifact of the revision `states`, naming the word, and the delivery plan's edge of §605 runs to
the module whose artifact `states` that word, so no manifest names the tool runner's module. A bundle is started by
the tool runner on its channel and ends with it, so a restart of the tool runner restarts its bundles, and each
reads the stated words at its own start; no event is heard and nothing polls. This amends §605's "each time the
tool runner of its node starts". Why: the words are stated only at run time, and the plan needs them in a manifest
(round 3, seams and tools: R3). How it is checked: unit (manifest validator), a `calls` word nothing `states`
refused; unit (tool runner), the words it states are exactly its manifest's `states`; unit (delivery plan), the edge to the module stating the word.
- §546 **When the builder forgets a node's toolchain.** The builder deletes its `toolchains-heard` key
`<node>.<language>` (§604) when the node-engine seat's `running.<node>.<module>` says `stopped` (§365) for the
module whose catalog graph entry claims the build seat of that language (its `acts` rule `build` with `on` naming
it, §535), heard through its consumer of that seat's `running`, read as §556 has it; never on a placement, which
is the seat `mesh-module-assigner`'s state. The state is an old `state` section of the builder, a foundation part,
one key per node and language, about 12 keys of about 0.3 KiB each, capped at 1 MiB, so it adds no objects store
to §607's count (§58 plans one only for a state of the template); `builder_toolchains` reads it and
`builder_toolchains_drop`, of class `act`, taking its default (§610), deletes one key,
with why, written in the hand-act log of the seat `mesh-hand-acts`; a
holder says the key again at its start and on each change (§492). This amends §604's "when the node leaves the
catalog's placements". Why: the builder reads no other module's state (ADR 0596 §2; round 3, seams and tools: R4).
How it is checked: whole (builder), a build module stopped on a node deletes that node's key, and the drop tool
deletes one key and logs it.
- §547 **The sum of the bus's caps is read against its limit.** Measured on 2026-11-11 against an embedded bus
(nats-server 2.11.17): an account's storage limit reserves each stream's maximum size when the stream is made (a
second stream of 64 MiB under an account limit of 100 MiB is refused, "insufficient storage resources"), and a
server's own limit does not. So each `<state>-objects` is planned with 16 MiB (about 2.4 GiB for 150 states), a
state needing more naming `objects-max-mib`, and no later measure is waited for. The bus module reads the account's
and the server's storage limits at its start and at each plan, sums the cap of every entry it plans (states, objects
stores, sent stores, streams, logs) and of every old section the catalog graph's entries declare, and shows the sum and
both limits in its `list` and its read tool. A plan whose sum would pass the account's limit is refused, naming the
sum and the limit, said as its `refused` (ADR 0595 §6): the plan refused is the one that adds or raises a cap, and
the caps already made stay as they are. With no account limit (unlimited) nothing is refused and the sum is shown
against the server's limit. This amends §607's 64 MiB and its measure in G's check. Why: the limit is a
fact of the server, read rather than written in the design, and a sum over it fails loudly (round 3, running and
testing: R5). How it is checked: whole (bus module), a fixture bus with a small account limit refuses the plan
that would pass it, naming the sum, and keeps the caps made; with no account limit nothing is refused; the sum and
the limits are in `list`.
- §548 **The forge module writes `seen` only from a pipeline's steps.** §487's `seen.<commit>.<module>` is written only
from a `step-ended` that carries a pipeline's `commit` and `fingerprint`, which a pipeline's step carries; the
`step-ended` of any other ask (§602) carries `asker`, `ask`, `node`, `outcome` and `at` and neither, so the forge
module writes nothing from it and names no asker. This amends §487.
Why: since §602 every ask says `step-ended`, and a builder's build is no run of a commit (round 3, health and
events: R6). How it is checked: unit (forge module), a builder's failed build writes no `seen`; whole (watchman), a
`stuck` raised for an ask clears on its late `step-ended`.
- §549 **Smaller corrections from round 3.** At its start the runner appends an `unlogged` entry (§606) only after
reading back its `step-log`'s last entry of that pipeline and step, and skips it when that entry is already
logged, so a stop between the append and the clear appends nothing twice. `step-progress` and `ask-taken` (§603)
name no `keep` and go on EVENTS, kept 7 days (§396). `runner_unlogged_append` is of class `act` and takes §391's
default callers. §58 is planned with the cap §607 and §547 give. How it is checked: whole (runner), a runner
stopped between the append and the clear logs the entry once; the rest as the lines they correct.
- §550 **A publication's pipeline keeps the version.** The runner's pipeline value gains `version`, the version
`package-published` names, for a pipeline the runner started from a publication or from `outdated` (§334), and
none for any other; `show` answers it. This completes §334's "the version in the pipeline's state". Why: the
runner's contract had no field for it (develop's note on §334). How it is checked: unit (runner's contract), the
field read; whole (runner), a publish's pipeline shows its version.
## The operator's answers of 12:41 CET
- §551 **A restore point is removed after 90 days, never the newest.** The operator, 2026-11-11 12:41 CET at lead-47's
terminal. The recovery module removes a restore point's directory (§516) 90 days after its `at`, as long as delivery
records are kept, unless it is the newest point of its module and kept data, which it never removes; it checks at
its start, once a day and at each `snapshot-kept`, on its own clock and its own directory, as one of its acts, never
beside a `restore-kept` (§554). Each removal is written in the hand-act log of the seat `mesh-hand-acts` and said as
`mesh-recovery`'s event `point-removed` on `mesh.seat.mesh-recovery.event.point-removed.<module>.<kept>` (`module`,
`kept`, `form`, `fingerprint`, `snapshot`, `at`), kept 90 days (§396). The backup holder's copies go by its
repository's own rotation (ADR 0235 §4). Data set aside is removed only by `recovery_discard_aside` (§554). This completes
§516 and answers issue 1921's note on the retention of restore points. Why: the operator's answer; the event, the
checks and their order are the design's. How it is checked: whole (recovery module), a point older than 90 days is
removed and said, the newest point of a kept data older than 90 days stays, and a removal due during a restore runs
after it.
- §552 **The checker may drop its own asks until its removal.** The operator, 2026-11-11 12:41 CET at lead-47's
terminal: the build seats' verb `drop` (§338) gains the checker as a caller until its entry is removed with its
buckets and bus account (ADR 0667 §12), as `run` has it, so the validator accepts the checker's manifest's
`invokes` of `seat:node-build-go.drop` and `seat:node-build-node.drop`. This amends §338. How it is checked: unit
(manifest validator), the checker's manifest accepted; whole (grants part), the checker's drop granted and another
module's refused.
- §553 **A step status left empty is `optional`.** The operator, 2026-11-11 12:41 CET at lead-47's terminal: a step
whose status a registration's policy (module-level `policy.steps`, or an artifact's) leaves empty is read as
`optional`: it runs, and a failure is shown and holds nothing (§23); `register` and `policy` accept such a policy
and refuse nothing for it, and the runner, `registrations` and `show` say the step `optional`. This completes §23.
How it is checked: unit (runner), a step with no status runs and its failure holds no want; unit (mesh-catalog's
contract), a registration with an empty status accepted.
## Questions an earlier line answers
These write nothing new; develop reads the line named.
- The tool runner's fallback of local commit 1015b38 for a foundation part's bundle goes: §407 binds that bundle to
its old `objects` section, and a binding that is missing fails loudly (ADR 0595 §1) (Q-T5, T-11).
- The re-read tool's callers of `seatlist.Part` are §204's for every part, the assigner's own list going (AS19).
- The bus module's cap of a state's objects store is §513's as §607 and §547 amend it, and the binding's `objects`
and `sent-objects` are §513's (NATS1, §399).
- The contracts' `Hold` is §194's view, the newest assignment half and the catalog half it names; the candidate's
whole-half reader is §195's and §441's, read by a run on request's steps, `grants-for` and the indexer's candidate
pass (contracts).
- The view's module value carries the entry's `manifest-digest` (§506) and computes none (VW1).
- `store-filling` is raised past 80 % and cleared below 70 % (§181).
@@ -0,0 +1,124 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0656"
- "ADR 0603"
- "ADR 0596"
- "ADR 0595"
- "ADR 0584"
- "ADR 0564"
- "ADR 0536"
- "ADR 0525"
- "ADR 0436"
- "ADR 0363"
---
# 47, milestone G: the review of §480 to §515, rounds 2 and 3
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n"; this file
takes §600 on, since §516 on are those of batch 4's file G-12.
*These lines answer what the four reviewers by architectural domain found in round 2 of
[G-11](G-11-develop-answers-review.md) (§480 to §515), the last round. Each is the design's choice with its reason.*
- §600 **A build seat's asks: subjects, workers and grants.** (§543, §544) A build seat (`node-build-go`, `node-build-node`,
`node-build-image`) keeps its three queues of main, `delivery`, `check` and `recheck` (ADR 0525 §4, ADR 0536 §4), on
its one work-queue stream. An ask is published on `mesh.seat.<seat>.accept.<queue>.<asker>`, `<queue>` one of the
three, `<asker>` the asker's name; the ask's kind (`step`, `run`, a build) stays in its body, as on main. The
stream takes, per queue, main's `mesh.seat.<seat>.accept.<queue>` and `mesh.seat.<seat>.accept.<queue>.*`, which
overlap no other queue's. Its workers stay the grants part's, one per queue (`SEAT_<SEAT>_<QUEUE>_worker`, as on
main), each filtered to both of its queue's subjects, so one consumer per queue takes an ask in either form and
every node's holder pulls it, as ADR 0436 §6 has one consumer. The grants part grants publish on
`accept.<queue>.<asker>` to that asker's account alone, each asker by the queues §338 gives it, and on main's
`accept.<queue>` to the checker's and the builder's accounts alone, until §483's removal. This amends §481's
subject and its "the seat's stream takes `accept.>`". Why: one stream refuses overlapping subjects and a work
queue has one consumer per filter (review round 2, security and upgrade 1 and 3, running and testing 1). How it is
checked: unit (grants part), each queue's worker filters both subjects and each asker's grant is its own name;
whole (grants part, Go build module), an ask under `runner` is taken and answered, and the runner's account
publishing on main's `accept.check` is refused.
- §601 **The order of G's rollout for the build seats.** (§544) The holders of the three build seats are delivered first,
then the grants part, whose workers then filter both forms (§600), then every asker of G (the runner, the indexer,
the assigner, the builder) and the watchman. Until every node's holder of a build seat is of G the grants part's
worker keeps main's one filter, so a holder of before G never takes a named ask, whose key it would write in main's
form. Each order is a served-verb edge (kind 6) the delivery plan derives: an asker names each queue it publishes
to in its manifest's `invokes` as `<seat>.accept.<queue>`, which the manifest validator checks against the seat's
definition, and the edge runs from that seat's holders to the grants part and from the grants part to the asker;
the watchman's `consumes` of the build seats' `step-progress` (§496) gives the edge from those holders to it, as an
event G adds. A holder is counted delivered once `version-delivered` (§376) says every present machine placing it
runs it. A revert runs the same edges backward (ADR 0656 §4). Until the watchman hears a holder's `step-progress`,
it times that holder's ask from its `ask-taken` (§602), and a body of before G with no `step` or `clock-from`
(§498) is timed from its own `at` with no step's own finding. This amends §483's "whose consumer of before G takes
only main's subject" and its edge's source. Why: one consumer is shared by every node's holder, so the filter moves
only once every holder reads both forms (review round 2, security and upgrade 2 and 4, seams and tools 2, health
and events 2). How it is checked: unit (delivery plan), the edges from `invokes` and `consumes` are derived and G's
plan names them; whole (grants part, Go build module, runner), a rollout with one holder of before G keeps main's
filter, and the runner's ask is answered once every holder is of G.
- §602 **An ask outside a pipeline has a start and an end.** A build seat's holder says `ask-taken` (`asker`, `ask`,
`node`, `at`) on `mesh.seat.<seat>.event.ask-taken.<node>` when it takes any ask, and `step-ended` when it ends any
ask, whatever its asker (§24's event, the asker in its body); a reader of `step-ended` takes only its own asks by
their ids. The watchman times every ask from `ask-taken` and its `step-progress` (§496), and ends the clock on
`step-ended`. A runner's step that stands raises `pipeline-stands` (§75); any other ask that stands raises
`stuck`, naming the seat, the asker, the ask and the node, as main's mirror does. This completes §496. Why: only the
runner says `step-started` (review round 2, health and events 1). How it is checked: whole (watchman, Go build
module), a builder's build whose holder stops raises `stuck` within five minutes and one that ends raises nothing.
- §603 **Where `step-progress` and `ask-taken` go.** (§549) Both are seat events and go on the stream that carries the seat's
events, at that stream's own age; no stream is added and no event's age changes. `step-progress` and `ask-taken`
are declared in the three build seats' definitions in `mesh-delivery-contracts`, and `step-progress` in
`mesh-wants`' definition for a composition step. This amends §496's "kept on the seat's stream for a day". Why:
a build seat's own stream is its asks' work queue (review round 2, running and testing 4, seams and tools' note).
How it is checked: unit (definitions), each event declared.
- §604 **The builder keeps the toolchains it heard.** (§546) The builder writes each `toolchain-reported` (§492) in a state of
its own, `toolchains-heard`, one key `<node>.<language>` holding the version, the recipe, the digest and when it
was said, and agrees the version from those keys, read back at its start, so a restart needs no node to report
again. Its manifest `consumes` the Go and node build seats' `toolchain-reported` (§702); its tool `builder_toolchains` lists the
keys; a key is deleted when the node leaves the catalog's placements (§365's reason). Why: a durable consumer does
not deliver an acknowledged event again (review round 2, seams and tools 1). How it is checked: whole (builder), a
restarted builder agrees the same version with no new report.
- §605 **The order after G's tool runner is derived from the package a build resolved.** (§545) A package's artifact in a
manifest takes `calls`, the capability words (§488) its code calls of the tool runner; mesh-sdk's Go and npm
packages name `ledger-upkeep-calls`, `sent-objects-calls` and `put-sessions` from G. For each module whose build
resolved a version of such a package (§466's `resolved`), the delivery plan derives a served-word edge (kind 6) to
the tool runner stating those words on each node, so no manifest names the tool runner's module. mesh-sdk's
bundle reads the stated words again each time the tool runner of its node starts. §494's release reads `calls`.
This amends §488's `needs`. Why: the bundle makes a session whenever a value is large, so only the package knows
(review round 2, seams and tools 3, running and testing's note). How it is checked: unit (delivery plan), a module
built on G's mesh-sdk is ordered after the tool runner on each node; unit (manifest validator), `calls` read.
- §606 **A step's end and its unlogged entry are one write.** (§549) The runner writes a step's end in `pipelines` with the
entry as `unlogged` in the same compare-and-set, appends it to `step-log`, then clears it; at its start it appends
every `unlogged` entry of its pipelines, in order. It reads the log's fill again at each step's end, at its start,
and once a minute while the log is `full`, so the entries the bus ages out say `room` with no step ending. Its tool
`runner_unlogged` lists the unlogged entries and `runner_unlogged_append` appends them by hand, written in the
hand-act log. This amends §486. Why: a stop between the two writes lost the entry, and only the bus's age frees a
full log (review round 2, running and testing 2, health and events 3, seams and tools' note). How it is checked:
whole (runner), a runner stopped after a step's end appends its entry at start; a full log whose oldest entries age
out says `room` with no step ending.
- §607 **A state's objects store has the state's size.** (§547) Each `<state>-objects` (§58) is planned with the state
template's size, 64 MiB, unless the state's `requires` names `objects-max-mib`; the sent stores keep §477's 256
MiB. An estimated 150 states across about 120 modules give about 9.4 GiB of caps. Whether the bus counts a cap
against its storage limit before anything is written is measured in G's check (the bus module's whole test reads
the account's reserved storage after its plan); if it does, the plan refuses caps whose sum passes the limit,
naming the sum, as §495 has it for sent stores. A write refused at a cap names that store, `<state>-objects` of
its owner. This amends §513 and leaves §58's "reserves nothing" to that measurement. Why: §58 names the template's
size (review round 2, running and testing 3). How it is checked: unit (bus module's plan), each objects store's cap;
whole (bus module), the reserved storage read after the plan.
- §608 **A carried merge that waits is a timed wait.** (§542) A merge act carried by the switch (§510) waiting for the first
registrations is said by the gate as `wait-held` with `step: merge` (added to §498's list), naming the pull request
and the head the Approve named, bounded at 60 minutes; past it the watchman writes `merge-waits`, which the
condition keeper raises and `wait-ended` clears. It merges only the head the Approve named: a pull request moved
since is refused, said as the delivery's `transition` with the why `head moved`. This amends §510. Why: an Approve
the operator gave must not wait unseen (review round 2, health and events 4, security and upgrade's note). How it is
checked: whole (gate, watchman), a carried merge waiting 60 minutes raises `merge-waits`; a moved head is refused.
- §609 **Smaller corrections from round 2.** An entry taken in before G carries as `manifest-digest` the digest its
module value held (§506). §508's check adds a revision moving the `node-engine` claim to another module, accepted
and said in the catalog's `catalog-revision`. `watchman_views` (§501) answers from the keeper's `view-waits` alone,
so a ready view is not listed and the watchman calls no part's tool. §485's reconnect case is a whole test of
mesh-sdk, and `observe` must not call `write`, since the writer runs one act at a time. A node-engine of before G says
no wait for a kept build; that is accepted, `kept-build-waits` naming its version (§499). The kept-data row of
`step-log` reads an old `logs` section (§470). How it is checked: as the lines it corrects.
@@ -0,0 +1,218 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0259"
- "ADR 0672"
- "ADR 0436"
- "ADR 0667"
- "ADR 0666"
- "ADR 0660"
- "ADR 0656"
- "ADR 0636"
- "ADR 0626"
- "ADR 0619"
- "ADR 0618"
- "ADR 0614"
- "ADR 0596"
- "ADR 0595"
- "ADR 0564"
- "ADR 0505"
- "ADR 0404"
- "ADR 0363"
- "ADR 0240"
- "ADR 0235"
---
# 47, milestone G: the recovery module, and the review of §516 to §553
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§554 and §555 are the operator's answer of 2026-11-11 at 12:55 CET at lead-47's terminal. §556 to §558 answer the
four reviewers' round 1 of [G-12](G-12-develop-answers-fourth.md) (§516 to §553); the rest of that round's findings
are written into the lines of G-12 they name.*
## The recovery module
- §554 **The recovery module takes, sets aside and restores kept data on the live bus.** The operator,
2026-11-11 12:55 and 13:02 CET at lead-47's terminal: a module of its own, on a seat of its own, with an account of
its own, owns snapshotting, setting aside and restoring a module's kept data on the live bus, each act in the
hand-act log; the bus module keeps no restore permission but a protected snapshot's restore in place (§681, O-7), the snapshot account stays
limited to the nightly dump (ADR 0626 §2), and the backup holder only stores copies. ADR 0672 decides the account,
its grants and its callers; this line is written to it.
- **What it alone does.** It alone takes a restore point before a module moves across a `form` change, and alone
restores into the live bus. The bus module's snapshot before its own `recreate` or `drop` (ADR 0595 §5, ADR 0626
§4) and the nightly dump (ADR 0626 §2) stay theirs. The restore ADR 0667 §12 tests of the snapshot `drop` takes is
the recovery module's `recovery_restore` naming that snapshot, which it reads through the seat `mesh-broker`'s
verb `snapshot-get`, or has the keeper restore in place by `snapshot-restore` when it is protected (§681).
- **Its seat, module and node.** The seat `mesh-recovery`, held once for the mesh, is defined in mesh-contracts
beside `mesh-broker`, and the module `mesh-recovery`, in mesh-catalog, claims it; its glossary word is the
*recovery module*. It runs on the node of the holder of `mesh-broker`, read from the catalog half and the seat
`mesh-module-assigner`'s placements, never a fixed name: G's seed writes its placement there. It moves as any
module that holds data, its points and asides holding no move (§562). Its data directory, its manifest's `data`
item `points`, is capped by its setting `points-cap` and measured by the disk readings (ADR 0571 §5): past its fill
mark it writes the finding `points-filling` (§561), and an act that would pass the cap ends `failed`, naming it,
before it removes anything; a `snapshot-kept` that ends so holds the move as a failed backup does (§296, §564). Its `findings` state and finding stream are declared as §529's other parts declare
them, so it waits on the bus module for them and is no foundation part (ADR 0619 §1).
- **Its verbs answer at once; the result is the event.** `snapshot-kept` and `restore-kept` answer `accepted` with
the act's `act` id, `busy` with the act that runs (it runs one act at a time and keeps no queue), or `error`, and
nothing more; the point comes on `ended`, the restore on `restored`. A caller told `busy` asks again when it hears
the ending event of the act it was named (§560). The module writes each accepted act in its directory before it answers; at its start an act
accepted and not ended is ended `failed` with `stopped`, so its caller asks again.
- **Its events.** On `mesh.seat.mesh-recovery.event.` and kept 90 days (§396): `ended`,
`restore-started`, `restored`, `set-aside`, `aside-discarded` and `point-removed`, each with the parts
`<module>.<kept>` and kept last per subject, and `expiry-ended`, which has no parts and is not (§691), their fields as §560 gives them. The node agent wakes only on the ending
event of its own act, and its wait for `ended` or `restored` is bounded at 90 minutes from its first ask (§405,
§560), past which its have says the wait and the watchman's `delivery-waits` raises it.
- **Who calls it.** `snapshot-kept` and `restore-kept` are of class `act` and take its default (§610, the operator's
answer of about 16:10 CET: no verb keeps a named list): the bus proves who called, every act is logged with it,
and the identity of who asked is the check (the operator, 13:06 CET); no want is read for it, since the operator,
2026-11-11 13:10 CET: "if something asks for a restore, you should do it; he'll have his reasons".
`kept-points` takes the default of a read (§391). The operator, 2026-11-11 13:06 CET at lead-47's terminal: setting
aside is a move, not a removal, so deleting the live objects once their aside is whole and checked (§516) needs
no person's warrant, the data staying in the aside until the operator discards it; setting aside without restoring is
the operator's hand tool `recovery_aside`, and discarding an aside the hand tool `recovery_discard_aside`, both of
class `act`, taking its default (§610), written in the hand-act log first, each a verb of the seat (§656); `restore-kept` refuses
`aside-only` and `discard-aside`, naming them (§516). The hand tools are checked by the identity of who asked: each takes its class's default (§610), the bus proves the
calling account and the verb logs it with the `asker` the tool runner proved (§682), with no warrant asked per act. Each act is written in the hand-act log of the seat `mesh-hand-acts` first, with the caller the
bus proved and `why`; an act whose log write fails does nothing and answers `error`, naming the log (as §480).
- **The operator's hand tools.** `recovery_take` (take a point of one module and kept data now), `recovery_restore`
(restore one point) and `recovery_retry` (ask again the last failed act of a module and kept data), each of class
`act`, taking its default (§610), written in the hand-act log, each a verb of the seat (§656); they are checked by identity, as above.
§566 adds `recovery_restore_aside` and `recovery_cancel`.
- **Its account.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal (the knowledge base's review of
ADR 0672, R3): the recovery module is one process with a bus account of its own (ADR 0436 §1, as ADR 0626 §1 has
the bus module's keeper), which holds the seat `mesh-recovery` and answers its verbs itself; each verb is one its
caller names (`by-caller`, ADR 0259 §3), so the bus proves who calls, each taking its class's default (§610): a
node-engine's account for `snapshot-kept` and `restore-kept`, and the tool runner's account, which the sessions and
the operator reach the bus as, for its hand acts, are the callers it serves in practice. Its tools bundle, carried by the tool runner, calls those verbs and holds no grant of its own. ADR 0672
§4 records that this changes ADR 0436 §3 and §5 for this process alone. The account is composed by the grants part
from the seat's definition and the process's `bus` declaration: the bus's stream
snapshot and restore subjects with their chunk subjects, a stream's deletion and a durable consumer's creation,
only for the bus names of a module's kept data; the subjects its seat says and its hand-act log write; its
consumers of `node-backup`'s `item-started` and `item-ended`, filtered to those two events, each with the ordered
consumer §556 needs, from the process's `consumes`; its calls of `node-backup`'s `restore`, which takes its class's default with no own-item limit (§610, §555), and
of `mesh-broker`'s `snapshot-get` and `snapshot-restore` with `snapshot-get`'s chunk subjects, and the hearing
of `snapshot-restored` from its `consumes` (§681); and nothing else. The bus names granted are those of every kept data
of every catalog graph entry the catalog half keeps (§443) and of every entry a restore point names (an old
section's name before a form change, a template's binding after), read by the grants part through
`kept-points` (§565); no stream of the mesh's own (`CONTROL`, `NODES`, `EVENTS`, `DEAD_LETTER_NOTICES`, `DEAD_LETTERS`,
`PROV_APPLIED`, `CHECKS_READY`, `CHECKS_OTHER`, every `SEAT_` stream and every `KV_` bucket of a seat) is ever
granted. Restricted streams (ADR 0436) are among the names, as ADR 0672 records.
- **Bus names.** The recovery module turns a module and a kept data into bus names from the catalog graph entry of
the fingerprint it acts at, read by `mesh-catalog`'s `entry` (§443) in its `invokes` (an edge of kind 6): a
template entry's name is `<module>_<local>` (ADR 0595 §1), an old section's its declared name; a point records the
names it took, and a restore uses those.
- **Never beside the nightly dump.** A `snapshot-kept` may run while the dump runs, both paced as ADR 0235 §10 has
it, so the server has at most two snapshots at once. No `restore-kept` starts while the dump runs, and no dump of
the bus starts while a restore runs: the holder of `node-backup` of G says `item-started.<node>.<module>.<item>`
and `item-ended.<node>.<module>.<item>` for every item, kept last per subject, 7 days, and puts off the bus's item
while a `restore-started` of the recovery module has no `restored`, the dump giving way when both start at once
(§563); the recovery module learns the bus module and
its node from the seat list's definition of `mesh-broker` and the catalog half's claim of it, never a fixed name.
Both read these streams as §556 has it, so a restart mid-dump or mid-restore misses neither. A blocked act waits,
neither refused nor failing a want: past 60 minutes the recovery module writes `recovery-waits` and the backup
holder `backup-waits`, each naming the item or the act it waits on, and `kept-points` lists the act that runs
with since when and what it waits on. The recovery module consumes the item events that G's backup holder adds,
an edge of kind 6 from that holder to it, so a backup holder of before G, which says none, is never relied on.
- **Its health.** Its declared health check names the tool `recovery_kept_points` (§657), and fails only while an aside's record ends at
`removed` (§516) and no running act holds it, so a restore under way never fails it; it then also writes the
finding `aside-unrestored` (§561), which `recovery_restore_aside` (§566) clears.
- **Its order.** The node-engine's `invokes` of its verbs gives the edge of kind 6 from the recovery module to the
node-engine, so §298's first edge reads "the new version of the recovery module" (§555). At a revert of G the
edge runs backward, as the order does when something goes away (ADR 0666 §14): the node-engine goes back only after every module it restores there said
`restored` and runs (§298), and the recovery module, new in G, goes only after the node-engine, so it stays while
any restore runs; its data item stays on its node, with its copies.
- **Its tools** are named `recovery_`.
Why: the operator's answers; the node, cap, events, answer on acceptance, dump order, health and edges are the
design's (review rounds 1 and 2; ADR 0672). How it is checked: test 36 with the real backup holder and the recovery
module; unit (grants part), the recovery module's account holds exactly the grants above, a call outside its names
refused, both a point's old-section names and a template's binding granted, and the bus module's and the snapshot
account's hold no restore; whole (recovery module), a hand act's log naming the account the bus proved and the asker, a
`busy` answer then the act on the other's `ended`, a second form change not woken by the first's `ended`, a dump
asked during a restore starting after `restored`, a restart during the dump starting no restore, an act whose
hand-act log write fails doing nothing, an unrestored aside failing the health check, a node-engine's `restore-kept` with `aside-only` or `discard-aside`
refused, and the operator's `recovery_discard_aside` removing the aside and writing the log.
- §555 **What moves to the recovery module.** §296 asks `mesh-recovery`'s `snapshot-kept` and waits for its `ended`,
never the seat `node-backup`; §297 asks its `restore-kept` and waits for its `restored`; §302's `backed-up` lists
the recovery module's data item as any item, and the restore points and asides are listed by `kept-points` (§517),
an aside discarded by the hand tool `recovery_discard_aside` (§554); §303 and test 36 name the recovery module where they named the backup
holder and the bus module; §337's and §405's callers `holder:node-backup@same-node` go with the two verbs of
`mesh-broker`, which gains only §681's `snapshot-get` and `snapshot-restore`; the seat `node-backup` gains nothing for kept data on the bus: its `aside` and
`discard-aside`, the arguments `kept`, `form` and `fingerprint` of its `now` and `restore`, and its events
`ended.<node>.<module>.<kept>` and `restored.<node>.<module>.<kept>` go from its definition in mesh-contracts, and
it gains only `item-started` and `item-ended` with their `<item>` token, its `restore` taking its class's default
with no own-item limit (§610); §450's `aside` answer is `recovery_aside`'s;
§406's catch-up is recorded in the point as before. Why: one owner of the acts (§554). How it is checked: unit
(definitions), `node-backup` and `mesh-broker` hold none of the moved verbs, `mesh-broker` only §681's two; test 36.
## The review of §516 to §553, round 1
- §556 **A part that follows a stream kept last per subject reads it whole at each start.** A durable consumer goes on
from its acknowledged position after a restart, so a part that acts on what it heard from a stream kept last per
subject, and keeps no state of it, reads at each of its starts every subject's last message through an ordered
consumer it makes for that one read, then follows its durable consumer from its acknowledged position, an event
older by `at` than what that read gave changing nothing. Until the read ends the part acts on none of those events
and its `<module>_first_views` names the stream `waiting`. So read: the node-engine seat's `running`, `composed`,
`wait-held` and `wait-ended` (the last two kept on the stream `running` is kept on, last per subject, a node's
module's newer one by `at` standing), by the assigner (§520), the condition keeper (§541), the builder (§546), the
node-engine and the grants part (§199), and the watchman for `delivery-waits` (§541); `mesh-healers'`
`attempt-changed` by the keeper (§541); `node-backup`'s `item-started` and `item-ended` by the recovery module, and the recovery module's `restore-started` and `restored` by the backup holder (§554). The
grants part grants each such reader, beside its durable consumer, the creation of an ordered consumer on that
stream with no durable name (the bus's consumer-create subject for that stream), from the reader's `consumes`. This amends
§199's "each part's durable consumer starts from the last message per subject". Why: what a part heard must not
depend on when it last restarted (review round 1, health and events 2, with running and testing 3). How it is
checked: whole (assigner, keeper), a restarted assigner holds no want it did not hold before, and a restarted
keeper holds the healers' attempts it held.
- §557 **A whole test's bounds are settings.** Every bound a whole test waits on (§535's five minutes, §534's 30
minutes, §544's 60 minutes, §540's two hours, §551's 90 days, §554's 90 minutes) is a setting of the part that
times it, read at its start, its default the design's value, so a whole test sets it in seconds and never waits on
real time. The bus module's and the recovery module's whole cases in these lines are cases of each module's one
whole test against the real bus, within the test budget. Why: a whole test that waits hours runs never (review
round 1, running and testing's notes). How it is checked: the code review; each whole test of these lines runs in
under a minute.
- §558 **A field of a served verb's answer is an edge of kind 6.** Kind 6 (§374) orders a module after a version that
serves a verb or a word it uses, and a verb's presence alone says nothing of a field its answer gained, so a
consumer relying on a new field of a served answer could run before the provider's new version and fall back in
silence (as the grants part's first view relied on `binding` in `mesh-broker`'s `list` before design 25's §9). An `invokes` entry therefore
takes a long form, `{verb, fields}`: `verb` as today (`seat:<seat>.<verb>`), `fields` the names of its answer's
fields the consumer relies on. The manifest validator refuses a field the seat's definition does not give that
verb's answer, naming it. The delivery plan derives the edge of kind 6 from the holders of that seat to the
consumer, held until the holder runs a build whose catalog graph entry's seat definition (§194) gives the verb's
answer every named field; §374's entry of kind 6 records the verb and its fields, and `outdated` (§375) says it
behind when the delivered version no longer gives them. Both forms stay: the short string where no field is relied on, the long form
where one is. On main mesh-sdk's manifest reader reads `invokes` as a list of strings, so the long form is read
first by §494's second reader release, merged and delivered before G's merge: its manifest reader takes either
form, and the running checker, the indexer and the grants part, which read manifests through it, are rebuilt with
it by the release's publish (kind 1), as for `max-mib`. That publish rebuilds every module importing mesh-sdk's
`catalogue`, the set the release rebuilds already, so the form adds no rebuild. The consumers of G that name fields:
every reader of `mesh-catalog`'s `revision` through
mesh-sdk's client (the bus module, the status view, the condition keeper, the tool runner, the assigner, the grants
part and the runner), with `none-yet` (§530); the status view, `mesh-catalog`'s `entry` with `manifest-digest`
(§506). The grants part names none: design 25 *bindings-first-view* §9 composes a binding from the fields `list` answered before G, so it relies on no
new field of `list`, and naming `binding` would close the circle of §569. The recovery module reads the manifest whole the entry carries (§443), a field of before G. Any other part of G relying on a field G adds to a served answer names it so in G's change, which the
code review checks. Why: the module graph orders a consumer after what it relies on, and a field is part of the contract
(lead-47's question from the operator's "the module graph is for this", 2026-11-11). How it is checked: unit
(manifest validator), a field the definition lacks refused; unit (delivery plan), a consumer naming a field
ordered after a holder whose definition gives it, and not before; unit (the release's reader), both forms read.
- §559 **A bundle's finding writer publishes and hears through two runtime calls.** A bundle, in Go or TypeScript,
holds no bus connection, so mesh-sdk's finding writer (§461, §503) in a bundle uses the runtime channel's existing
calls, each gaining one argument: `mesh/publish` takes `ack: true`, and the tool runner then publishes the event on
the subject the module's membership names for it (its old stream's `mesh.stream.<module>.event.<event>.<node>` or
its template binding's, §529; ADR 0363 §8) and answers only once the bus acknowledged it, with its sequence, or
`{error}`; `mesh/subscribe` takes `deliver: new`, and the tool runner makes an ordered consumer on the stream that
carries the pattern, starting at the next message, and delivers each message to the bundle as a subscription
delivers today. The writer says each finding event with `ack: true` (§383's one minute of resending counting from
an unanswered or `{error}` publish) and makes its consumer of `findings-asked` with `deliver: new` before its start
statement (§485). The tool runner states the capability word `finding-calls` (§488, §545) once it answers both,
and the bundle's writer fails at once, naming the word and the node, where it is not stated. Why: one writer in both
languages over the one channel a bundle has, as §518 (develop question of 13:08 CET on §461). How it is checked:
unit (tool runner), a publish with `ack` answered with its sequence and a refused one with `{error}`, a `deliver:
new` subscription given no message older than it; whole (mesh-sdk, TypeScript and Go bundles), a finding written
and an ask answered through a tool runner of G; unit (mesh-sdk, both languages), the writer fails at once, naming
`finding-calls` and the node, against a tool runner that does not state it.
@@ -0,0 +1,637 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0673"
- "ADR 0672"
- "ADR 0668"
- "ADR 0618"
- "ADR 0667"
- "ADR 0666"
- "ADR 0664"
- "ADR 0626"
- "ADR 0619"
- "ADR 0596"
- "ADR 0595"
- "ADR 0573"
- "ADR 0564"
- "ADR 0539"
- "ADR 0500"
- "ADR 0429"
- "ADR 0404"
- "ADR 0283"
- "ADR 0259"
- "ADR 0235"
---
# 47, milestone G: develop's fifth batch, and round 3's notes on §516 to §559
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§560 to §586 answer develop's fifth batch (lead-47's input of 2026-11-11), the reviewers' round-3 notes on §516 to
§559, the knowledge base's review of ADR 0672 (the recovery module), and round 1 of this batch's own review, written
into the lines it names. Seven carry the operator's answers of 2026-11-11 at lead-47's terminal, each line saying
so: §562 (the module's move), §563 (a put-off dump), §568 (the builder's retry), §570 (refused asks), §575 (a failed
build's words), §584 (the forge and image registry in the foundation) and §554's account. The rest are of internal
form only. A note that corrects a line of G-12 or G-14 is written in that line; here only what is new.*
## The recovery module
- §560 **Every event of the recovery module says its act, and its caller wakes on its own act.** (§554, §516, §551)
Every event of the seat `mesh-recovery` carries `act`, the id `snapshot-kept` or `restore-kept` answered with
`accepted`, or a hand tool's own act id (§566), and `at`, the UTC time it was said in RFC 3339. Each event's other
fields:
- `ended.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint`, `snapshot`, `outcome` (`done` or `failed`),
`error` when failed, and on `done` the point whole (§516).
- `restore-started.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint` and `snapshot`, said before the
restore reads the dump's item events and before any aside is taken (§563).
- `restored.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint`, `snapshot`, `outcome`, `error`, `aside` and
`missing` (§516).
- `set-aside.<module>.<kept>` and `aside-discarded.<module>.<kept>`: the aside's `id`, `name` and `size`.
- `point-removed.<module>.<kept>` as §551 has it; and `expiry-ended`, a subject without parts, with `removed`, the
number of points the run removed, 0 included.
- **Every act ends with an event under its `act`.** `snapshot-kept` and `recovery_take` end with `ended`;
`restore-kept`, `recovery_restore` and `recovery_restore_aside` (§566) with `restored`; `recovery_aside` with
`set-aside`; `recovery_discard_aside` with `aside-discarded`; a run of §551's removal (at the start, once a day,
at a `snapshot-kept` or by `recovery_expire_points`) with `expiry-ended`, also when it removes nothing;
`recovery_retry` with the ending event of the act it asks again, under an `act` of its own; `recovery_cancel`
(§566) with the cancelled act's ending event, `outcome` `failed` and `error` `cancelled`.
- **An act a restart cut off** (accepted and written in the directory, never ended, §554) is ended at the module's
start, after the start's repair of asides (§516, §564), with its own ending event under its `act`, `outcome`
`failed` and `error` `stopped`.
- **A restore runs** from its `restore-started` until a `restored` with the same `act`; the backup holder reads it
so (§554, §563).
- **The caller wakes on its own act.** The node agent told `accepted` wakes only on an `ended` or a `restored`
whose `act` is the one it was given; a `form` and a `fingerprint` alone wake nothing, since a retried act's old
`failed` carries the same ones. Told `busy` with the running act's `act`, it wakes on that act's ending event
and asks again, and its new answer gives the `act` it waits on next.
- **The 90-minute bound** of the node agent's wait (§554) counts from its first ask for that point or restore,
every `busy` and every ask again within it. Past it, the node agent's have says the wait and the watchman's
`delivery-waits` raises it.
- **How the node agent hears and keeps it.** The node-engine's manifest `consumes` `mesh-recovery`'s `ended` and
`restored`, read as §556 has it. Before it asks, the node agent writes in its have the want's wait with `ask-at`
(its first ask's time); once answered `accepted`, it writes the `act` there too. After a restart it reads that
have: an `act` whose ending event the read of §556 already gives is taken as heard, and one not yet ended is
waited on, asked nothing again; a wait with no `act` yet is asked again within its bound.
- **What the node agent does with each ending.**
| outcome, `error` | what the node agent does |
|---|---|
| `done` | goes on with the move or the revert |
| `failed`, `stopped` (a restart or a move cut the act off, §562) | asks again, within the same bound |
| `failed`, `cancelled` (§566) | asks nothing again; holds the move, the reason `cancelled` with the hand act's `why` |
| `failed`, `cap` (§564) | holds the move, the reason the error's own words |
| `failed`, `restore point missing` or `restore point damaged` (§516, §567) | holds the revert as §300 has it, the reason the error's own words |
| `failed`, any other error | holds the move, the reason the error's own words |
A hold is the phase `held` with that reason, said as `wait-held` (§541), and ends on a new want or the
operator's hand act (`recovery_retry`, then a new ask of the node agent on that act's ending).
This replaces §554's list of events. Why: an event a caller cannot tie to its own call wakes it on another's
failure, and an act whose end is never said holds its caller unseen (round 3, health and events, items 1 and 2;
running and testing, item 3). How it is checked: unit (mesh-recovery's contract), each event's fields read; whole
(recovery module), a retried act's caller not woken by the first act's `failed`, an expiry run that removes nothing
says `expiry-ended` with `removed` 0, an act cut off by a restart ends `failed` with `stopped`; unit (node agent),
`busy`, then the other act's ending event, then an ask again within one bound, the bound passed after repeated
`busy` answers, and each row of the table; whole (node-engine, recovery module), a node-engine restarted during a
restore takes that restore's `restored` and asks nothing again.
- §561 **The recovery module's and the backup holder's findings, each with its scope, id and clearing.** (§554) Each
is written by mesh-sdk's finding writer (§461) as the key `<scope>.<id>.<kind>` (ADR 0564 §2), each token escaped
as a key's name is (§73), and said on the part's finding stream (§383, §529):
- `points-filling`: the recovery module's, scope `mesh-recovery`, id the node it runs on. It is written when the
directory of its data item `points` passes the fill mark of its cap (§564), which the recovery module measures
itself at its start, after each act and after each expiry run, naming the size, the cap and the five largest points and asides. It is cleared when a
measure finds it below the mark.
- `recovery-waits`: the recovery module's, scope the module whose act waits, id the act's `act`. It is written
60 minutes after the act began waiting on the dump of the bus's item (§563), naming the item and since when. It
is cleared when the act starts its work, or ends.
- `backup-waits`: the backup holder's, scope its node, id the item that waits (`<module>.<item>`, one escaped
token). It is written 60 minutes (its setting `put-off-bound`, §557) after the item was put off (§563), naming the restore's `act`. It is cleared
when the item starts again.
- `aside-unrestored`: the recovery module's, scope the module, id the aside's `id`. It is written at an act's end
and at the module's start for each aside whose record ends at `removed` and that no running act holds, which
the health check also fails on (§554). It is cleared when that
aside's record ends at `restored` or the aside is discarded.
The recovery module and the backup holder of G are observers on the condition keeper's list of sources (§387,
§388), written into it in G's change. Why: a finding no one can clear stays open for ever, and the four findings
of §554 named no scope, id or clearing (round 3, health and events, item 4). How it is checked: whole (recovery
module, backup holder, the keeper), one whole test writes and then clears each: the points directory filled past
the mark, then an aside discarded; an act held by a dump past the bound (a setting, §557), then started; a dump
held past it, then started; an aside left at `removed` with no running act, then restored by
`recovery_restore_aside`.
- §562 **The recovery module moves as any module that holds data; its points and asides hold no move.** (§554) The
operator, 2026-11-11 about 14:15 CET at lead-47's terminal: the recovery module may be moved off its node at any
time, and the restore points and asides in its directory do not hold the move. A placement that moves it is made as
for any module that holds data. The assigner asks for the move a person must allow before it writes the want (ADR
0539 §14). The old node's want is written as removed, and its data item stays on that node's disk, with its copies
in that node's repository, until the operator answers (ADR 0429 §6). On its new node the recovery module starts
with an empty directory: `kept-points` lists nothing of the old node's, and a `restore-kept` of a point left there
answers `missing`. Its `from-backup: yes` then asks the new node's backup holder, which holds no copy of it, so
`restored` says `failed` with `restore point missing` and §300 holds the revert. The placement verb asks the
recovery module nothing, and §554 no longer refuses a move while a point younger than 90 days is listed.
- **An act a move cuts off.** When its node's want is written as removed, the old instance takes no new call
(answering `error` naming the move) and, before it stops, ends its running act through its failure path (§516):
a restore past its removal puts the aside back under the live names and sets the consumers back; then it ends
every accepted act, the running one included, with its ending event, `outcome` `failed` and `error` `stopped`.
- **A stop it could not finish** (the old node gone or killed): the new instance, at its start, reads the seat's
stream as §556 has it, and ends `failed` with `stopped` every act whose `restore-started` or acceptance (its
`ended` or `restored` missing) the stream holds with no ending event, and writes `aside-unrestored` for each such
restore, naming the old node, since its aside is on the old node's disk.
- **Who asks again.** The node agent and the backup holder hear those endings (§560, §563); the node agent asks
again the new holder, which it finds from the catalog half's claims of `mesh-recovery`, and the backup holder
starts a put-off item again on a `stopped` `restored`.
Why: the operator's answer (the knowledge base's review of ADR 0672, R6); the ending of a cut-off act is the
design's (round 1 of this batch's review, running and testing R2, health and events H1). How it is checked: unit
(assigner), a placement that moves the holder of `mesh-recovery` while points are listed is made, asked as ADR
0539 §14 has it; whole (recovery module), on an empty directory a restore of an unknown point says `restore point
missing`; a move during a restore after its removal leaves the live data as before and ends the act `stopped`; a
new instance whose old one was killed during a restore ends that act `stopped` and writes `aside-unrestored`; a
put-off dump starts again after it.
- §563 **When a dump and a restore start at one moment, the dump gives way.** (§554) Each side says its start before
it reads the other's:
- **The backup holder** of G, for the item of the holder of `mesh-broker` (read from the seat list's definition
and the catalog half's claim, §554), says `item-started`, then reads `mesh-recovery`'s `restore-started` and
`restored`, last per subject, once more. When a restore runs (§560), it ends the item at once with `item-ended`,
`outcome` `put-off` and `restore` naming that act, having read nothing of the bus, and starts the item again when
it hears `restored` with that `act`.
- **The recovery module** says `restore-started`, then reads the item events of the bus's item. While an
`item-started` has no `item-ended`, it waits, and takes no aside.
- **Said means acknowledged.** Each side reads the other's only once the bus acknowledged its own start event on
its stream.
Since each side says its start before it reads, at least one side sees the other, and the backup holder always
gives way, so the two never overlap. A dump that has run since before the restore began is waited for. The backup
holder ends every item of its own that it said started and never ended, at its start, after its read of §556, with
`item-ended`, `outcome` `failed` and `error` `stopped`, so no restore waits on a dump that no longer runs. Every
`item-started` and `item-ended` carries `at`, and `item-ended` its `outcome` (`done`, `failed` or `put-off`).
- **A put-off dump is that night's backup.** The operator, 2026-11-11 about 14:05 CET at lead-47's terminal: an
item ended `put-off` is neither a failed night nor a missed one (ADR 0235 §13, §21 hold): the item runs when
`restored` is heard, and the night is the outcome of that run. The item's last good backup ages as before, so
`backup-stale` is raised only by its own age rule, never for the put-off itself. `backed-up` (§302) lists a
put-off item with the restore's `act` and since when; `backup-waits` names it after 60 minutes (§561).
Why: §554 left open which side gives way when both start at once (round 3, running and testing, item 1); how a
put-off item counts is the operator's answer (the knowledge base's review of ADR 0672, R5). How it is checked:
unit (backup holder and recovery module against a fake bus), both orders of "say, then read" in both sides, each
ending with no overlap; whole (backup holder, recovery module, the real bus), a dump and a restore asked at one
moment never overlap and the item runs after `restored`; a put-off item raises no `backup-stale` and is not counted failed; a backup holder
restarted during an item ends it `failed` with `stopped`, and a waiting restore then starts.
- §564 **The cap is a setting, an act that would pass it is refused before it removes anything, and a failed act
leaves no half-made point or useless aside.** (§554, §557)
- **The settings.** The cap of the data item `points` is the recovery module's setting `points-cap`, 10 GiB unless
set, and its fill mark the setting `points-filling-mark`, 80 % unless set, both read at its start, as §557 has a
bound.
- **Before it writes.** Before it writes anything, `snapshot-kept`, `recovery_take` and the aside of
`restore-kept` add up the bytes the bus states for each bus object they take. An act that would pass the cap
ends `failed` with `error` `cap`, naming the cap, that size and the directory's fill, before any aside is taken
and so before any live object is removed. A `snapshot-kept` that ends so holds the move as a failed backup does
(§296). A point or an aside that passes the cap while it is taken (the stream grew) ends its act the same way.
- **Brought back.** A point brought back from the backup (`from-backup: yes`, §516) is not counted against the
cap: it lies beside its path only while its restore runs, and is removed when the restore ends.
- **On a failure.** An act that ends `failed` removes its point under a temporary name (§516) then, not only at
the next start. An aside whose record ends at `set aside` (the live objects were never removed) is removed when
its restore fails or stops before the removal, and at the start, since the live data was never touched.
Why: the cap is a bound a whole test sets (§557), and a restore refused for size after the removal would leave the
module's data only in an aside (round 3, running and testing, item 2 and its notes). How it is checked: whole
(recovery module, with small settings), a snapshot passing the cap ends `failed` and holds the move; a restore whose
aside would pass the cap is refused with the live data untouched; `points-filling` written past the mark (§561); a
failed point leaves no temporary name; an aside left at `set aside` by a stopped restore is removed at the start.
- §565 **The grants part reads the restore points' names through `kept-points`, and composes without them until it
answers.** (§554) The bus names of the recovery module's account include those a restore point names (§554). The
grants part reads them by its `invokes` of `seat:mesh-recovery.kept-points`, a kept call (ADR 0668 §1). It asks
again on every `ended` with `outcome` `done`, every `point-removed` and every `expiry-ended` of `mesh-recovery`,
which it consumes (ADR 0668 §4), so a point's names are granted before any restore of it. Until `kept-points`
first answers after a start, the grants part keeps the names it last granted from it, kept in its state
`memberships` with the account, and adds the catalog half's kept entries (§443); it takes no name away before
that answer, and says the wait in `grants_first_views` (§556). The edges between the two (kind 5, the grants part has acted for the recovery module's account; kind 6,
the recovery module serves `kept-points`) are a cycle of delivery edges, delivered as one step (§49). Why: the
account must follow the points without the grants part reading another part's state (ADR 0596; round 3, seams and
tools' notes; running and testing R3). How it is checked: unit (grants part), a restarted grants part with
`kept-points` unanswered keeps a point's names it granted before, and a point's old-section names are added after
an `ended` and taken away after its `point-removed`.
- §566 **Two more hand tools: put an aside back, and cancel a waiting act.** (§554, §516) Each is of class `act`,
taking its default (§610), checked by the identity of who asked (§554), written first in the hand-act
log of the seat `mesh-hand-acts` with the caller the bus proved and `why`, and an act of its own that runs one at a
time (ADR 0672):
- `recovery_restore_aside` (`module`, `kept`, `id`, `why`) restores an aside under the live names it was taken from,
for an aside whose record ends at `removed` that no running act holds (the case `aside-unrestored` names, §561).
It refuses, naming them, while live objects of those names exist (an aside taken by `recovery_aside` is put
back by this tool too, the refusal naming `recovery_aside`'s aside when the names are live), and it ends with
`restored`.
- `recovery_cancel` (`act`, `why`) ends an accepted act that has not begun its work (one waiting on the dump, §563)
with its ending event, `outcome` `failed` and `error` `cancelled`. It refuses an act that has begun, naming its
step, since a restore cut off after its removal is put back only by its own failure path (§516).
Why: a module's tools recover what a person may need to recover (*Tools are how a module is spoken to*; round 3,
seams and tools' notes). How it is checked: whole (recovery module), an aside left at `removed` put back by
`recovery_restore_aside` and refused while the live names exist; a waiting act cancelled and a running one refused.
- §567 **A restore point is checked by the digest of its bytes before it is restored.** (§516) A point's record
carries `digest`, the `sha256:` digest of each bus object's snapshot file, written when the point is renamed whole
(§516). The same digests are kept on the bus, apart from the bytes: in the recovery module's own state `digests`
(its kept data, by the template `state`), one key per point, its `snapshot` escaped as a key's name is (§73),
written before `ended` is said and deleted when the point is removed (§551); about 0.3 KiB a key, capped at 4 MiB.
`kept-points` lists the digests, and `ended` carries them with the point. Before a restore takes any aside, it
computes the digest of the bytes it is about to restore, whether from its own directory or brought back by the
backup holder (`from-backup: yes`), and compares it with the key in `digests`; the record in the directory is never
the reference. The state holds digests only and lists nothing, so §517's "no `bus-points` state" stands for the
list. A mismatch ends the act `failed` with `restore point
damaged`, naming the point and the bus object, and §300 holds the revert. Why: a point brought back from the backup
was restored unchecked (round 3, security and upgrade's notes). How it is checked: whole (recovery module, backup
holder), a point whose file and record were both altered after it was taken is refused before any aside is taken.
## The builder
- §568 **A transport fault in the builder's own read ends no build key, and a key's error says which kind of fault it
is.** (ADR 0664 §1 to §3, ADR 0668 §1 to §3, ADR 0673 §1 to §4; issue 1612, met again 2026-11-11)
- **The builder's own reads.** The builder's reads of the build seats' state `steps` (its own step keys, §467,
§481) are kept calls (ADR 0668 §1, §2). A read that meets a transport fault, of ADR 0664 §1's classes or the
read's own deadline, changes nothing the builder holds and writes no build key. The builder's watch of `steps`
takes up again from its last revision once the bus answers, and a read it made at its start is made again then.
Past five minutes it writes `call-unanswered` (ADR 0668 §3). A build key is written `failed` only from a step
key's end (ADR 0664 §3).
- **The words of a failed key.** A step that ended failed because its third run met a transport fault (ADR 0664
§2) gives the build key an `error` that opens `transport fault:`, then the class (`no responders`, `name lookup`,
`connection` or `deadline`), the step's seat and node, and the cause of its last run in the build module's words.
Every other failure keeps its own opening (`not stored:`, `not pinned:`, `version taken:`, `refused kind:` or the
build's own words) and never opens `transport fault:`. The builder's read tools show the error whole, so a reader
tells a passing cause from a real one without the journal.
- **What builds such a key again.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal, decided by the
record of the builder's retry, ADR 0673 §1 to §3, which changes ADR 0664 §3: a key whose error opens `transport
fault:` is built again, once, as a new revision of the key (ADR 0429 §18), on the first event after its failure
that says its cause passed. Two events say so (ADR 0673 §2): the node-engine seat's `running.<node>.<module>`
saying `running` for a module whose catalog graph entry claims the build seat the failed step ran on (§546), on
any node; and the node-engine seat's `manifest-reads-changed` of the node the failed step ran on with `why`
`returned` (ADR 0618 §4, ADR 0668 §4; §590), which it says when that node is heard again after being away. One
saying `why` `started` builds nothing again. The builder hears both through its consumers of the node-engine seat, read as §556 has it.
It builds a key again at most once per event heard, and only while the key's newest revision is that failure, so
one event starts one revision. A revision that fails again on a transport fault is said as every failed key is,
its error opening `transport fault:` again, and waits for the next such event. The builder's manifest `consumes`
both events.
- **Three automatic builds, then the operator is asked.** The operator, 2026-11-11 about 14:55 CET at lead-47's
terminal: the builder counts, in the key's newest revision, the revisions events started since the last one a
person started (`event-builds`). After three that failed, the next such event starts nothing: the builder asks
the operator once, through the seat `operator-channel`'s ask (level `approve`, options *Build once more* and
*Leave it*, `about` the key so a newer ask replaces it, ADR 0666 §12), naming the module, the fingerprint and
the last error whole. *Build once more* builds one revision, counted as a person's, so the count starts again;
*Leave it*, a decline or the ask's expiry leaves the key failed until `builder_build_again` or a new
fingerprint. The failure stays said throughout. A key failed for any other cause
is never built again on these events. `builder_build_again` (ADR 0668 §5) and the builder's next start build it
again too, as before. The builder's read tool of a key, `builder_build` (§589), names the event that built its
newest revision again, `event-builds`, and the open ask.
Why: a read the builder made met `no responders` and failed a key, which then stayed failed for about 45 minutes
(issue 1612, 2026-11-11), against ADR 0664 §3 (a key is not written failed while its step runs on); the retry on an
event is the operator's answer. How it is checked: unit (builder), a `no responders` read of `steps` leaves the key
`building` and the next revision ends it; a third-run transport fault's key error opens `transport fault: no
responders`, and a compile error's does not; a `running` of a holder of that build seat builds the key again once,
and a second `running` while the new revision builds starts nothing; a node's `manifest-reads-changed` with `why`
`returned` builds again a key failed on that node, and one with `why` `started` does not; a key failed on a compile error is not built again on either event; after three failed
event-started revisions the fourth event asks the operator once and builds nothing, and *Build once more* builds
one.
## The order of a change
- §569 **A cycle with a build edge is refused, whatever its other edges, and a grant composed from a first view is an
edge.** (§49, §558, §374) The indexer's cycle rule (§49) reads every edge of the six kinds in one graph per watched
branch. A cycle whose edges are all delivery edges (kinds 2, 4, 5 and 6) is delivered as one step. A cycle with at
least one build edge (kind 1 or 3) is refused, whatever its other edges, at the check and at take-in, naming every
artifact on it in order with each edge's kind and the manifest word it came from: no build on such a cycle can
start before a delivery that waits for that build (ADR 0666 §14). Two kinds of edge enter that graph:
- **A seat verb a build calls.** When a build module's step calls a seat verb while it builds (the image build
module calling `container-image-registry`'s `mirror` for a base image), its manifest names it in `invokes`, and
the indexer derives an edge of kind 3 from that seat's holders to every image artifact the build module builds.
The edge is met by the holder that runs when the build starts, whatever its version: it orders a build after a
running holder, never after the holder's new version. So the holder's own artifacts are built through its
running version, and the edge makes no cycle through them.
- **A grant from a first view.** A binding the grants part writes for a holder from its first view of a served
answer (design 25 *bindings-first-view* §4 and §9)
is an edge of kind 5 from the grants part to that holder. Where the grants part names a field of that answer
(§558), an edge of kind 6 runs from the answer's seat holder to the grants part. Its new version must run, so this
edge is met only by that version.
The circle of 2026-11-11 was such a cycle: the bus module's image waits, by kind 3, for the registry holder's
delivery; the registry holder waits, by kind 5, for its binding `mirrors` from the grants part; and the grants part
waited, by kind 6 on `mesh-broker`'s `list` with `binding`, for the bus module's new version: the kind-3 edge waited
for a registry that served `mirror`, which needed its binding, which needed the new bus module. It is refused at its
check, naming the three artifacts. Design 25 *bindings-first-view* §9 removes the kind-6 edge: the grants part
composes a binding from the fields `list` already answers, so it relies on no new field and names none (§558).
G's delivery then puts the grants part before the bus module's build. Why: the circle mixed a build edge and a
delivery edge, and no rule saw it (issue 1966, notes 203 and 204; the operator, 2026-11-11 13:30 CET, on design
25's §9). How it is checked: unit (indexer's graph), a cycle of kinds 3, 5 and 6 is refused naming its three
artifacts and their kinds; a cycle of kinds 5 and 6 alone is one step; a build module's `invokes` of `mirror` gives
a kind-3 edge to its image artifacts; the image registry's own image, built through its running holder, is not
refused.
## Asks to the operator
- §570 **An ask the router refuses is heard by its asker, and no asker asks for longer than its level allows.**
(ADR 0259 §3, §4) The kind behind it is an asker left waiting on an ask the router never took (issue 1610; met
again 2026-11-11, when the bus module's three `nats_recreate` asks were refused at take-in and their tool kept
saying the operator had not answered).
- **The router says the refusal.** The router (the holder of `operator-channel`) says every ask it refuses at
take-in, for each cause ADR 0259 §4 gives as it holds, on that asker's
`mesh.seat.operator-channel.event.decided.<asker>`. The event carries `outcome` `refused`, the ask's id (the warrant's `ask`), why
in the router's words (its `words`) and `at`, as an ask's end without a warrant is said (ADR 0259 §3).
- **Every asker ends its request on it.** The asker client, mesh-contracts' `operatorchannelclient` (§638), gives its caller `refused` with `why` as soon as
it hears it. An asker's tool or act that waits on that ask answers `refused` with why, never "not answered yet";
the bus module's `nats_recreate` and `nats_drop` are among them.
- **The lifetime is held where every ask is made.** `operatorchannelclient` changes in G's mesh-contracts release, which
every asker is built against (§47). That client, through which every asker publishes
an ask, refuses before it publishes an ask whose expiry passes the bound of its highest option level (`approve`
24 hours, `destroy` 10 minutes, ADR 0259 §4), naming the level and the bound. The bus module's keeper asks for
its level's bound in place of its fixed 12 hours (`AskLasts`).
- **The operator sees a refused ask.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal: the router
keeps each ask it refuses at take-in in its bucket `asks` under `<asker>.<id>` as a closed record, with `outcome`
`refused`, `why` in the router's words and `at`, for 30 days like every closed record (ADR 0259 §3). It writes
the record only by a create: when `<asker>.<id>` holds a record already (an open ask, its tickets with it), the
refusal writes nothing there and is said on `decided` alone. The operator, 2026-11-11 about 15:15 CET ("just make
it configurable"): the router keeps at most `refused-kept` refused records per asker, a setting of the router,
20 unless set, removing the oldest first, each also gone after its 30 days, each removal written in its journal. The seat's
`asks` lists it with that outcome and words, as an ask that ended.
Why: ADR 0259 §4 refuses such an ask on the decided subject and bounds a destroy option at 10 minutes, and neither
reached the asker; the list is the operator's answer. How it is checked: unit (`operatorchannelclient`), a table
over the levels in which an expiry at the bound is published and one past it is refused before it is published,
and a `refused` that ends the request with why; the code review refuses an ask built outside the client; whole
(router, bus module), a refused ask is said on `decided`, a refusal of an id that is open leaves the open record and its tickets as they
were, a twenty-first refusal of one asker removes its oldest, `asks` lists it `refused` with the router's words, and
`nats_recreate` answers `refused` with those words.
## A module's packages
- §571 **A mesh package's entry in a lock file is written by the build, never by hand.** (ADR 0573 §2, §4; §47,
§478, §504) A module's `package.json` names a mesh package (one whose name §504 maps to a catalog artifact) with the
range `*`, or with the oldest version it accepts (ADR 0573 §2). Its committed `package-lock.json` may hold any entry
for that package, or none.
- **What the build writes.** The Node and TypeScript build module removes every entry of a mesh package from the
workspace's lock before it installs. It then writes the entry from the version the planner read and the
integrity the package registry gives for that version (ADR 0573 §2). In a delivery group's check where a member
publishes the package, it writes the entry from that member's head, packed for the check, with the packed
tarball's integrity (ADR 0573 §4, ADR 0500 §4). Every other entry is installed as the lock names it and checked
against its integrity (*Every dependency comes by the version its lock file names*).
- **Before its first publication.** A package of which no version is published yet (`npm-package-registry`'s
`versions` answers none, as for the packages G itself publishes first) is reached only in a group's check, from
its member's head. Outside a group, the edge of kind 1 (§47) builds the dependent after the package's first
publication, which G's delivery orders.
- **The check's lock rule** never refuses a mesh package's entry, nor its absence.
- **The reuse key** of a build takes the versions it wrote for mesh packages, as the fingerprint takes them (ADR
0573 §2), so a build is never reused across two of them.
Why: ADR 0573 §2 has the build write a mesh package's version into the workspace, and a lock made by hand holds
the integrity of a locally packed tarball, which the published one never matches (develop question of the forge
module's developer, 2026-11-11). How it is checked: unit (Node build module), a lock naming a local tarball's
integrity for a mesh package is built with the registry's version and integrity; a lock without the entry is built
alike; a non-mesh entry with a wrong integrity is refused; whole (G's group check), the forge module's tools are
built against the head of the member that publishes the package, packed.
## The builder's forms
- §572 **The builder's process answers a package's name, and its tools ask it.** (§478, §504) The mapping from a
package's name to its module and artifact is the builder process's own, from its view of the catalog half (§348).
The seat `mesh-builds`' `reserved`, given `name`, is answered by the builder's process from that view. The
builder's tools bundle holds no catalog view: `builder_versions`, asked by a name, calls `reserved` (or, for a
module, `reserved` with `module`) through its `invokes` and answers what the process answers. Why: one mapping
and one view, in the part that keeps them (ADR 0596 §2; develop question BL7's name part). How it is checked: unit
(builder), `reserved` with an npm name and with a Go module path answered from the view; whole (builder),
`builder_versions` by name answers the same as `reserved`.
- §573 **The build seat's ask carries `version`.** (§370, §458) The ask of each build seat (`node-build-go`,
`node-build-node` and the image build seat) gains the field `version`: the version `MAJOR.MINOR.PATCH` the builder
reserved for the build (§369), or empty for a build that reserves none (§371, §373). The field is part of the build
seats' contract package, beside the ask's other fields. The builder fills it when it asks, and each build module
reads it and passes it as `MESH_VERSION` (§370), passing none when it is empty, so the build stamps none (§458). A
build module of before G ignores the field and stamps nothing. Why: §370 named no field (develop question of the
builder's developer on CT5). How it is checked: unit (build seats' contract), `version` read and an empty one
read as none; whole (Go and TypeScript build modules), a build asked with a version carries it.
- §574 **`toolchain-agreed` carries `digests` as a list.** (§472, §495) The event's field `digests` is a list, each
item `{digest, nodes}`: the `sha256:` digest of the toolchain program and the nodes that reported it at the agreed
version, sorted by digest, with each item's `nodes` sorted. It replaces the field `digest`, which the event no
longer carries; a kept event of before G that carries `digest` alone is read as
one item with no `nodes`. Why: §472 names one digest per group of nodes, and a list keeps two digests of one version apart
(develop question of the contracts developer). How it is checked: unit (mesh-builds' contract), the list read, and
an event of before G carrying `digest` alone read as one item with no `nodes`, so a reader of G refuses none
and nothing waits for the next agreement.
## What a node-engine says of a build
- §575 **A want held for its build says how the build stands.** (§524) The operator, 2026-11-11 about 14:05 CET at
lead-47's terminal: the words "the build of <module> at <fingerprint> is not done" are the assigner's, the reason
of its hold under its first rule (no want for a fingerprint whose build is not done, ADR 0429 §6). That reason now
says how the build stands, from the builder's `build.ended` the assigner already consumes and from the build it
waits on: a failed build reads `the build of <module> at <fingerprint> failed: <the error>`, the error whole as
`build.ended` carries it, `transport fault:` included (§568); a build that runs reads `the build of <module> at
<fingerprint> is being built`. The assigner says it in its wait for that module, which the watchman's
`delivery-waits` names; the node-engine reads no build state for it (ADR 0596). "Is not done" is said no more.
Why: the operator's answer; a failed build said as not done left the operator and the watchers waiting for a
build that had ended (round 1 of this batch's review, seams and tools T1, placed the words in the assigner). How
it is checked: unit (assigner), a module whose build ended failed is held saying `failed:` with the error, and one
whose build runs saying `is being built`.
## The tool runner, and kept data at run time
- §576 **A bundle reads its tool runner's capability words through one call.** (§488, §545, §559) The tool runner
answers the runtime call `mesh/capabilities` with `{words}`, the capability words it states (§545). mesh-sdk's
bundle, in Go and in TypeScript, makes that call once, at its first call that needs a word, and keeps the answer
for as long as it runs. A bundle lives no longer than the tool runner that started it (its channel is that tool
runner's standard input and output), so after a restart of the tool runner the bundle is started again and reads
the words again. A tool runner of before G answers `mesh/capabilities` as an unknown call, which the bundle reads as
no word stated, so a call of a family G adds fails at once, naming its word and the node (§488). A bundle learns
its node from `MESH_NODE`, which the tool runner sets in the environment of every bundle it starts, read alike in Go
and in TypeScript. Why: §488 named no channel (develop question of the mesh-sdk developer). How it is checked: unit
(tool runner), `mesh/capabilities` answers the stated words; unit (mesh-sdk, both languages), the words read once,
an unknown-call answer read as none, and the node read from `MESH_NODE`.
- §577 **A part reaches the kept data its `requires` declares through its binding, never by an old name.** (§529;
ADR 0595 §1, §9; ADR 0619 §2)
- **A bundle** reaches it only through the tool runner's `mesh/` calls by its local name (ADR 0595 §9), which the
tool runner resolves in the module's membership binding.
- **A process with an account of its own** reaches it by its binding: mesh-sdk's process package gains `Binding`,
which takes a local name and gives the entry's bus names (bucket, stream and subjects, object stores). It asks the
seat `mesh-broker`'s `show` for the module and the local name at the process's start, as a kept call (ADR 0668 §1)
that the process's `bus` names in `invokes`. It asks again when it hears `mesh-broker`'s `recreated` or `dropped`
for that entry, which the process consumes. Until `show` answers, the process acts on nothing that needs the entry
and its health says it waits, naming the entry (ADR 0283).
- **The old name** (mesh-sdk's opening of an essential bucket by its declared name) is for a foundation part's old
`state` section alone (ADR 0619 §2). A part that declares an entry in `requires` opens it by `Binding`; the forge
bridge's `performed` is among them, moved so in G's change.
- **The validator's rule for the finding words** (§529) accepts the old stream `<module>-findings` only for a part
on the foundation list (ADR 0619 §3 as ADR 0666 §4 changes it). It refuses that stream for every other part, whose
finding stream it requires in the template form: `finding-events`, by the template `events`, its entry listing
`finding-written.*`, `finding-cleared.*` and `findings-stated.*`.
Why: a part's kept data is the bus module's to name and the part's to ask for (ADR 0595 §1, §7), and §529 left the
run-time path and the validator's row open (develop question of the watchman's developer). How it is checked: unit
(mesh-sdk's process package), `Binding` answered from `show`, asked again on `recreated`, and waiting while
unanswered; unit (forge bridge), `performed` opened by its binding; unit (manifest validator), a foundation part's
old finding stream accepted, another part's refused and its template form accepted.
## The indexer's forms
- §578 **`manifest-digest` carries its prefix, and the key does not.** (§506, §445) The catalog graph entry's field
`manifest-digest` is `sha256:` followed by the 64 hex digits, as every digest a record names. The module's value
and §445's `manifests` key stay the 64 hex digits as on main, since a key's name takes no `:` (§73), and mesh-sdk's
catalog client gives the bare digits as today. The indexer writes all three from the one digest it computed at take-in.
Why: one digest in two forms, the field's as the records write a digest and the key's as a key's name allows
(develop question of the indexer's developer). How it is checked: unit (indexer), the entry's field after its
prefix equals the key's name and the module's value.
- §579 **The indexer keeps the records pinning each revision, and removes a revision only on an event.** (§153,
§178, §216)
- **Where.** The indexer, a foundation part, keeps its old `state` section `pins`: one key per catalog graph
revision, `<branch>.<revision>` (the watched branch and the revision's id, each escaped as a key's name is, §73),
whose value is the list of delivery record ids that pin it. A list of ids is kept rather than a count, so a
`pinned` heard twice pins once. At about 20 revisions pinned a day for 90 days, about 1,800 keys of about 0.2 KiB
each, it is capped at 4 MiB (an estimate).
- **Applying `pinned` and `freed`.** The indexer adds or removes a record's id by compare-and-set, and only then
acknowledges the event on its durable consumer of `mesh-delivery`'s events (§153). That consumer, when first
made, starts at the stream's first message. A `freed` said while it was stopped is applied when it comes back,
and one applied twice changes nothing. A `pinned` of a revision the indexer no longer holds writes its key with
the record's id and `missing: true`, and the finding `pin-missing` (scope the indexer, id the revision) naming
the record, which `indexer_unpin` clears.
- **The gate states its pins.** At each start the gate says `pinned` again for every revision each record it keeps
pins, then once `pins-stated` (`at`), so pins said before the indexer's consumer existed, or older than the
stream keeps, are heard again; the set form absorbs the repeats.
- **Current.** A revision is current while it is the newest revision the indexer took in on its watched branch,
whichever watched branch that is, or the newest revision of an open candidate (its `candidates.<group>`). It stops
being current when a newer revision of its branch is taken in, or when the candidate's `heads-replaced` is heard
(its heads replaced, or its pull request closed) or the candidate is taken in.
- **Removal.** When a revision's list is empty and it is not current, the indexer removes its revision object and
its key, and its entry objects as §179 has it, but only while its consumer of `mesh-delivery`'s events is caught
up (none of its messages pending), so a `pinned` said before is applied first, and only once it has applied a
`pins-stated` said after its state `pins` was made. It judges on applying `freed`, on a revision ceasing to be
current, on being caught up, and at its start after both of these hold, when it judges every kept revision, so a
stop between a take-in and a removal keeps nothing for ever. Never on a timer.
- **Inputs.** A catalog graph revision names no input revision. "The input revisions it names" (§178) are an
assignment graph revision's catalog revisions, which a delivery record pins itself (§153), so the indexer keeps
each by its own list alone.
- **The assigner** keeps its pins of assignment graph revisions in the same form and by the same rules: its old
`state` section `pins` (it is a foundation part, ADR 0619 §1), one key per assignment graph revision, capped at
4 MiB; a revision is current while it is the newest the assigner wrote, or the newest it wrote for an open
candidate; its tools are `assigner_pins` and `assigner_unpin`, as the indexer's below.
- **Its tools.** `indexer_pins` reads each kept revision with the records that pin it, whether it is current and
why, and the store's fill against its cap. `indexer_consumer` reads each of the indexer's durable consumers: its
stream, name, acknowledged sequence, the messages pending, and the last event applied with its `at`. Both are
of class `read`. `indexer_unpin` (`revision`, `record`, `why`; class `act`, taking its default (§610),
written in the hand-act log first) removes one record's id from a revision's list, for a `freed` that was lost,
and the removal rule then applies.
Why: §153 and §178 said what is kept, not where or how it is counted (develop questions IX17 and IX18). How it is
checked: unit (indexer), a revision pinned twice by one record and freed once is removed; a `freed` applied twice
changes nothing; a revision of a second watched branch kept while current; a candidate's revision removed on
`heads-replaced`; a revision that stops being current while a `pinned` of it is pending is kept; a `pinned` of a
removed revision writes `pin-missing`; `indexer_unpin` frees a revision; whole (indexer, gate), a `freed` said
while the indexer was stopped is applied after its start; a `pinned` said before the indexer of G first started is
heard through the gate's restatement; an indexer stopped between a take-in and a removal removes the revision at
its start.
## Observers with more than one bundle, and the forge module's forms
- §580 **Each finding kind belongs to one bundle, and a statement covers only its own kinds.** (§383, §384, §485;
the writer's own kind `finding-refused` and the statement's `bundle`: §707)
A module whose bundles write one state `findings` (the forge module has four: its TypeScript tools, its Go package
registry, its forge and its agent token) gives each finding kind to exactly one bundle. That bundle's mesh-sdk
writer is made with the kinds it owns, and refuses a `write` or a `clear` of any other kind, naming it. Each bundle
judges and states its own kinds from its own truth (§384); no bundle reads the whole state to state another's
findings. Its `findings-stated` carries `kinds`, the kinds it states, and the writer's statement deletes only keys of
those kinds. The keeper sets its view of that observer, node and set of kinds from the statement, and clears only
conditions of those kinds that the statement does not name. A statement with no `kinds` (a writer of a part with
one bundle) covers every kind, as before. Why: a statement that deletes every key it does not state would clear
another bundle's findings (develop question of the forge module's developer, GIT14). How it is checked: unit
(writer, both languages), a statement with `kinds` deletes no key of another kind, and a write of another kind is
refused; whole (keeper), two bundles' statements on one node each keep the other's conditions open.
- §581 **A `seen` key's 30 days are judged on each step end, with no timer.** (§487) The forge module writes `at` in
each `seen` key's value, the time of the `step-ended` that wrote it. On each `step-ended` it handles, and at its
start, it deletes the `seen` keys whose `at` is more than 30 days old, at most 100 for each event, the oldest first,
so the bus keeps no key time-to-live and no template option is added; its tool `git_seen_expire` (class `act`,
taking its default (§610), written in the hand-act log) runs the same removal now. The forge module being a foundation part
(§584), its state `failed-steps` is in its old `state` section, and a bundle reaches it through the tool runner's
state call by its name, as every old section is reached. Why: §487's 30 days needed
a trigger, and an event the module already handles is one (develop question of the forge module's developer). How
it is checked: unit (forge module), a `seen` key older than 30 days is deleted at the next `step-ended`, and a
younger one is kept.
- §582 **`reserved` by name answers a released version from the catalog.** (§381, §478, §369) Given `name` and
`version` with no reservation of that version (§369 deletes a reservation once its version is released), the
builder finds the module from the name (§572) and asks the seat `mesh-catalog`'s `versions` for that module, as a
kept call in its `invokes`. When the catalog holds that version released, `reserved` answers `released` with the
artifacts and the digests the release recorded, and the registry holder answers its publish as held when the
digest is the release's, storing nothing (§381). A version neither reserved nor released, or a name no entry
carries, is answered `error`, naming which, and the holder refuses the publish. Why: the holder knows only the name,
and the module is the builder's own mapping (develop question of the forge module's developer). How it is checked:
unit (builder), `reserved` for a released version answers `released` with its digests, and an unknown version
`error`; whole (registry holder, builder), a publish of a released version at its digest stores nothing.
- §583 **A manifest's first `version` is §368's.** The `version` G writes into the forge module's and the image
registry module's manifests is the `MAJOR.MINOR` of the version §372 fills for each, read through
`builder_versions`, and `0.1` for a module with none published (§368); their old values (`"1"` and `"2"`) carry
nothing over. Why: §368 answers it (develop questions GIT15 and MAN1). How it is checked: as §368.
- §584 **The forge module and the image registry module are foundation parts.** (§6, §529, §569; ADR 0619 §1 to §3)
The operator, 2026-11-11 about 14:30 CET at lead-47's terminal: ADR 0619 §1 is followed as written. The bus
module's build calls both before the bus module can answer, the forge for its source and the image registry's
`mirror` for its base image, so both are foundation parts.
- **The list.** The manifest validator's list (§6, ADR 0619 §3) gains the holder of the seat `git` (the forge
module, `gitea`) and the holder of `container-image-registry` (the image registry module, `distribution`) in G's
change.
- **Their kept data.** Each keeps its kept data in its old sections (ADR 0619 §2): its state `findings` and its
finding stream `<module>-findings` as §529 has a foundation part declare them, and every other state in its old
`state` section, the forge module's `failed-steps` among them (§581). The image registry module's `holds`,
`windows` and `mirrors` go back from their template entries to its old `state` section under the names they had
there. The operator, 2026-11-11 about 15:15 CET ("whatever must be done to not end up in a cycle"): at its first
start, before it serves anything, the image registry of G copies every key of its template `holds`, `windows` and
`mirrors` into its old sections, from its own data alone, and says in its journal and its read tool how many keys
it copied of each; a key already in the old section stays. This needs no other part's answer and adds no edge,
so it can close no cycle; holding its delivery until the template state is empty could wait for ever, since the
running registry keeps writing it. The template entries, no longer declared, then become orphans that `orphans`
lists, removed only by the operator's `drop` (ADR 0595 §4, §5), as §529 has it for the healers' old stream. Their
other template entries become old sections, empty, nothing copied (§612).
- **The delivery order.** The running image registry, of its build before G, serves `mirror` only once its bindings
are composed, and both the bus module's new build and the image registry's own new build ask it for their base
images. So the grants part with design 25 *bindings-first-view* §9 is delivered first, alone, and composes the
running registry's three bindings from `list`'s fields. Then
the image registry's and the bus module's builds of G are built through the running registry's `mirror` (edges of
kind 3, §569), and delivered by the edges of §49. Once the image registry of G runs, its state is in the old
sections, which the grants part creates as it creates every foundation part's, and no binding of a first view is
needed for it.
Why: the operator's answer; the list and the order follow ADR 0619 §1 and §3 and §569. How it is checked: unit
(manifest validator), both manifests of G's heads accepted with their old sections, and a template entry in either
refused; the catalog's validation at G's heads refuses neither; whole (image registry), a first start with keys in
its template `mirrors` copies them, says how many, and then serves them; the delivery plan of G puts the grants part's
delivery before both builds.
- §585 **What the forge module reads from `step-ended`, and the form of its `failed-steps`.** (§39, §475, §487, §581)
The forge module's readings where no line spoke, made design lines:
- **The fingerprint** of a step is the body's `fingerprint`, the build-input fingerprint, which the runner fills
in every `step-ended`. No field of the step's `result` is read for it. A `step-ended` without it writes the
forge module's finding `step-unread` (scope the module the step is of, id the run), naming the run, the module and
the step, cleared by a later `step-ended` of that step with its fingerprint, so a failure that could not be
recorded is seen.
- **A failure** is a `step-ended` whose `outcome` is `failed` and whose `status` is `required`. An `optional` or
`bypass` step's failure, and a `could not run`, record no failure, as ADR 0666 §11 refuses only a failed
`required` step.
- **A rerun's pass** clears a failure when the body says `rerun: true` and its `by`, the caller the runtime proved
as the runner's verb `rerun` names its callers, is `operator` or `seat:mesh-healers`; any other `by` clears
nothing (§39).
- **The watched branches** are those of each registration whose git origin is a repository the forge holds, per
repository, read through `mesh-catalog`'s `registrations` and kept current on `registration-changed`.
- **A `seen` key** `seen.<commit>.<module>` holds `{v, commit, module, fingerprints, at}`: `v` the form's version
(1), `fingerprints` every build-input fingerprint the runs named for that commit and module, and `at` as §581
has it. Both key forms of `failed-steps` are the forge module's own, defined in its own package, since no other
part reads that state.
- **The fill** of `failed-steps` that its read tool shows is read through the bundle's state call
`mesh/state.info`, against the state's cap of 64 MiB (§623).
Why: the forge module's developer read these where no line spoke (develop, batch 5); each follows §39, §475, §487
and ADR 0666 §11. How it is checked: unit (forge module), a `required` failure recorded and an `optional` one not;
a rerun pass by `operator` clears and one by another caller does not; a `seen` value read in its form, and both
key forms; a `step-ended` without `fingerprint` writes `step-unread`.
- §586 **The client of `reserved` is the seat `mesh-builds`' contract package.** (§381, §478, §582) The forge
module's package registry and the image registry module both ask `reserved`. The call is the seat `mesh-builds`'
contract, so its client lives where ADR 0573 §1 puts a seat's contract: the package the seat's holder's
repository publishes, mesh-delivery's contracts package, which defines `mesh-builds` (§52). That package gains a
`mesh-builds` client with `reserved`, its input and its answers (`reserved`, `released`, `error`), and both
registry holders use it in place of their own copies. It is not put in mesh-sdk, which keeps no seat-specific
verb (ADR 0573 §1). Why: code two modules share has one home, and for a seat's verb that home is the seat's
contract package (develop question of the forge module's developer). How it is checked: unit (mesh-delivery's
contracts), the client's input and each answer read; the code review finds no second copy in either holder.
@@ -0,0 +1,216 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0673"
- "ADR 0672"
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0664"
- "ADR 0656"
- "ADR 0618"
- "ADR 0615"
- "ADR 0614"
- "ADR 0597"
- "ADR 0596"
- "ADR 0595"
- "ADR 0441"
- "ADR 0429"
---
# 47, milestone G: develop's sixth batch
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§587 to §594 answer develop's sixth batch (lead-47's input of 2026-11-11, met in the join and develop after §560 to
§586). Each is of internal form only, within what the records and the lines it cites decide; §588 carries the operator's
answer of 2026-11-11, between 15:14 and 15:42 CET. A line of another file that one of them corrects is corrected in that line (§169, §170,
§171, §177, §226, §404, §462, §513, §568, and the event table of G-4); here only what is new.*
## The ledger's upkeep calls
- §587 **`mesh/ledger.fill` answers the history's fill, and `mesh/ledger.remove` has a request and an answer of its
own.** (§408, §462, §488, §155; ADR 0597 §1, §8, §10, §13) Both calls are in mesh-sdk's `go/buscalls`, as the
constants `LedgerFill` (`mesh/ledger.fill`) and `LedgerRemove` (`mesh/ledger.remove`), under the capability word
`ledger-upkeep-calls` (§488), served by the tool runner for the calling module's own ledgers alone, as every call of
the package is.
- **The fill.** The request is `LedgerRequest`, `{ledger}`, the ledger's local name in `requires`. The answer is
`buscalls.Fill` of the ledger's history alone: its messages, its bytes and the ledger's `cap` in bytes. The
history holds every operation and each instance's snapshot subject (ADR 0597 §1, §8), and the ledger's cap is
its history's (design 25's template row for a ledger, its state and index buckets being states), so neither the
fullest of the parts nor their sum is answered. A full state bucket is said by `apply`'s `state_behind` (ADR 0597
§6), not by this call. The gate's `filling`, `full` and `room` (§181) and `gate_records` (§159) judge this answer
against the 768 MiB of §155.
- **The removal.** The request is `LedgerRemoveRequest`, `{ledger, instance}`. The tool runner removes the
instance's operations and its snapshot from the history (by the instance's subjects), its key in the state bucket
and its keys in every declared index, and answers `LedgerRemoved`, `{instance, operations, bytes}`: the number of
operations removed and the bytes the history held less after the removal. An instance the ledger does not hold
answers `operations` 0 and `bytes` 0, so a removal said twice changes nothing. `Applied` gains no field: a
removal is no operation of the history and has no author. Only the owner's bundle calls it, through its tool
runner (ADR 0597 §13 leaves any other caller to the design, and the design names none); the gate calls it for a
record past its retention (§154).
This amends §462's "as `buscalls.Fill`" and its removal, which named no shape. Why: one cap, the history's, is
the ledger's, and a removal is not an operation (develop questions on §462 and §408, join of the tool runner). How
it is checked: unit (`buscalls`), each request and answer read; unit (tool runner), a fill answers the history's
bytes against the cap with snapshots counted, a removal answers the operations it removed, and a removal of an
unknown instance answers 0; whole (gate), a record past its retention is removed and the next fill is lower.
## The builder's publishes and its reads
- §588 **A reserved version left unpublished is published again, by the builder on an event or by a person's tool.**
(§52, §369, §381, §590, §610, §633; ADR 0673 §2; *Tools are how a module is spoken to*) A publish sends the
artifacts the build stored, by their digests, to the holder of the registry seat of their kind, under the same
version, and builds nothing, so it takes no build capacity.
- **By itself, on an event.** The operator, 2026-11-11 between 15:14 and 15:42 CET at lead-47's terminal, decided by
ADR 0674 (the builder's publish again), which extends ADR 0673 to publishes: the builder
publishes again, by itself, every reserved version `version-unreleased` names (§369), on the node-engine seat's
`running` of a module whose catalog graph entry claims the registry seat of the missing artifacts' kind, on any
node, and on the node-engine seat's `manifest-reads-changed` with `why` `returned` of the node that holder runs on
(ADR 0673 §2, §590); one with `why` `started`, or with none, publishes nothing. No timer. The builder's manifest
`consumes` both events (it does for ADR 0673 already).
- **Each event once.** The builder acts on a message of those subjects at most once, by its stream sequence: it
keeps, in its old `state` section `acted`, one key per subject holding the last sequence it acted on, so the read
of §556 at its start acts on each subject's last message only when that is newer than the kept one, and a message
read again after a restart starts nothing. The keys are one per subject of those two events, at most one per
module and node, about 0.1 KiB each; the section is capped at 1 MiB.
- **One queue per holder, kept on the bus.** Each registry seat holder has one queue of versions to publish, sent
one at a time, each bounded by §369's 10 minutes; a version already queued or being published is not queued
again. The queues are kept in the builder's old `state` section `publish-queue`, one key per queued version,
`<holder module>.<module>.<version>` (each token escaped as a key's name is, §73), holding its artifacts' digests,
when it was queued and whether its publish has begun; a key is deleted when its outcome is said. At its start the
builder reads the section whole and takes up every queued version again, a begun one sent anew (a publish of the
same digests under the same version, which the holder takes as it takes §52's), so a restart loses none. The
section is capped at 4 MiB (about 0.5 KiB a key); a version that would pass it is not queued, said in
`version-unreleased` as `queue full`, and waits for the next event or the tool. One event queues at most
`republish-max-versions` versions (a setting, §633, 50 unless set), at most one per `MAJOR.MINOR` of each module
(§369's key), the newest keys first; the rest wait for the next event or the tool. `builder_publish_queue` (class
`read`) lists the queued versions per holder, in order, with when each was queued and whether it has begun, and
the `acted` keys with their sequences.
- **An artifact no store holds.** It is named once in `version-unreleased` as not stored, and is not tried again on
events: its build key is built again by `builder_build_again`, which reserves anew.
- **The failure still said.** A publish that fails again keeps `version-unreleased` with the holder's words and
waits for the next such event.
- **By a person's tool.** `builder_publish_again` (class `act`, §610, written in the hand-act log) takes `module`
and `version`, each optional, and `why`: with both, that reserved version's missing artifacts (§52); with `module`
alone, each reserved version of that module whose artifacts are not all published, at most one per
`MAJOR.MINOR`; with neither, every version `version-unreleased` names, one module after another, the newest key
first. It queues as the events do and answers at once with the versions it queued; each outcome is said as
`package-published`, or left in `version-unreleased` with the holder's words.
- **Before develop.** These lines rest on ADR 0674, accepted before G's builder is built from them.
Why: the operator's answer; §369 gave the tool one version, and a recovery of a registry holder away for hours
left many (issue 1966 note 206); the queue, the sequence, the cap and the unstored artifact are the design's
(review: health and events 5, running and testing 4 and 7). How it is checked: unit (builder), a version left
unreleased is published again on the holder's `running` and on its node's `returned`, not on `started` or no
`why`, once per message: a restart reading the same last message starts nothing; a version queued is not queued
twice; one event queues at most the setting's number; an unstored artifact is named once and not tried on the next
event; whole (builder, a fake registry holder away and back), every unreleased version published with no hand act,
one at a time; a restart with three versions queued publishes the three; a fourth past the cap is said `queue full`;
one call with neither argument publishes three modules' versions.
- §589 **`builder_build` answers a module's build keys whole.** (§348, §568, §575; *one-question-one-tool*) The
builder's tool `builder_build` (class `read`) takes `module` and, optional, `fingerprint`. With `fingerprint`, it
answers that key whole; with `module` alone, the module's build keys newest first, at most 20, each with its
`fingerprint` whole (the build-input fingerprint as the catalog graph entry carries it, never cut), `state`
(`queued`, `building`, `done` or `failed`), the key's revision and since when, the step and its node, the error
whole, `event-builds` and the open ask (§568), and `newest: <branch>` for each watched branch whose newest catalog
graph entry of the module names that fingerprint, read from the builder's own view of the catalog half (§348). A
module none of whose keys the newest entry names says so, naming the entry's fingerprint. Beside it,
`builder_builds_since` gains `module`, which narrows its answer to that module's builds, and `builder_stuck`
answers each key's fingerprint whole. Which node wants which fingerprint is the assigner's to answer, through the
seat `mesh-wants`, never the builder's (ADR 0596); the words of a want's hold (§575) may shorten a fingerprint for
reading, while every field named `fingerprint` in a tool's answer is whole. Why: no read answered a module's
wanted build whole in one call (lead-47, issue 1966). How it is checked: unit (builder), `builder_build` by module
answers its keys with whole fingerprints and marks the one the newest entry names; `builder_builds_since` with
`module` answers that module's builds alone.
## A node's capabilities, said once
- §590 **One event says a node's capabilities: `manifest-reads-changed`.** (§177, §226, §404; ADR 0618 §3, §4, ADR
0673 §2, ADR 0614 §4) The node-engine seat's event `manifest-reads-changed`, on
`mesh.seat.node-engine.event.manifest-reads-changed.<node>`, published only by that node's node-engine (ADR 0363
§2), is the one event of a node's stated capabilities. It carries, as the seat's definition already has it,
`node`, `manifest-reads` (every capability the engine states now, whole), `at`, and `why`: `started` when the
engine started or what it states changed, `returned` when its node is heard again after being away (ADR 0673 §2).
G adds no event `capabilities-changed`: §177, §226, §404 and G-4's event table name `manifest-reads-changed` in
its place.
- **The assigner** consumes it, asks the seat's `manifest-reads` of that node again (ADR 0618 §3, §4), and makes a
new assignment graph revision only when the capabilities answered differ from those its newest revision placed
on, so a `returned` that changes nothing makes no revision.
- **The builder** consumes it as ADR 0618 §4 and §568 have it, and builds a key again only on `returned`.
- **An older node-engine's event.** A `manifest-reads-changed` with no `why`, from a node-engine of before G, is
read as `started`, so it builds and publishes nothing again.
Why: two events of one fact, one decided (ADR 0618 §4) and one the design's (§226), are a second source of the
same fact, and ADR 0673 §2 builds on the decided one's `why` (lead-47, on ADR 0673). How it is checked: unit
(assigner), a `started` with new capabilities makes a revision and a `returned` with the same ones makes none;
the code review finds no `capabilities-changed` in any module or seat definition at G's heads.
## The bus's object stores
- §591 **Which cap governs which object store.** (§58, §477, §513, §547, §607) The bus module plans:
- **each `<state>-objects`** with 16 MiB, unless the state's `requires` names `objects-max-mib` (§547, which
replaced §607's 64 MiB, which replaced §513's 256 MiB);
- **each `<module>_sent-objects`** with 256 MiB (§477, kept by §607);
- **a foundation part's old `objects` section** with the `max-mib` it declares, `sent-objects` 256 (§477).
No store is planned with 1024 MiB; the bus module's earlier commit that did is changed to these. Every one of
them counts in §547's sum against the account's limit. Why: three lines in sequence named three caps for one
store (develop question NATS1, join of the bus module). How it is checked: unit (bus module's plan), a table of the
three kinds with their caps, and a state naming `objects-max-mib` planned at that size.
## The gate from G's delivery
- §592 **The gate's merge path from G's delivery, whole.** (§97, §150, §151, §156, §162, §164, §166, §167, §169,
§542; ADR 0666 §10, §11, ADR 0596) The gate makes no merge and hands none to the forge:
- **Who merges.** A session merges through the seat `git`'s `merge-pull` (ADR 0666 §11). The gate calls no
`merge-pull`, its manifest `invokes` none, and it says no `owed` act `merge`: `owed`'s acts are the delivery's
view, a comment and the note's line (§167). The merge as an owed act is not built.
- **How the gate learns a merge.** From the indexer's `take-in.taken`, one record per source revision of a
watched branch the indexer takes in, a group's members in one (§151). The gate does not consume the forge
bridge's `merged` and `pushed` messages on its stream `forge` (§509); those are the indexer's. It consumes from
that stream only `owed.ended` (§164), through a durable consumer its manifest's `consumes` declares and its
binding names, so the gate's code names no module: the forge bridge's module name leaves the gate's code.
- **A group.** Its members land one by one (*Groups*), its end the runner's `group-ended` (§431); §166's order of a
group's merges ends at G's delivery.
- **What was owed at the switch.** Each `owed` key the old bridge had not performed becomes an act (§169). Each
`merges` key it had not performed is said once as `approval-carried` (§542) and becomes no act. The gate writes
`merges` no more (§156) and reads it no more, and keeps it until §97's removal (§542).
- **A carried approval nobody uses is seen.** For each `approval-carried` it says, the gate writes the finding
`approval-carried-open` (scope the gate, id `<repository>.<pull>`, each escaped as a key's name is, §73), naming the
head the Approve named, and clears it when it hears the seat `git`'s `pull-merged` or `pull-closed` of that pull
request, which its manifest `consumes`.
- **The approval.** No Approve is asked per merge (§150); an `approval` step's answer is the runner's (ADR 0666
§10, §542).
This replaces the merge parts of §169 and §171, and §170 from G's delivery. Why: the join kept the old hand-off
(`merges`, the bridge's messages, the bridge named in code) beside the owed acts, and the lines were spread over
four places (join of the gate). How it is checked: unit (gate's manifest), no `invokes` of `merge-pull` and no
`consumes` of `merged` or `pushed`; the code review finds no module name of the forge bridge in the gate's code
and no act `merge`; whole (gate, bridge), a merge made through `merge-pull` opens one record from `take-in.taken`,
and a `merges` key at the switch is said `approval-carried` and performed by nobody, its `approval-carried-open`
raised and then cleared by that pull request's `pull-merged`.
- §593 **The gate hears neither the teller's answers nor the view's reach.** (§150, §431, §512) The gate's reads of
the teller's `answered` and the status view's `reached`, and its calls of `teller_answers` and `view_reach`, served
the Approve per merge, which goes at G's delivery (§150, §431): the gate's `asks` go with it. So the gate's
manifest declares neither event in `consumes` nor either tool in `invokes`, and its code calls neither; the commit
that added those calls is undone. `view_reach` stays the status view's own read tool, answered from the catalog
half (§196, test 61). Why: §431's "by their events" named the readers that stay, and the gate is not one of them
(join of the gate). How it is checked: unit (gate's manifest), neither event nor tool named.
## Readers of a module's manifest
- §594 **The readers take a module's manifest from its catalog graph entry.** (§196, §421, §443, §445; ADR 0656 §6,
ADR 0615 §1) The holders of `mesh-config`, `mesh-provisioner` and `mesh-self-check`, and the claude-code module,
read a module's manifest from the catalog graph entry that carries it whole (§443), taken through
`meshcatalogclient`'s view of the catalog half, which follows each watched branch's newest `catalog-revision` and
asks `revision` again on a gap (§196). None reads a manifest by digest from the indexer, its state `manifests` or
its tools: `manifests` is kept only for node-engines from before G (§445). Why: test 61's "no call of the indexer
by name" holds for every reader of which modules exist, and the entry carries the manifest (join of these readers,
RDR1 to RDR4). How it is checked: test 61, a row for each of the four readers; the code review finds no read of
`manifests` in them.
@@ -0,0 +1,598 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0673"
- "ADR 0672"
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0660"
- "ADR 0656"
- "ADR 0626"
- "ADR 0619"
- "ADR 0618"
- "ADR 0596"
- "ADR 0595"
- "ADR 0579"
- "ADR 0573"
- "ADR 0448"
- "ADR 0429"
- "ADR 0363"
- "ADR 0286"
- "ADR 0283"
- "ADR 0259"
---
# 47, milestone G: develop's seventh batch, the operator's answers of 15:14 to 15:42 CET, and the refreshed build maps
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§610 to §676 answer lead-47's seventh batch of 2026-11-11: the operator's answers of 2026-11-11 between 15:14 and
15:42 CET at lead-47's terminal (§610 to §613, and §588 in G-16), the design questions of the six refreshed build
maps, and develop's D-1 to D-15. Each other line is of internal form only, within what the records and the lines it
cites decide. A line of another file that one of these corrects is corrected in that line (§15, §30, §391, §394,
§423, §462, §515, §528, §570, §584, §585, §588, design 34 every-event §11, §19, §35, and design 46 7-approval-mark §22); here only what is new. A question one line answers
for several build maps is answered once, and the maps' names for it are given in brackets.*
## The operator's answers
- §610 **No verb keeps a list of named callers: every verb takes its class's default.** (§391, §392, §394; ADR 0286;
the records this ends are named below) The operator, 2026-11-11 about 15:40 CET at lead-47's terminal: "the operator
and agents may do everything, we might make it more strict and start asking for additional checks for agent stuff,
modules can get as many permissions as they want"; and about 16:10 CET: "we can always lock it down later, but we
most definitely don't want stupid things like 'named lists'".
- **The defaults.** A verb's definition names its class, and its callers are its class's default, which the
seat-definition reader applies: `read` takes `invokers`, `persons` and `sessions`; `act`, `warrant` and `accept`
take `operator`, `sessions` and `invokers`. `invokers` is every module whose manifest names the verb in `invokes`
(§391). A module tool's callers are its class's default alike.
- **No list.** A seat definition and a manifest name no `callers`: the seat-definition reader and the manifest
validator refuse the field, naming the verb or tool, and every `callers` G's heads carry goes in G's change. So
`git`'s `tag` (§370), the build seats' `run` (§229), `node-backup`'s `now`, `restore`, `aside` and `discard-aside`
(§405, §555), `restore` taking any module's item (no own-item limit), `mesh-recovery`'s `snapshot-kept` and
`restore-kept` (§554, §659), `mesh-broker`'s `snapshot-get` and `snapshot-restore` (§681), the node-engine's eleven verbs
(§393) and its `first-views` and `first-view-again` (§422), `operator-channel`'s `ask-again` (§394), the indexer's `indexer_unpin` (§579) and every hand tool of G
take their class's default. §392's prefixed forms and `holder:<seat>@same-node` (§336) are no longer written in
a definition; a part that must tell callers apart reads the account the bus proved (`by-caller`, ADR 0259 §3).
- **What stays.** Who may publish an event or a step's result stays its owner's alone (ADR 0363 §2, ADR 0666
§21), and a verb's own judgement of its input stays (`restore-kept` refusing `aside-only`, §516; `merge-pull`
counting a rerun only by the operator or the healers, ADR 0666 §11, §585).
- **The approval mark stays** (ADR 0286), so a `warrant` verb, or any verb a manifest marks, runs only on the
operator's Approve of that one call, whoever calls (§677).
This amends §391, §392, §393 and §394, and every line naming `callers`. Why: the operator's answers. How it is
checked: unit (seat-definition reader, manifest validator), a definition or manifest naming `callers` refused, and
a table row per class giving its default; whole (backup holder, recovery module), a `restore` from a session and
from the recovery module for another module's item taken, and a `snapshot-kept` from a session taken.
- §611 **Five parts move only their findings to templates in G.** (§529, §577) The operator, 2026-11-11 between 15:14
and 15:42 CET at lead-47's terminal: in G only the state `findings` and the finding stream of the teller, the
status view, the watchman, enrolment and the forge bridge move to `requires` templates (§529). Their other old
sections stay as they are, keys and names untouched: the watchman's `presence` and `stuck`, the teller's `answers`,
`asked` and `ends`, the view's `answers` and `reach`, enrolment's `enrolments`, and the forge bridge's `heads`,
`inputs` and stream `forge`. The manifest validator accepts these parts' other old sections until a later line
moves them; §577's rule for the finding words stands. [mesh-delivery DQ-4.] Why: the operator's answer. How it is
checked: unit (manifest validator), each of the five manifests of G's heads accepted with its old sections and its
finding templates.
- §612 **The forge module's and the image registry's other template entries become old sections, empty.** (§584) The
operator, 2026-11-11 between 15:14 and 15:42 CET at lead-47's terminal: the forge module's `incoming` (objects,
2048 MiB), `transfers` and `idle`, and the image registry module's `incoming` (objects, 8192 MiB), `transfers`,
`idle`, `findings` and stream `events`, move to old sections under the same names, each with the cap it declares
today, and start empty: nothing is copied. They hold uploads in flight, which their callers ask again, and
findings, which are stated again at the start (§384). The template entries, no longer declared, become orphans that
`orphans` lists, removed only by the operator's `drop` (ADR 0595 §4, §5), as §584 has it for `holds`, `windows`
and `mirrors`, which alone are copied. [mesh-catalog Q-F2.] Why: the operator's answer. How it is checked: unit
(manifest validator), both manifests of G's heads hold no template entry; whole (image registry), a first start
copies only `holds`, `windows` and `mirrors`.
- §613 **The gate's kept data, state by state.** (§15, §156, §172, §431, §542, §584) The operator, 2026-11-11
between 15:14 and 15:42 CET at lead-47's terminal:
- `pulls` and `asks` go with nothing copied (§172, §431): G's gate declares neither, and they are orphans that
`orphans` lists, removed only by the operator's `drop` (ADR 0595 §4).
- `merges` and `source` stay in the old `state` section (§542, §15).
- `deliveries`, `groups`, `owed` and `conditions` move to `requires` entries of the template `state`, under the same
names. The old sections' keys stay, read no more, so a revert's gate of before G finds them, until the operator's
`drop`.
- **Copied at every start that finds the old section changed.** The gate of G keeps, in a template state of its
own `copies`, one key per state with the old section's last revision it copied and when. At each start, before it
serves anything, for each of the four whose old section's last revision is newer than that key, it copies every
key changed since: a key whose write in the old section is newer than its template key's replaces it, a key
deleted in the old section after its template key's write is deleted in the template, and an older one is left.
A deletion G's gate makes itself in an old section (below) moves that state's mark in `copies` to the
deletion's revision in the same step, so the next start copies only what another gate wrote after it and never
deletes G's ended records from the template.
It then writes the mark, and says in its journal and in `gate_records` how many keys it copied and deleted of
each. So forward, a revert (whose gate of before G writes the old sections), and forward again carry every change
made while reverted, and nothing older overwrites a newer write.
- **What G ended, a revert does not take up.** When G's gate ends a delivery, a group or an owed act, it deletes
that key in the old section, the ended record staying in its template; so a revert's gate of before G finds only
what G left open, and never acts again on what G ended.
[mesh-catalog Q-G1.] This corrects §15's "the gate's other kept data stay in ADR 0595's templates". Why: the
operator's answer; the copy at every start is the design's (review: security and upgrade 5, running and testing 2).
How it is checked: unit (manifest validator), the gate's manifest of G's heads declares `merges` and `source` old
and the five templates; whole (gate), forward, then a revert's write and deletion in the old sections, then
forward: the template holds the reverted gate's write, the key it deleted is gone, and a key written in the
template after the revert is kept; a delivery G ended is not in the old section the reverted gate reads; a delivery ended, then a restart of G's
gate: its ended record is still in the template.
## Reading the catalog half, every part alike
- §614 **How a part reads the catalog half per watched branch, once for every part.** (§196, §199, §443, §459, §479,
§507, §521, §530, §532, §556; ADR 0656 §6, ADR 0596) Every part that reads the catalog half (a bundle or a process)
reads it so, and no part builds its own:
- **The reader.** Through `meshcatalogclient`, the client of the seat `mesh-catalog` in mesh-delivery's contracts
(ADR 0573 §1), wired on the one `go/views` reader the part already has: the bundle reader for a bundle, the process
reader (`views.ProcessReader`) for a process. Each watched branch is a view of that reader named
`mesh-catalog.catalog-revisions.<branch>` (§479), routed by §521, and the part's pass reads the views through that
reader alone.
- **The branches.** `meshcatalogclient` follows the branches itself, as mesh-delivery's code shared by every
reader of the seat: the registrations' watched branches, read by `registrations` at the start and followed by
`registration-changed` (§532); before the first registrations, the branches the gate's `source` answers, read at
the start and followed by the gate's `source-moved` (§507), which it stops following at the first
`registration-changed`. The gate's `source` given no branch answers `{branches}`, the branch of each
`source.<branch>` key it keeps, and given a branch that branch's source revision. mesh-sdk's `go/views` adopts and
drops the views (§532) and holds no seat's verb. The forge module, in TypeScript, follows the branches the same
way through the TypeScript form of `meshcatalogclient`, beside the seat's generated TypeScript contract in
mesh-delivery's contracts, on mesh-sdk's TypeScript view follower (§196).
- **The manifest entries every such part declares.** In `consumes`: `mesh-catalog.catalog-revision`,
`mesh-catalog.registration-changed` and `mesh-delivery.source-moved`. In `invokes`:
`seat:mesh-catalog.revision` (with the field `none-yet`, §617), `seat:mesh-catalog.registrations`,
`{verb: seat:mesh-delivery.source, fields: [branches]}` in the long form (§558), and `seat:mesh-catalog.entry` for a part that reads an entry by its fingerprint.
- **An older build's entry.** A part that resolves an entry by fingerprint (§459's reader, the node-engine's want,
§421) keeps beside its views the entries by fingerprint it asked of `mesh-catalog.entry`. When it hears a
`running` or a `wanted` naming a fingerprint that no branch's newest revision lists and it does not hold, it asks
`entry` once, as a kept call (ADR 0668 §1), and keeps the answer; it removes one when no `running` or `wanted` it
holds names that fingerprint any more. The reader it passes to a pure function (§459) answers from the views and
those entries alone, and reads no bus. Each kept entry is `asked`, `answered` or `not-found`:
- **asked**: the reader answers it as pending, a state apart from none; the part's health says it waits on that
fingerprint, and the node-engine's have says the phase `held` with its detail opening `entry-asked:` (§421's
`entry-missing` is said only once `entry` answered not found);
- **answered**: the answer starts a new pass at once;
- **not-found**: the part writes the finding `entry-not-found` (scope the part, id the fingerprint), cleared when
nothing it holds names that fingerprint or a later ask answers it.
`<module>_first_views` lists these entries with their state and since when, and the part's tool
`<module>_entry_again` (`fingerprint`; class `act`) asks one again now; the node-engine's are its seat's verbs
(§683).
- **A branch at `none yet`.** A view whose `revision` answered `none-yet` (§530) is ready and empty: the reader's
answer for that branch lists no entry.
- **The first pass.** A part acts on nothing until every branch it follows has its first view, `none-yet`
counting as one; until then `<module>_first_views` says each branch `waiting` and its health says it waits (§200,
§556). After its first views it acts on what it holds, and a view behind is read again by §196.
- **The node-engine follows no branch.** It reads the catalog half only by the fingerprints its wants name
(§421), through the entries above, and declares none of the branch entries; it never waits on a branch's view, the
registrations or the gate's `source`, so it composes the gate (and every part) before any of them answers, and no
cycle runs through it at G's delivery. It keeps those entries on its node and serves their rows and ask again as
its seat's verbs (§683). The grants part composes a want's account before its first views (§684).
- **A dropped branch.** When `go/views` drops a branch's view (§532), it clears, through the part's mesh-sdk writer (§639: a views reader writes its findings through the part's writer, never a writer of its own),
every finding that view raised (`first-view-unanswered`, `view-waits`, `view-unsaid`, `view-too-large`), says the
drop and each cleared finding in the part's log, and removes the view's row of `<module>_first_views` [D-12].
This is the one form for every reader of which modules exist and of the catalog graph's entries: the tool runner
(its owner of the assigner's views, §616, and its branches, [mesh-tools Q-R12]), the grants part [D-5], the
readers of §594, the node-engine (§421) and the Go build module (§615); and it answers mesh-sdk's question on
§532. Why: the same question came from five build maps, each about to build its own form (develop's D-5). How it
is checked: unit (`meshcatalogclient`), a branch added by `registration-changed` and one by `source-moved` before
the first registrations are followed, an entry asked once on a `running` of an unlisted fingerprint and dropped
when nothing names it, a pending entry read apart from none and a not-found one raising `entry-not-found`, a
`none-yet` branch read as empty; unit (gate), `source` with no branch lists the branches; whole (grants part, tool
runner), a part started with one branch at `none-yet` acts once every first view is in; a dropped branch's findings
are cleared; whole (node-engine), a want composed with no branch view and no `source` answered.
- §615 **The tool runner's and the Go build module's manifests come from the entry too.** (§594, §614, §443, §445,
test 61) The mesh MCP server of the tool runner takes each module's manifest from its catalog graph entry, through §614's
reader; its first view of the indexer's `manifests` and its `consumes` of `mesh-delivery-indexer.views.manifests`
go [mesh-tools Q-R2]. The Go build module's compose ask takes each manifest whole from the entry its fingerprint
names, by `seat:mesh-catalog.entry` (§506's `manifest-digest` naming the entry's), and its `invokes` of
`mesh-delivery-indexer.indexer_read_manifests` goes [mesh-catalog Q-B1]. Both join §594's readers. Why: test 61, and
`manifests` is kept only for node-engines of before G (§445). How it is checked: test 61, a row each.
- §616 **The assigner's views are named by the seat, in the code and in the manifest.** (§428, §7) The tool runner's
code reads the module that holds `mesh-module-assigner` from the catalog half's claims of that seat (§614) and
names the views `<that module>.assignments` and `<that module>.seat-holders` from it, never by a name fixed in code.
Its manifest's `consumes` names them by the seat, marked so as `invokes` marks a seat:
`seat:mesh-module-assigner.views.assignments` and `seat:mesh-module-assigner.views.seat-holders`, which the grants
part resolves to the seat's holder from the catalog half as it resolves a seat's events, so a new holder is
granted with no change of the manifest. The seat `mesh-module-assigner`'s definition declares its states
`assignments` and `seat-holders` as views (ADR 0571 §6), and the manifest validator refuses a seat-named view the
definition does not declare, naming it. The edge is of kind 6 from the seat's holders to the consumer, held until a
holder runs a build whose seat definition declares the view, as §558 derives it for a field. mesh-sdk's manifest
reader reads the `seat:` form in `consumes`, first in §494's second reader release (§621), which the parts reading
manifests take before G's merge. Design 46
7-approval-mark §19's holder of `operator-channel` is read from that view so found. [mesh-tools Q-R3, Q-R17.] Why:
§428 removes the name from code, and the catalog half says who claims a seat (review: seams and tools' note). How
it is checked: unit (tool runner), a fixture catalog half naming another holder of the seat gives views of that
name; unit (grants part), a seat-named view granted for the seat's new holder; unit (manifest validator), a
seat-named view the definition lacks refused; the code review finds no assigner
name in the tool runner's code or manifest.
- §617 **The long form of an `invokes` entry stands beside `*`.** (§558, §545) A manifest's `invokes` may hold `*`
and long-form entries together: the tool runner's is `*`, then `{verb: "seat:mesh-catalog.revision", fields:
["none-yet"]}` and `seat:mesh-catalog.entry` with no fields named [mesh-tools Q-R4, Q-R18]. The manifest validator
accepts a long-form entry beside `*`, and each named verb gives its edge of kind 6 (§558). In mesh-sdk's Go form,
`Invokes` stays the list of verbs, each long-form entry's verb among them, and `InvokeFields`, beside it, maps a verb
to the fields it names, so none of `Invokes`' readers changes [mesh-sdk 4]. Why: `*` lets a part call any verb, and a
long-form entry is how it says it relies on a verb or a field, which `*` cannot say. How it is checked: unit
(manifest reader), `*` with two long-form entries read into `Invokes` and `InvokeFields`; unit (manifest
validator), the tool runner's manifest accepted and its two edges derived.
## The tool runner
- §618 **The tool runner's own fingerprint reaches no bundle.** (§205, §519; design 46 7-approval-mark §5, §19)
`MESH_FINGERPRINT` is one of the tool runner's own words, which `BundleEnv` hands no bundle: a bundle reads only the
fingerprint the node-engine composed into its environment (§519). A bundle whose environment carries none has no
served entry the tool runner can read, so every marked call on that runtime is refused, as design 46 7-approval-mark
§5 and §19 have it. [mesh-tools Q-R5, extended.] Why: a bundle given the tool runner's own value is judged against
the tool runner's entry. How it is checked: unit (tool runner), a bundle composed with none sees none and its marked
call is refused; one composed with a fingerprint sees its own.
- §619 **A foundation part's bundle reaches its old `objects` sections through its membership.** (§407, §518) The grants
part issues each old `objects` section of a foundation part in the membership, as it issues its old `state`
sections: mesh-sdk's membership gains `Objects`, beside `State`, mapping each section's name to its object store.
The tool runner resolves a put or get session's `name` (§518) of a foundation part's bundle in that field, and of any
other part in its state's objects store and its sent store, as §518 has them. [mesh-tools Q-R6.] Why: §407 binds
that bundle to its old `objects` section and no membership field carried it. How it is checked: unit (membership),
the field read; whole (tool runner), a foundation part's bundle puts and gets a value over the limit in its old
section.
- §620 **A sent object over the limit is read by a get session.** (§59, §518) The tool runner's `mesh/objects.sent-get`,
asked for another module's sent object larger than the bus's per-message limit, answers `{chunked: true, size}` and
sends the object through §518's get session on the bundle's channel, read from the sender's sent store under the
standing grant §59 gives the caller. [mesh-tools Q-R7.] Why: a bundle calling a verb whose reply is larger had no
way to read it. How it is checked: whole (tool runner), a reply's object of 20 MiB read whole by a bundle.
- §621 **What the second reader release reads, and how it is released.** (§494, §545, §605, §508, §554) §494's release
reads, beside `calls` (§605) and the long form (§617), the tool runner's artifact word `states` [mesh-tools Q-R8,
mesh-sdk 3] and `by-caller` on a verb, which the seat definition's format gains for `snapshot-kept` and
`restore-kept` (§554) [mesh-contracts DQ-1] and every verb G gives the form (§681, §682), and the `seat:` form
of a seat-named view in `consumes` (§616). Since it also carries §508's validator rule, which is the indexer's,
the release is a delivery group of mesh-sdk (the reader's words) and mesh-delivery (the rule), checked and delivered
together [mesh-sdk 5]. Why: the running reader refuses an unknown word, and the rule is mesh-delivery's code. How
it is checked: unit (the release's reader), `states` and a verb's `by-caller` read; the group's one check.
- §622 **Where the capability words and `mesh/capabilities` are named.** (§462, §488, §576) mesh-sdk names each
capability word as a constant beside the calls of its family: the bus-state words (`bus-state-calls`,
`ledger-calls`, `ledger-upkeep-calls`, `sent-objects-calls`) in `go/buscalls`, and `put-sessions`,
`finding-calls` and `seat-settle-delay` with `mesh/publish` in `go/stdio.go` and `src/stdio`; the call
`mesh/capabilities` and its answer `{words}` are named there too, since `go/buscalls` holds only bus-state calls.
Which calls each word covers stays the tool runner's (§462). mesh-sdk's Go package names all four words its calls
need in `calls` (§605), the npm package `finding-calls` alone, since its code makes no session, ledger or sent-object
call. [mesh-tools Q-R9; mesh-sdk 1, 2, 11.] This amends §462's "mesh-sdk gains no capability list": it gains the
words, not the families. Why: the bundle fails naming a word (§488), so it must know it. How it is checked: unit
(mesh-sdk), each call's word; unit (tool runner), its families use mesh-sdk's constants.
- §623 **The forge module's fill is read through `mesh/state.info`.** (§585) A bundle's state call `mesh/state.info`
answers a state's bucket against its caps, so the forge module's read tool shows `failed-steps`' fill from it, not
counted from the keys. §585's fill bullet is corrected so. [mesh-tools Q-R11.] Why: the call exists. How it is
checked: unit (forge module), the fill read from the call.
- §624 **The tool runner's manifest `version` is §368's.** (§368, §583) The tool runner's manifest names `0.1`, the
`MAJOR.MINOR` of §368, as every manifest; design 34 every-event §11's "Its `version` stays `2`" is corrected.
[mesh-tools Q-R13.] Why: §368 is the later line and holds for every manifest. How it is checked: as §368.
- §625 **The act's build of a marked wait is the tool runner's own `MESH_FINGERPRINT`.** (§205; design 34 every-event
§35) The build an act of a marked wait names is the tool runner's own fingerprint, read from its `MESH_FINGERPRINT` at
its start (§205), the fingerprint the node-engine's `running` says of it; the tool runner consumes nothing more for
it. Design 34 every-event §35 is corrected so. [mesh-tools Q-R14.] Why: the node-engine hands each process its own
build (§205). How it is checked: unit (tool runner), the act carries the fingerprint of its environment.
- §626 **A module ends G with no more tests than it starts with.** (§419, test budget) §419's count starts, for each
module, at the commit of its repository's main from which G's branch of that repository starts (its merge base with
that branch), and the check compares the module's count at G's heads against it. This holds for the tool runner's
work of design 34 every-event and design 46 7-approval-mark: the new tables and the whole test of design 46
7-approval-mark §22 and design 34 every-event §19 replace existing tests, and every test they delete counts. A test
moved with its code (§655) counts in the module it moves to, as no new test there, and as deleted in the module it
left. [mesh-tools Q-R16.] Why: §419 is milestone G's rule and both parts ship in G's release. How it is checked: the
check counts each module's tests against its count at that commit.
## The node-engine and the node agent
- §627 **The node-engine is a foundation part.** (§6, §529, §577; ADR 0619 §1 to §3) The node-engine composes every
part, the bus module included, before the bus module can answer, so it is a foundation part as ADR 0619 §1 defines
one, as §584 has it for the forge module and the image registry. The manifest validator's list gains the holder of
the seat `node-engine` in G's change; its kept data stays in its old sections, its old stream `mesh-host-findings`
among them, and it opens them by their declared names (§577's old-name path), not by `Binding`. [the node-engine's map, RQ-1.]
Why: ADR 0619 §1. How it is checked: unit (manifest validator), the node-engine's manifest of G's heads accepted with
its old stream.
- §628 **G removes the node-engine's reads of the indexer and the builder alone.** (§421, §195, §435) In G the
node-engine's reads of the indexer's and the builder's states go, as §421 names them. Its reads of the assigner's
`assignment` and `candidates`, the provisioner's `provisions` and the vault's `signers` and `node-logins` stay in G
(§459's input among them), and `compose-candidate` keeps its reads of `candidates`, `manifests` and `verdicts` until
it goes with the checker (§435). §421's "holds no read of another module's state" is checked for the indexer's and
the builder's states. [the node-engine's map, RQ-2, RQ-3.] Why: G moves the reads §421 names; the others need verbs no line of G
gives, and they are reads of another module's state that ADR 0596 §2 ends when their owners move. How it is checked:
the node-engine's account holds no read of the indexer's or the builder's states.
- §629 **The have's wait and the node-engine's `wait-held`.** (§423, §498, §499, §541, §560) A node-engine's
`wait-held` and `wait-ended` carry the fields of the seat `mesh-wants`' wait (§498, §512: `owner`, `step`, `node`,
`on`, `bound`, `since`, `words`) and `reason`; `node` is the node waited on (the build node for a kept build), and
the subject's `<node>` the node that says it [mesh-contracts DQ-3]. The have's value gains `wait`, one such wait with
`ask-at` and `act` (§560), present while the have waits; `waits`, the composition digest of the phase
`waiting-yes`, stays as it is [the node-engine's map, RQ-4, mesh-contracts DQ-4]. This corrects §423's "with no new field". Why:
§499 and §560 need the wait in the have. How it is checked: unit (node-engine seat's contract), `wait` read with
both fields; whole (node-engine), a kept-build wait says it.
- §630 **At a revert the node agent reads the point from `kept-points`.** (§516, §560) At a revert the node agent asks
`seat:mesh-recovery.kept-points` for the module and kept data, takes the point of the form and fingerprint it reverts
from, and passes its `snapshot` and its catch-up to `restore-kept`; its manifest names the verb in `invokes`.
[the node-engine's map, RQ-5.] Why: `ended` is kept last per subject, so a later point replaces it. How it is checked: unit (node
agent), a revert after two points restores the one of its form.
- §631 **A manifest's `catch-up` is a tool and its arguments.** (§451, §516) A manifest declares `catch-up` as `{tool,
arguments}`, mesh-sdk's reader takes it whole (a string of before G read as `{tool, arguments: {}}`), and the node
agent passes it to `snapshot-kept` unchanged. [the node-engine's map, RQ-6.] Why: `snapshot-kept` takes both. How it is checked:
unit (reader), both forms read.
- §632 **The node agent's `why`.** (§516) It gives `snapshot-kept` the `why` "form change of `<module>`'s `<kept>` to
`<fingerprint>`" and `restore-kept` "revert of `<module>`'s `<kept>` from `<fingerprint>`", which the hand-act log
keeps. [the node-engine's map, RQ-7.] How it is checked: unit (node agent), both words.
- §633 **§557's bounds are `config` keys.** (§557) Each bound §557 makes a setting is a key of the part's manifest
`config`, read at its start through mesh-sdk's `stdio.Config`, its default the design's value; the node-engine's are
`recovery-wait-minutes` (90) and `view-waits-minutes` (5) [the node-engine's map, RQ-8], the condition keeper's two hours of §540
`owner-handover-minutes` (120) [D-7], and every other part's is named `<what>-<unit>` alike. The gate's own settings
stay where they are. Why: one form for every part's bound. How it is checked: each whole test sets its bounds in
its fixture's `config`.
- §634 **The node-engine's own name, everywhere it is used, is §527's.** (§428, §527) Its discovery, its finding
stream's subject and observer, and the vault's login names read the node-engine's own module from §527, as its
guard, compose and bootstrap do. [the node-engine's map, RQ-9.] How it is checked: the code review finds no fixed name.
- §635 **The node-engine invokes `withheld`; it gains no `uses`.** (§555, §558; design 46 7-approval-mark §6) Its
manifest names `seat:mesh-grants.withheld` in `invokes`, which gives the served-verb edge, and gains no `uses`.
[the node-engine's map, RQ-10.] How it is checked: unit (manifest validator), the edge derived.
- §636 **The private network module's name is its own manifest's.** (§428) `overlay.Name` stays the `module` the private network package's (`overlay`)
computed manifest names itself; mesh-networking removes the fixed `mesh-provisioner` alone, read from the seat
list's holder of `mesh-provisioner`. [the node-engine's map, RQ-11.] How it is checked: the code review.
- §637 **Withdrawn (the operator, 2026-11-12; A-320).** **Test 36 reads the builds of before G through the registry seat.** (§405, §555, §610) Test 36's run, in the
check the running checker makes of G (ADR 0667 §2), fetches the builds of before G and the recovery module by their
fingerprints through the seat `oci-artifact-registry`, read-only, under the checker's account, its manifest naming
the fetch in `invokes`, granted by §610's default. [the node-engine's map, RQ-12.] How it is checked: test 36 runs in that check.
## mesh-sdk's shared parts
- §638 **The asker client is mesh-contracts' `operatorchannelclient`.** (§570, §586; ADR 0573 §1) The client §570
names is `operatorchannelclient`, of the seat `operator-channel`'s contracts in mesh-contracts, which already
bounds the expiry; `Take` answers `Refused` with the router's words. mesh-sdk builds nothing for it. The fields
§570 names are the contract's: on `decided`, the warrant's `ask` (the ask's id) and `words` (the why); on the record,
`state` `refused` and `answer`, and the record gains `at`. §570 is corrected so. [mesh-sdk 6, mesh-contracts DQ-9,
DQ-8.] Why: a seat's client lives in its contracts package (ADR 0573 §1). How it is checked: unit
(`operatorchannelclient`), a refusal answered and an expiry past its level's bound refused.
- §639 **A views reader writes its findings through the part's writer.** (§461, §485, §580) `go/views`' findings
(`view-waits`, `first-view-unanswered`, `view-unsaid`, `view-too-large`) are written through the part's mesh-sdk
writer as kinds of their own (§580), and the writer's statement covers them beside `observe`'s. [mesh-sdk 7.] How it
is checked: unit (writer), a statement keeps the reader's open findings.
- §640 **The finding writer reads the generated types; it keeps no copy of the layout.** (§439, §452) mesh-sdk's
writer, in Go and TypeScript, defines the small interface it needs of a finding's body (its key's parts, its
`since`, its kind) and takes the types the seat generator emits for `findings` through it; it holds no
`FindingBody`, `FindingCleared`, `FindingsStated` or `FindingsAsked` of its own, so there is one layout and nothing
to keep equal. [mesh-sdk 8.] Why: a copy held equal by a test in another repository is not checked on a change of
mesh-sdk alone, and the TypeScript copy by none (review: running and testing 6). How it is checked: unit (writer,
both languages), a generated body written and stated; the code review finds no copy.
- §641 **A stream kept last per subject: where it is read and how it is told.** (§396, §556) mesh-sdk's
`HearSeatEvents` makes §556's read at the start with an ordered consumer, then follows the durable, and names the
stream `waiting` in `<module>_first_views` until the read ends [mesh-sdk 9]. The grants part tells such a stream by
the seat definition's `keep` of that event, and grants its ordered consumer from the reader's `consumes`
[mesh-delivery DQ-7]. `wait-held` and `wait-ended` of the node-engine each have their own stream (§396), with
`running`'s keep (no age limit, last per subject) [mesh-contracts DQ-2]; §556's "on the stream `running` is kept
on" reads so. How it is checked: unit (mesh-sdk), the start read then the durable; unit (grants part), the ordered
consumer granted from `keep`.
- §642 **A binding's `objects` is a state's field, and the sent store an entry.** (§399, §513) In mesh-sdk's
membership, `objects` is a field of a state's binding, and the sent store an entry of its own, local name
`sent-objects`, as §399 has it; §513's "fields of `membership.BusBinding`" reads so. [mesh-sdk 10.] How it is
checked: unit (membership).
- §643 **Put and get sessions are Go only in G.** (§518, §57) A TypeScript bundle's value over the per-message limit
fails at once, naming the limit, in G. [mesh-sdk 12.] How it is checked: unit (mesh-sdk TypeScript), the refusal.
- §644 **`Hearing` is told which events are defined.** (§474) `catalogue.Hearing` takes, beside the manifests, a
function the grants part passes, answering whether an event is defined by a seat definition of the seat list or is
one of mesh-sdk's finding events. [mesh-sdk 13.] How it is checked: unit (`Hearing`).
## mesh-delivery's parts
- §645 **The checker's seat definition is in mesh-delivery's contracts.** (§528) §528's "the checker's seat
definition in mesh-contracts" is corrected to mesh-delivery's contracts module, where `tag-wanted` is removed.
[mesh-delivery DQ-1, mesh-contracts DQ-13.]
- §646 **The runner's bodies name `fingerprint`.** (§585) The runner's `step-ended`, pipeline and wait bodies name the
build-input fingerprint `fingerprint`, not `fp`, as §585 reads it. [mesh-delivery DQ-2.] How it is checked: unit
(runner's contract).
- §647 **The runner times an ask from `ask-taken`.** (§535, §602) §535's five minutes run from the ask until the build
seat holder's `ask-taken` (§602), which the runner consumes; `step-started` is the runner's own. [mesh-delivery
DQ-3.] How it is checked: whole (runner), an ask no holder takes ends its pipeline after the setting.
- §648 **A `take-in.taken` entry carries the pull request.** (§476, §542) An entry is `{module, commit, fingerprint,
repository, pull}`, the last two from a `merged` message and absent for a `pushed` one. [mesh-delivery DQ-5.] How it
is checked: unit (indexer's contract).
- §649 **`ParseStepKey` answers the asker.** (§484) `buildstep.ParseStepKey(key)` answers `(asker, node, id, err)`,
`asker` empty for main's form. [mesh-delivery DQ-8.] How it is checked: unit, both forms.
- §650 **The `mesh-builds` client's name.** (§586) It is `contracts/go/clients/meshbuildsclient`, with `Reserved`.
[mesh-delivery DQ-9.]
- §651 **The checker's first tier of the change's own acting parts is not built.** (§528, §66, §279 to §290) The
checker of G's heads never makes G's own check (ADR 0667 §2), so the first tier of §279 to §290 (CK6 to CK8) is not
built in the checker; the runner's steps check a change's acting parts from G's delivery. [mesh-delivery DQ-10.]
- §652 **`pins-stated` is kept as `pinned` is.** (§579) The seat `mesh-delivery`'s definition gives `pins-stated` the
`keep` of `pinned`, so the indexer's consumer from the stream's first message hears it. [mesh-delivery DQ-11.]
- §653 **The teller's reads of the gate's `asks` go.** (§431, §512, §593) The teller's manifest drops its `reads` of
`mesh-delivery-gate.asks`, since the Approve per merge goes. [mesh-delivery DQ-12.]
## mesh-catalog's parts
- §654 **The readers of the forge module's old events and tools move to the seat `git`.** (§310, §311, §313, §431;
ADR 0659 §1) [mesh-catalog Q-F1.]
- **mesh-config** consumes `git`'s `pull-merged` and `pull-closed` in place of `gitea.pull.merged`,
`gitea.pull.closed` and `gitea.forge.pull-request.closed`, and invokes `seat:git.pull` in place of
`gitea.gitea_get_pull_request`; **the records module** consumes `git.pull-merged`.
- **The order.** Main's forge module says its own `pull.merged`, not the seat's events, so each consumer of
`git.pull-merged` or `pull-closed` is ordered after the forge module's version that says them, by the edge of kind
6 its `consumes` gives (§558); its durable consumer is made from the stream's first message, so no merge said
before it began is missed, and no catch-up through `git.pull` is needed.
- **mesh-issues** invokes `seat:git.pull` and `seat:git.contains`, and reads a pull request's steps from the
runner, `seat:mesh-pipeline-runner.list` and `seat:mesh-pipeline-runner.show` in its `invokes`: `list` given the
pull request's head commit answers that commit's pipelines, one per module of the repository it builds (G-6),
and `show` of each answers it whole with each step's result, the review step's among them (ADR 0666 §22). For a
repository of several modules mesh-issues shows one row per module's pipeline, each with its steps; a commit
`list` answers with no pipeline is shown as "no run". The forge's own
reviews are read nowhere: G keeps no review record (ADR 0659 §1). Its read of `seat:mesh-delivery.checks` and
`gitea_pull_reviews` go.
How it is checked: unit (manifest validator), each manifest of G's heads names none of the old events or tools;
unit (mesh-config, records), each acts on a `git.pull-merged` fixture; unit (mesh-issues), a commit with no
pipeline shown as "no run", and one of a repository of two modules shown as two rows.
- §655 **The take and restore of one stream is shared code in mesh-sdk.** (§554, §464; ADR 0672 §1) The code that
takes and restores one stream's snapshot moves whole from the bus module's `bussnap` into mesh-sdk's `go/bussnap`,
imported by the bus module (its snapshot before `recreate` and `drop`), by the recovery module (its points and
restores), and by any part that takes the nightly dump's stream snapshots; no module imports another module's
package, and no third copy is kept. The edge of kind 1 from mesh-sdk's package (§47) orders its publication before
the bus module's and the recovery module's builds. [mesh-catalog Q-R1.] Why: code two modules share lives in
mesh-sdk (§464's reason). How it is checked: the code review finds one copy.
- §656 **The hand tools' acts are verbs of the seat `mesh-recovery`.** (§554, §566; ADR 0672 §7) Each hand tool's act
is a verb of the seat `mesh-recovery`, `by-caller`, answered by the process: `take`, `restore`, `retry`, `aside`,
`discard-aside`, `restore-aside`, `cancel` and `expire-points`, each of class `act` (§610), beside `snapshot-kept`,
`restore-kept` and `kept-points`, all defined in mesh-contracts. The tools bundle's `recovery_*` tools call them,
named in its `invokes`, and hold no grant of their own; the bus proves the tool runner's account, as ADR 0672 §7
has the bundle call the seat's verbs. [mesh-catalog Q-R2.] Why: ADR 0672 §7 (review: seams and tools 2). How it is
checked: unit (mesh-recovery's contract), each verb read; whole (recovery module), each hand tool acts through its
verb. Each verb's fields and answers, and the `asker` each hand verb takes and logs beside the account the bus
proved, are §682's.
- §657 **The recovery module's health check is a tool.** (§554, §468, §561) The recovery module serves the read tool
`recovery_kept_points`, answering what `kept-points` answers, its bundle naming `seat:mesh-recovery.kept-points` in
`invokes`, and its declared health check names it. The check fails while an aside's record ends at `removed` and no
running act holds it, as §554 has it, which `recovery_kept_points` answers. §554's "calls `kept-points`" is
corrected so. [mesh-catalog Q-R3.] How it is checked: unit (manifest validator), the check names a served tool;
whole (recovery module), an unrestored aside fails the check.
- §658 **A module new in G starts with the tests its lines name.** (§419) A module new in G counts from none: its
tests are the checks its design lines name (for the recovery module, its one whole test and the unit rows of its
lines), within the per-module budget. [mesh-catalog Q-R4.]
## mesh-contracts' seat contracts
- §659 **`snapshot-kept` and `restore-kept` are of class `act`.** (§554, §610) Class `act`, taking its default, as
every verb does (§610). [mesh-contracts DQ-5.]
- §660 **A finding's key and value.** (§461, §437, §439) The state `findings` keeps the key `<scope>.<id>.<kind>` and the
value `#/bodies/Finding`. [mesh-contracts DQ-7.]
- §661 **`list` shows the caps.** (§547) The seat `mesh-broker`'s `list` answer gains `caps {sum-mib,
account-limit-mib, server-limit-mib}`, an absent limit meaning unlimited. [mesh-contracts DQ-10.]
- §662 **A subject function with parts can fail.** (§523) A generated subject function with `<part>` tokens answers
`(string, error)` in Go and throws in TypeScript for an empty part, `.` or `..`. [mesh-contracts DQ-11.]
- §663 **The condition keeper's sources are the bus module's `created` events, and it follows a moved stream.** (§515,
§529, §611, §612) The keeper takes a source when it hears the seat `mesh-broker`'s `created` of a part's
`finding-events` entry, named in its `consumes`, and a foundation part's old finding stream from the catalog half
(§614); at its start it reads both by `list` and the catalog half. When a part's catalog graph entry no longer
declares the stream the keeper follows for it (its findings moved to a template, §611, or to an old section, §612),
the keeper drops that source (the grants part its consumer, §690), keeps the part's open conditions, and asks `findings-asked` on the
new stream once it is a source; the part's statement there settles them (§384). §515 is corrected so.
[mesh-contracts DQ-12; D-7's COND9.] Why: the grants part makes the consumers (§386), and no binding names one; a
moved stream must neither raise twice nor leave a condition open (review: health and events 1). How it is
checked: whole (keeper, bus module), a part declaring finding events is asked once its entry is `created`; a part
whose findings move keeps its condition raised once and clears it from the new stream.
- §664 **`backed-up` needs no contract change.** (§563) The backup holder's answer carries a put-off item's `act` and
since when in its own fields. [mesh-contracts DQ-14.]
- §665 **`attempt-changed` is keyed by condition and attempt.** (§541, §73) The subject is
`mesh.seat.mesh-healers.event.attempt-changed.<condition>.<attempt>`, each token escaped as a key's name is (§73),
kept last per subject, 7 days and 16 MiB (§541), so two attempts on one condition both stay. At about 0.5 KiB a
message, 16 MiB holds about 30,000 attempts, far more than seven days have seen (an estimate). The stream discards
nothing new: a publish past its cap fails, and the healers say it in their health and write the finding
`attempt-unsaid` (scope the healers, id the condition), cleared by that subject's next publish taken. §541's
subject is corrected so. [mesh-contracts DQ-15.] How it is checked: unit (healers' contract), two attempts of one
condition kept; whole (healers), a full stream raises `attempt-unsaid`.
## Met in develop
- §666 **`nats_snapshot_get`.** Replaced whole by §681 (G-18): the keeper serves the seat `mesh-broker`'s
`snapshot-get` and `snapshot-restore`, the bundle lists by `nats_snapshots`, and a protected stream's snapshot is
never sent (the operator, O-7). [D-1.]
- §667 **Where a trusted change and the seed wait for their warrant.** (§3, §8; ADR 0666 §2, §12; ADR 0259 §6; ADR
0286 §10) [D-2, D-3.]
- **Kept.** The indexer keeps each change of `register`, `policy`, `watch` and `unwatch` awaiting its warrant in its
old `state` section `proposals`, one key `<verb>.<module>` (`<verb>.<module>.<branch>` for `watch` and
`unwatch`, each token escaped as a key's name is, §73), holding the change whole, its `digest` (`sha256:` of the
change in RFC 8785's canonical form), the current ask's id, its caller and when it was asked; the seed's key is
`seed`. Capped at 4 MiB.
- **Asked.** Through `operatorchannelclient`, kind `ask`, level `approve`, its `about` the key, so a newer change of
the same key replaces the open ask (ADR 0666 §12); the ask names every value the change moves (§3) and carries the
change's `digest`, so the warrant approves those exact values (ADR 0286 §10).
- **Heard.** The indexer's manifest `consumes` `operator-channel.decided`, on
`mesh.seat.operator-channel.event.decided.<the indexer's module>`, through a durable consumer. A warrant is
applied only when its ask's id is the key's current ask and its digest the key's, and when the router's own
record of that ask, read under the indexer's module's name on §700's path, holds it with `Warrant.For` (ADR 0286 §26, §27, §36),
never the decided event's body alone: it writes the version by
compare-and-set and ends the key `applied`. A warrant of a replaced ask writes nothing, said in its journal. A
Decline, an expiry or a refusal ends the key with that outcome; the seed's keeps `seed-waits` (§8).
- **Ended keys stay readable.** An ended key keeps its outcome, its ask and when, until the next change of the same
key replaces it.
- **A restart.** At its start the indexer reads each open key and the router's record of its ask on §700's path: a decided one is applied or ended as above, an open one waited on, and one the router no longer holds ended
`lost`, said.
- **Its tools.** `indexer_proposals` (class `read`) lists the open and ended keys with their asks and outcomes;
`indexer_proposal_drop` (`key`, `why`; class `act`, in the hand-act log) cancels the key's open ask and ends it
`dropped`; `indexer_seed_again` raises the seed's ask again from its key (§8).
How it is checked: whole (indexer), a `policy` applied on its warrant after a restart while open; a decline ends
the key `declined`, read by `indexer_proposals`; two `policy` changes of one module leave one ask, and the first
ask's late warrant writes nothing; a dropped key's ask is cancelled.
- §668 **`manifest-accepted`'s `problems`.** (§500; ADR 0579 §1) The event gains `problems`, a list of strings
(§692): each problem the indexer's re-judgement finds in a manifest it keeps accepted because its digest is
unchanged (ADR 0579 §1), the same items its verdict writes as warnings; an empty list when none. It is defined in
the seat `mesh-catalog`'s definition in mesh-delivery's contracts, a row of its contracts change. [D-4.] How it is
checked: unit (indexer), a manifest a stricter validator faults is accepted with its problems, and a mended one
says none.
- §669 **A have whose parts differ says the phase `held`.** (§524) §524 holds as written: the phase `held`, its detail
opening `parts-differ:` and naming each digest that differs, and `wait-held` with the reason `parts-differ`; the
build as made (H-8) stands. [D-6.]
## The builder's forms
- §670 **The key codec of the catalog half is a package of its own, which reaches no bus.** (§614) The codec of a
catalog key's parts (`KeyPart`, `ParseKeyPart`) is mesh-delivery's contracts package
`contracts/go/clients/meshcatalogkey`, which imports neither mesh-sdk's `views` nor the bus client;
`meshcatalogclient` keeps the wiring of §614 and imports it. The builder's `builds` package imports
`meshcatalogkey` alone, so it reaches no bus. [D-8.] Why: a package that only reads and writes keys needs no bus,
and the builder's `builds` package is held to that (its test `TestThePackageReachesNoBus`, red on main too). How it
is checked: that test, and one in `meshcatalogkey` that it imports no bus.
- §671 **`build.ended` names the node that keeps a kept build in `kept-on`.** (§271, §379, §526) For a kept build the
builder's `build.ended` carries `kept-on`, the build node that keeps it, once for the build, beside `made`, the name
§526 gives the want's field; every artifact of the build is kept on that node, and each artifact in `made` keeps its
digest as now. A build not kept carries no `kept-on`. [D-9.] How it is checked: unit (builder), a kept build's
`build.ended` names its node, and the assigner writes it into the want (§526).
- §672 **The builder's toolchain wait.** (§267, §276, §472, §557, §629, §633) The builder holds an entry while its
fingerprint names a toolchain version of its language other than the one the builder's newest `toolchain-agreed`
of that language names (§267).
- **Its state.** Each wait is kept in the builder's old `state` section `waits`, one key `<module>.<fingerprint>`
(each token escaped as a key's name is, §73), capped at 4 MiB, deleted when the wait ends.
- **Its events.** It says `wait-held` and `wait-ended` on its own old `streams` section `waits`, on
`mesh.stream.mesh-delivery-builder.event.wait-held.<module>.<fingerprint>` and `.wait-ended.<module>.<fingerprint>`,
kept last per subject, 7 days, 16 MiB; the body the fields of §629's wait with `step` `build` (§702), `on`
`toolchain:<language>@<version>`, `module` and `fingerprint`, and `wait-ended` its `how` (`ran` when a
`toolchain-agreed` names that version, `released` by `builder_release`). At its start it says `wait-held` again
for every key of `waits`. G-4's table reads so.
- **Its bound.** 60 minutes, its setting `toolchain-wait-minutes`; past it the builder writes the finding
`toolchain-waits` (scope `build`, id `<module>.<fingerprint>`, §702), cleared when the wait ends.
- **Its tools.** `builder_waits` reads the state, and `builder_release` builds the entry with the running build
module, written in the hand-act log (§276).
[D-10.] How it is checked: whole (builder, with small settings), an entry held until `toolchain-agreed` names its
version, then built and ended `ran`; one released by hand ended `released`; one past its bound raises
`toolchain-waits`; a restart says the open wait again.
- §673 **`package-published`'s one `dependencies` list holds both kinds.** (§374, §466) The contract's `dependencies`
in each artifact of `package-published` is §466's `resolved` and `toolchain` in one list: each item `{kind,
part, version, digest}`, kind 1 for each `resolved` package (its part the package's artifact reference, §504) and
one item of kind 3 for the toolchain (its part the agreed toolchain's part, its digest the building node's, §466).
The indexer writes §374's entries of kinds 1 and 3 from the items of those kinds. [D-11.] How it is checked: unit
(`mesh-builds`' contract), a list of both kinds read; whole (indexer), the release's kind-1 and kind-3 entries.
- §674 **`<module>_first_views` says the part's own build.** (§205, §424, §519, §618) Both answers of a part's first
views, the bundle reader's and the process's status tool's, gain `own-build` beside their list: the fingerprint the
node-engine composed into the part's environment (§519), or `unknown` when it carries none (§618). A process writes
it at its start at the key `own-build` of its state `first-views`, which its tools bundle's status tool reads.
[D-13.] How it is checked: unit (`go/views`), both answers with a fingerprint and with none.
## The indexer's entries
- §675 **`entry` finds an entry by its fingerprint, and keeps one while a want or a running build names it.** (§443,
§199, §556, §614) [D-14.]
- **By fingerprint.** The indexer keeps, in its old `state` section `entry-index`, one key per build-input
fingerprint, holding the digest of its entry's object (§179, §443); it writes the key when it first stores the
entry and deletes it when it removes the object. `entry` reads the key and then the object. Capped at 8 MiB (an
estimate: about 0.1 KiB a key for some tens of thousands of builds).
- **Its callers.** `entry` is of class `read`, so it takes `read`'s default (§391, §610), its invokers among them
(§614).
- **Kept while named.** The indexer consumes the node-engine seat's `running` and `mesh-wants`' `wanted`, read whole
at its start as §556 has a stream kept last per subject and then followed by a durable consumer, both in its
`consumes`. It keeps in its old `state` section `named` one key per fingerprint with the set of
`<node>.<module>` whose want or running build names it, written by compare-and-set, capped at 4 MiB. An entry
object is removed only at the first revision after no kept revision lists it and its `named` set is empty; a
`wanted` or `running` that moves on removes its `<node>.<module>` from the old fingerprint's set.
- **A node that leaves.** A `running` stays on its stream for ever, so a member a `running` put in the set leaves it
also when the want of that node and module is removed (`wanted` saying it removed: the node left or the module was
unplaced), whatever that node last said running.
- **Its tools.** `indexer_entries` (class `read`) reads a fingerprint's key, its object's digest and its `named` set;
`indexer_named_drop` (`fingerprint`, `member`, `why`; class `act`, in the hand-act log) removes one member.
How it is checked: unit (indexer), an entry no revision lists is answered by `entry` while a want names it and
removed after the want moves on; a member whose want is removed leaves the set; `indexer_named_drop` removes one;
a restart reads `named` whole before it removes anything.
- §676 **Each entry carries its artifacts' package names.** (§491, §478, §504, §412) At take-in the indexer reads each
artifact's package name from the revision's own files at the artifact's path (the `module` of its `go.mod` for a Go
package, the `name` of its `package.json` for an npm package, the manifest's image name for an image) and writes it
into the entry's artifact as `package`, `{ecosystem, name}`. The manifest validator judges §491 from the entries'
`package` fields, and the builder's mapping (§478, §504) reads them there. [D-15.] How it is checked: unit
(indexer), a Go and an npm artifact's names read into their entries; unit (manifest validator), two entries
carrying one name refused, naming both.
@@ -0,0 +1,536 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0676"
- "ADR 0674"
- "ADR 0673"
- "ADR 0672"
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0660"
- "ADR 0656"
- "ADR 0626"
- "ADR 0619"
- "ADR 0618"
- "ADR 0596"
- "ADR 0595"
- "ADR 0579"
- "ADR 0573"
- "ADR 0448"
- "ADR 0436"
- "ADR 0429"
- "ADR 0363"
- "ADR 0286"
- "ADR 0283"
- "ADR 0259"
---
# 47, milestone G: the fix round of batches 6 and 7
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§677 to §690 come from the fix rounds of the review by architectural domain of §587 to §676 (lead-47's inputs of
2026-11-11): the approval mark once no verb names its callers (the operator's answer of about 16:10 CET, §610); the
keeper's snapshots, with the operator's answer O-7 of 20:22 CET (§681); the recovery module's verbs, the entries the
node-engine keeps and the grants part's first composition (§682 to §684); the gate before a marked verb's running
(§677, the operator's O-8); develop's D-16 to D-49, of internal form only; and the operator's O-9 (§705, written whole in §706). The rounds' other fixes are written in the lines they correct.*
## The approval mark, with no named callers
- §677 **A marked verb a module's process serves is one call with a gate before its running, asked, checked and
claimed by its holder alone.** (§610; ADR 0676 §1 to §7; ADR 0286 §6, §10 to §13, §24, §26 to §31, §35, §36,
§38, §43; ADR 0259 §6) Once no verb names its callers (§610), a module's process may call a marked verb (ADR 0286 §1, §2)
directly. Design 46 7-approval-mark's gate wraps only the handlers the tool runner serves (its §20); a marked verb a
module's process serves is judged by that process, as ADR 0286 §43 has the controller judge its own verbs, whoever
calls it. The operator, 2026-11-11 about 20:40 CET at lead-47's terminal (O-8): "the tool call should be the same,
just with a 'gate' between the call and the execution"; so a module's call of a marked verb is one call:
- **Only Go holds the gate, and every marked verb is `by-caller`.** The gate is mesh-sdk's Go seat server's; the
manifest validator refuses a TypeScript process that serves a marked verb, and the seat generator makes every
marked verb `by-caller` (§697).
- **Refused before anything is kept.** A call whose arguments hold anything shaped like a secret or pass 16 KiB is
refused at once, asking nothing and keeping nothing (ADR 0286 §12; ADR 0676 §1).
- **The holder keeps the call and asks, under its own module's name.** Otherwise mesh-sdk's seat server makes the
act as ADR 0286 §10 and §11 make one (the address as called, the holder's own node and build, the seat and verb,
every argument, digested with `operatorchannelclient.ActDigest`), and finds or raises its ask through the key
`act.<digest>`: it writes that key with the bus's create, naming a new ask, and raises the ask through
`operatorchannelclient` under the holder's module's name at level `approve` (ADR 0286 §13, §15 to §23) only when
the create succeeds; an instance whose create fails reads the key and takes that ask, so two instances raise one
ask; a key whose ask has ended is replaced by compare-and-set on its revision. When the call says `wait: held`
(below), the holder keeps it as a waiting call of its own, `call.<id>`, `<id>` 128 random bits, holding the call
whole, the caller the bus proved and its ask (ADR 0286 §24; ADR 0676 §2), and answers at once `{asked: <ask id>, call: <id>}`.
- **A second call of the same act.** Every call is kept as its own `call.<id>`, whoever makes it; one made while
that act's ask is open takes the same ask (ADR 0286 §13, §23, §38; ADR 0676 §4) and its answer names that ask and its own
call. On the Approve the oldest waiting call of the act runs; each other waiting call of the act is asked anew at once,
as a new ask of that act (ADR 0286 §33: a second run takes a second approval), and its end comes when its own ask
ends.
- **On the Approve it runs that same call.** When the holder hears its own `operator-channel.decided` of the ask, it
reads the router's own record of the ask under its module's name, on the one path §700 names, never the decided
event's body nor the caller's word (ADR 0286 §6, §26, §36; ADR 0676 §3), holds the warrant to it with `Warrant.For`
and `Option.Performs` against the waiting call (ADR 0259 §6; ADR 0286 §27, §28), claims it once (below), runs the
waiting call, once, and
records the run in the hand-act log of the seat `mesh-hand-acts` as a kept call until it is taken (ADR 0286 §35,
ADR 0668 §1). The waiting call runs only within 30 minutes of the choice; heard later (the holder away meanwhile),
it ends `expired` and runs nothing (ADR 0286 §29). After a Decline the same act is not asked again for 10
minutes: a call of that act within them ends `declined` at once, with no ask (ADR 0286 §38; ADR 0676 §4).
- **The caller hears the outcome on its own call's subject.** The holder says the call's end on its seat's event
`gated-ended`, subject `mesh.seat.<seat>.event.gated-ended.<caller>.<call>`, `<caller>` the account the bus proved,
escaped as a key's name is (§73): `{call, ask, act, verb, outcome, answer, error}`, `outcome` one of `ran` (with
the verb's `answer`, and in its own field `approval` the line naming the approval, ADR 0286 §34), `declined`, `expired` and `refused` (with the router's or the check's words); a decline or
an expiry runs nothing and says so (ADR 0676 §2). Kept last per subject, one day, and no argument is in it: a caller down for
longer than a day loses that outcome, and reads the holder's `approvals` verb (below) for it. Every seat
definition with a marked verb gains the event through the seat generator; the grants part grants hearing
`gated-ended.<caller>.>` to that caller alone (ADR 0286 §6), from its `invokes` of the marked verb by name, as it composes a
`by-caller` verb. mesh-sdk's seat client, in `go/seats` beside the seat server, sends `wait: held` on every call
of a marked verb, waits as a kept wait (ADR 0668 §1) on its own `<call>` alone and reads it at its start, so a
restart of either side loses no outcome; it makes no second call. A module may not mark its call of a marked
verb safe to repeat (ADR 0668 §2): only the wait on `gated-ended` is kept. The calling module holds no warrant,
and the holder asks once per call; `mesh-delivery.release` stays unmarked (ADR 0286 §46), so ADR 0286 §47's
question is closed (ADR 0676 §7).
- **A person through the tool runner keeps ADR 0286 §37's path for now.** An MCP call cannot be held for an ask's
60 minutes (O-8; ADR 0676 §5); this "for now" is tracked by issue 417. A call that does not say `wait: held` (the tool runner sends none) is answered with ADR 0286
§37's refusal naming the ask and keeps no call, its ask found and written through `act.<digest>` as above, so the
same call made again after the Approve finds that ask, is checked as above, claimed and run. `wait` is no
boundary: either path runs the act only on the operator's warrant for that act, claimed once, so a module that
leaves it out only takes the person's path.
- **The holder claims, once, in its own state.** The holder's template state `approvals` (a `requires` entry, 48
hours to live, 64 MiB) keeps `call.<id>`, `act.<digest>` and `claimed.<ask id>`, the claim, written with the
bus's create, which refuses a key that exists, so a warrant runs one call, on one instance or two (ADR 0286 §30,
§31; ADR 0676 §3). At its start the holder reads every waiting call and acts on each ask already decided as above. A waiting
call past its ask's 60 minutes with no decided heard raises the holder's finding `gate-unanswered` (scope the holder,
id the call), cleared when its end is said. The manifest validator refuses a module that serves a marked verb from
a process and declares no such entry. A state that cannot be read or written refuses every marked call of that
holder, said as ADR 0286 §40 has it.
- **Its read.** Every seat with a marked verb gains the verb `approvals` (class `read`, its default), served by the
seat server: the waiting calls with their caller, ask and state, the `act.` keys and the claims, never an argument
of a call it was not asked by that call's caller.
- **A holder that does not check is never reached.** The grants part composes, for a module's process that serves
a marked verb, the subscription of that verb's subjects only when the catalog graph entry of the build its want
names (heard by `wanted`, §344) declares that `approvals` entry, which only a build of G's seat server passes the
validator with, so a G holder is granted before it subscribes; otherwise it composes the subscribe deny design 46
7-approval-mark §6 composes for a runner that does not state `approval-mark/1`, named in `withheld` with "its
holder checks no warrant". So a holder of before G serves none of its marked verbs until its G version runs, and
nothing runs unchecked. This changes ADR 0286 §7 and §8 for a process holder (ADR 0676 §6).
Why: the operator's answers O-6 and O-8; the holder alone can read the router's record of an ask made under its
own name (review rounds 2 and 3). How it is checked, as cases of the seat server's one whole test (a fixture marked
verb, its holder and a module's process): one call answered `asked` and run once on the Approve, its outcome heard
on `gated-ended` by that caller and no other; a repeated identical call kept as a second call, asked anew after the
first runs, each waking on its own `<call>`; two callers of one act, one ask, one run, the second asked anew; two
instances of the holder raising one ask and one claim, a race of two at `claimed.<id>` running once; a decline and
an expiry run nothing; a restart of the holder between the ask and the Approve runs the waiting call once; a warrant
replayed runs nothing; a call within 10 minutes of a Decline ended `declined` unasked; a waiting call heard 31
minutes after its choice ended `expired`; a secret-shaped argument refused unkept; a call with no `wait` refused with the ask and run
on the same call made again; a decided event whose body was altered runs nothing, the holder reading the router's
record; a warrant whose act is not the waiting call's runs nothing; the run found in the hand-act log; the `ran`
end read with its `answer` and its `approval` field, and the `refused` end with the router's or the check's words;
the outcome gone from `gated-ended` after one day; a caller holding a warrant of its own for an ask of its own still
asked (ADR 0676 §7); unit (seat client), the module's call client never marks its call safe to repeat; unit
(manifest validator), a module serving a marked verb from a process with no `approvals` entry refused; unit (grants
part), a holder whose wanted entry declares no `approvals` composed with the deny and named in `withheld`, one that
declares it granted before it runs.
## Develop's D-16 to D-18
- §678 **The forms of a finding's `owner` and `waiting-on`.** (§451, §540, §580) Each is one of three forms, written as
patterns in the finding layout's definition: a session `^[a-z0-9][a-z0-9._-]*@[a-z0-9][a-z0-9-]*$` (who@node), a seat's
name `^[a-z][a-z0-9-]*$`, or a commit's author `^forge:[A-Za-z0-9._-]+$`. mesh-sdk's finding writer (§580) refuses any
other, naming the field; the contract's fixtures read each form. [D-16.] How it is checked: unit (writer), each form
written and another refused.
- §679 **`reserved`'s answers.** (§582, §586) `reserved` answers `{module, version, artifacts, released}`, `released` a
boolean (as §530's `none-yet`), or `{error}`, a string; `module`, `version` and `artifacts` are required only without
`error`. [D-17.] How it is checked: unit (`mesh-builds`' contract), the three answers read.
- §680 **Where the seat generator and the build modules' conformance kit live.** (§464, §495) §495's "the seat
generator is imported by no running module" reads "by no running module but through `steprun`'s regenerate and
compare". The build modules' shared conformance kit is `sdk/steprun/conformance` in the delivery SDK, which
mesh-catalog's build modules and their checks import. [D-18.] How it is checked: the code review.
## Round 2 of the review: the keeper's snapshots, the recovery module's verbs, the entries the node-engine keeps
- §681 **The keeper's snapshot is listed by a tool, sent by the keeper, and never sent when it holds a protected
stream.** (§554, §567, §610; ADR 0675; ADR 0626 §1, §3, §4; ADR 0436 §3, §5; ADR 0672 §1, §8; ADR 0667 §12) This
replaces §666 whole. [D-1; review rounds 2 and 3.]
- **When it is taken.** The bus module's keeper, taking the snapshot before a `drop` or `recreate` (ADR 0595 §5),
writes into that request's record in its state `requests` the snapshot's `path` (under its snapshots directory),
`size`, `digest` (`sha256:` of its bytes) and `protected`, true when the entry has a stream ADR 0436 §3 lets no
other account hear (the mail passwords' among them), before it touches the live data. A snapshot it cannot write
whole and record so, or one that would pass the directory's cap `snapshots-cap` (a setting, 20 GiB unless set),
makes the `drop` or `recreate` refuse, naming why, before anything is removed. Only the snapshots it recorded
count against the cap; one of before G does not.
- **Its fill.** Past 80 % of the cap the keeper writes the finding `snapshots-filling` (scope the bus module, id
`snapshots`), naming the fill and the cap, and clears it once the fill is back under 80 %, each judged at every
snapshot taken and removed and at its start.
- **Who deletes it.** The keeper alone, a snapshot and its record kept `snapshot-keep-days` (90 unless set, ADR
0667 §12) after its request: it removes those older at its start and at each new request, each removal written in
its journal; nothing waits on a timer, so one may outlive its days until then, which `nats_snapshots` says as
`expired`. By hand, the seat `mesh-broker`'s verb `snapshot-remove` (`request`, or `expired: true` for every
expired one; `why`; class `act`, its default; `by-caller`), written in the hand-act log first, removes one now; a
snapshot of before G it removes only by `request`.
- **Listed.** The tools bundle's `nats_snapshots` (class `read`) reads the keeper's state `requests`, as ADR 0626
§4 has the bundle read it, and lists each snapshot: its request, module, local name, stream, `size`, `digest`,
`at`, `protected`, `expired` and `recorded`.
- **Sent by the keeper.** The keeper's process serves the seat `mesh-broker`'s verb `snapshot-get`, `by-caller`,
class `read`, taking its default (§610). It takes `request` and `call`, 128 random bits the caller chooses, and
answers `{request, module, local, stream, size, digest, at, chunks}`, then sends the file in chunks of at most
512 KiB on `mesh.seat.mesh-broker.chunk.snapshot-get.<call>.<n>`, each acknowledged by the caller on
`mesh.seat.mesh-broker.ack.snapshot-get.<call>.<n>` within 30 seconds before the next, a missed one ending the
send, said in its journal. The verb says so in its seat definition by the field `chunks: true`, a word of the
definition format that §494's second reader release reads (§621, §697), costing no rebuild beyond that release's.
From it the grants part composes the holder's publish of `mesh.seat.<seat>.chunk.<verb>.>` and subscribe of
`.ack.<verb>.>`, and the reverse for an account that names the verb by name in `invokes`; a `*` entry grants no
chunk subject. Since every such account may ask any snapshot it could hear by the read default, hearing another's
chunks gains it nothing it could not read with the read default, and a protected snapshot's chunks are never
published. A caller granted no chunk subjects (a session through the tool runner) is answered
the fields alone, with `chunks` 0, and no file is sent. The verb answers `error`: `snapshot damaged`, naming the
request, when the file's digest differs from the recorded one; `snapshot missing` when no file is there;
`snapshot protected` when `protected` is true; and `snapshot taken before milestone G` for a request record that
holds no `digest`. The read default widens who reads a dropped or recreated module's data beyond its owner (ADR
0595's kept data reached only by its owner), for this snapshot alone (ADR 0675).
- **A protected snapshot is restored in place.** The operator, 2026-11-11 20:22 CET at lead-47's terminal (O-7):
the stream snapshot the keeper took before a `drop` or `recreate` of a protected stream is never handed out, and
is restored only in place by the bus module on the recovery module's request; the recovery module's own asides
of restricted streams stay as ADR 0672 §8 and §9 have them. The keeper's process serves `mesh-broker`'s verb
`snapshot-restore`, `by-caller`, class `act`, taking its default (§610): it takes `request`, `verify` and `why`,
answers at once `accepted` with an act id, `busy` or `error` (as §691's form), checks the file against its
recorded digest, and with `verify` true says that and restores nothing; otherwise it refuses `names live` while a
live object of the stream's name exists, before anything else, and restores the snapshot's stream under its own
name, bounded at 60 minutes. It says the outcome on `mesh.seat.mesh-broker.event.snapshot-restored.<request>.<act>`
(`{request, act, outcome, error}`, kept last per subject, 90 days), so a retried restore is told apart from an
earlier one. A restore that fails, passes its bound or is cancelled removes the keeper's own partial stream (it
held no live data, since `names live` came first) and then says `snapshot-restored` with `outcome` `failed`
under that act, before the keeper answers anything more of it; one cut off by the keeper's restart is ended so at
its start. `mesh-broker`'s verb `snapshot-restore-cancel` (`act`, `why`; class `act`, its default, `by-caller`)
cancels a running restore so and answers `accepted`, or `error` when that act has ended. "Protected" means a
stream ADR 0436 §3 restricts. The keeper holds a standing grant of the bus's
stream restore subjects and their chunk subjects for the bus names of the streams ADR 0436 §3 restricts, and only
those, and uses them only on a `snapshot-restore` call.
- **Its use.** `recovery_restore` names a snapshot by `request` (§682). For one that is not protected the recovery
module reads it through `snapshot-get` into `incoming/<request>` in its own data directory, refusing before the
first chunk when `size` would pass `points-cap` (§554), checks the bytes against `digest` before it takes any aside
(§567), restores it itself (§516), and deletes the file when the act ends, done or failed, and at its start any
file left there. For a protected one it asks `snapshot-restore` with `verify` true, then sets the live data aside
(§516), then asks `snapshot-restore`, and waits on `snapshot-restored` of that request and the act it was answered,
bounded at 90 minutes, past the keeper's 60. It puts its aside back under the live names (§516) only on that
event's `failed`. At its bound it first asks `snapshot-restore-cancel` and waits on the same event for the
cancel's outcome; only when the keeper answers neither, it puts nothing back, ends `failed` with
`snapshot-restore unanswered` and writes `aside-unrestored` (§561), which `recovery_restore_aside` (§566) clears,
refused while the names are live. Its
account (§554) hears `snapshot-restored`, from its `consumes`.
- **A snapshot of before G.** A request record with no `path` or `digest` was written by a keeper of before G: it
is listed with `recorded` false, `snapshot-get` and `snapshot-restore` refuse it as above, and the keeper of G
neither sends nor deletes it by age: it stays where the keeper before G wrote it until `snapshot-remove` names it.
- **Its record.** ADR 0675, which changes ADR 0626 §1 and §3, ADR 0436 §3 for the keeper's process, and ADR 0672
§1, §8 (its read opened to the read default) and §10 (a protected snapshot restored under the stream's own name by
the keeper), keeping ADR 0436 §3 and §5 for every other account.
Why: the operator's answers O-6 and O-7; the tools bundle runs as the tool runner and cannot read the keeper's
directory (review rounds 2 and 3). How it is checked, as cases of the bus module's one whole test and the recovery
module's one whole test, adding no test function to either (with small settings): a snapshot of three chunks sent
and restored whole; a missed acknowledgement ending the send; `snapshot missing`; a file altered on disk refused
`snapshot damaged`; a session through the tool runner answered `chunks` 0; a snapshot removed after
`snapshot-keep-days` at the next request; `snapshots-filling` raised past 80 % and cleared under it; a protected
snapshot refused by `snapshot-get` and restored in place by `snapshot-restore`, refused `names live` while its live
stream exists, a retried restore woken by its own act; no chunk of a protected snapshot ever published; a restore
stopped midway leaving no partial stream; the recovery module's bound cancelling first, then restoring the aside; a request record of before G listed `recorded` false and
refused; a `drop` past `snapshots-cap` refused before it removes anything; unit (grants part), only the keeper
publishes the chunk subjects, a `*` entry gets none, and the keeper alone holds the restore subjects of the
restricted streams' names and of no other stream.
- §682 **The seat `mesh-recovery`'s hand verbs: their fields, their answers, and who asked.** (§516, §554, §566, §656;
ADR 0672 §4 to §6, §12; ADR 0386 §8) [D-19; review round 2, seams and tools 8.] Each hand verb is `by-caller`, of
class `act` taking its default (§610), defined in mesh-contracts with fixtures, and takes, beside its own fields,
`why` (required) and `asker`, the caller the tool runner proved and recorded for that call (ADR 0386 §8: who@node,
or `operator`), which the tools bundle's `recovery_*` tool passes and which a module's process leaves out. `asker` is a stamp,
no boundary (as ADR 0626 §4 has it): it is taken from any caller and logged beside the account the bus proved, so
a false one is seen. The
hand-act log's entry names both the account the bus proved and `asker`: the bus's is the boundary, `asker` names
who asked, for the record (as ADR 0626 §4 has the stamp). Each answers at once, as `snapshot-kept` does (§554):
one of `accepted` (the act's id), `busy` (the id of the act that runs) or `error` set alone (§691); the result
comes on the act's ending event (§554, §560), under its `act` id.
- `take` (`module`, `kept`, `fingerprint`): takes a point of that kept data at that fingerprint's form, as
`snapshot-kept` does with no `catch-up`; ends with `ended`.
- `restore` (`module`, `kept`, and exactly one of `snapshot`, a point's name, or `request`, a keeper's snapshot
by its request, §681; `from-backup`, yes or no, only with `snapshot`): restores as `restore-kept` does; ends with
`restored`.
- `retry` (`module`, `kept`): asks again the last failed act of that module and kept data, with its fields;
`error` `nothing to retry` when its last act did not fail; ends with that act's ending event.
- `aside` (`module`, `kept`): sets the live kept data aside without restoring (§516); ends with `set-aside`.
- `discard-aside` (`module`, `kept`, `id`): removes that aside's directory; ends with `aside-discarded`.
- `expire-points` (no field of its own): runs §551's removal of points past their 90 days now; ends with
`expiry-ended`.
- `restore-aside` (`module`, `kept`, `id`) and `cancel` (`act`) as §566 has them; `cancel` answers in the same
form (§691): `accepted` with the id of the act it cancelled, whose ending event follows under that id, or
`error` naming the step the act has begun.
- `restore` and `restore-kept` are two verbs, both kept: `restore-kept` is the node-engine's (§516), taking
`form`, `fingerprint` and `snapshot` and refusing `aside-only` and `discard-aside`; `restore` is the hand verb
above, taking a point by `snapshot` or a keeper's snapshot by `request`.
Why: the developer's D-19; the bundle calls on the tool runner's account, so the log would name the runner, not the
asker (review round 2). How it is checked: unit (mesh-recovery's contract), each verb's fields and answers read
from its fixtures, `restore` with both `snapshot` and `request` refused by the schema's `oneOf` (§701); `retry`
with nothing failed answered `nothing to retry`; `cancel` of a begun act refused naming its step; whole (recovery module), a hand act's log
entry names the tool runner's account and the asker.
- §683 **The node-engine keeps the entries and wants it composes from in its composition state.** (§421, §614,
§627, §422, §693; ADR 0593 §6) [review rounds 2 and 3.] The node-engine, a foundation part (§627), composes the bus
module and the indexer that serves `mesh-catalog.entry`, so it never relies on `entry` answering at a restart:
- **Kept on the node.** Each entry it holds (§614, *An older build's entry*) and the wants of its last composition
are kept in its composition state on the node (ADR 0593 §6), beside the composition, written whole before the
composition that uses them; an entry is removed when no want or `running` it holds names its fingerprint, and a
module's want when `wanted` says it `removed` (§693). At its start it reads them before it asks anything, so a
restart composes the bus module and the indexer while both are down; only a fingerprint it holds no entry for is
asked of `entry`.
- **They give way to the bus.** The kept wants stand only until the seat `mesh-wants`' stream answers its read of
§556; from then the stream's wants are the ones it composes from, so a revert then an upgrade never composes from
a want older than the stream's. Until its first live answer its have says it composes from kept wants and
entries, and a kept part it cannot read is said in its have and its log, that module composed as its running
composition stands and its entry asked of `entry`.
- **Its first start.** The node-engine of G names `seat:mesh-catalog.entry` in its `invokes` (§614), so by §558's
kind 6 its G version runs on a node only after the indexer of G answers `entry`; the bus module and the indexer
of G are composed by the node-engine of before G, as on main. A node-engine of G at its first start, or on a newly
adopted node, holds nothing kept and asks `entry` of the indexer that runs.
- **Its rows and its ask again, on its seat.** It serves no module tools (§422): its kept entries and kept wants
are rows of the seat `node-engine`'s verb `first-views` (§698), and the seat gains the verb `entry-again`
(`fingerprint`, `why`; class `act`, taking its default, §610), both in mesh-contracts with fixtures, in place of
§614's `<module>_first_views` and `<module>_entry_again` for it.
Why: the node-engine composes before the bus answers (§627). How it is checked: whole (node-engine), a node-engine
restarted with the bus module and the indexer stopped composes both from its composition state; a kept part made
unreadable is said and asked again; a revert then an upgrade composes from the stream's wants once it answers;
unit (the seat's contract), `first-views` rows and `entry-again` read from fixtures.
- §684 **The grants part composes a want's account before any branch view.** (§614, §507, §558, §374) [review round
2, seams and tools 1.] The grants part follows the branches as §614 has it, from the gate's `source` with no branch
(named in its `invokes` in the long form, `{verb: seat:mesh-delivery.source, fields: [branches]}`, §558), so its
full pass waits on the gate's G version (kind 6), while the gate's G version waits on the grants part composing its
account (kind 5). So, until every branch it follows has its first view, the grants part keeps the user list it last
wrote and composes, for a want whose account it holds none of or whose fingerprint changed, that one account from
the want's catalog graph entry by fingerprint (§614, *An older build's entry*) and the assigner's views alone, as it
composes any account, every marked subject in it denied while the marks are unread (design 46
7-approval-mark §19); it writes that account into the user list it keeps, and says in its log and in
`<module>_first_views` that it composed it before its first views. Its full pass, once the first views are in,
composes every account again as always. So the gate's G version is granted before it answers, and no cycle stands.
- **The indexer's own account.** `entry` is served only by the indexer of G, so the grants part of G names
`seat:mesh-catalog.entry` in its `invokes` and, by §558's kind 6, runs only after the indexer of G answers;
the indexer of G's account is composed by the grants part of before G, as on main, so no cycle runs through the
indexer.
How it is checked: whole (grants part, gate), with the gate of before G running and a want of the gate's G
version, the grants part composes that account before any branch view, and its full pass once `source` answers;
whole (delivery plan), G's indexer is composed while the grants part of before G runs, and the grants part's G
version is ordered after it.
## Develop's D-20 to D-23
- §685 **A condition carries `owner` and `waiting-on`.** (§451, §540, §678) [D-20, mesh-catalog COND7.] The seat
`mesh-conditions`' definition gives a condition, in its state `conditions` and in every condition event, the
fields `owner` and `waiting-on`, both optional, beside `resolver`, which keeps its own meaning (who resolves it: `self`,
`operator`, `agent` or `healer:<name>`). Each takes §678's three forms, refused otherwise, naming the field. The
keeper sets both from the finding when it raises the condition, and changes them only as §540 says (the two hours'
handover to `operator-channel`, and `conditions_owner`), saying each change as it says any change of a condition.
A condition kept from before G carries neither and is read as having none; the keeper writes them into it at its
next change.
How it is checked: unit (mesh-conditions' contract), both fields read in the state and the events in each form;
whole (keeper), a `pipeline-failed` raised with the author's `forge:` owner and changed to `operator-channel`.
- §686 **The generated finding type carries its accessors; the writer takes it through an interface.** (§439, §640)
[D-21.] The seat generator emits, beside each Go type generated from the finding layout of `mesh-conditions`'
`bodies`, in the same generated package of mesh-contracts, methods on its pointer: one getter and one setter per
field the writer reads or fills (`kind`, `scope`, `id`, `at`, `since`, `severity`, `summary`, `headline`, `source`,
`subject`, `observer`). mesh-sdk's Go writer defines, in `go/views`, the interface `views.FindingBody` with those methods and takes a
`views.FindingBody` and a constructor of one (`func() views.FindingBody`, the generated `New`); it uses no generics. In
TypeScript the generated type is structural: the writer's interface names those fields as properties, set by
assignment, and no setter is generated. How it is checked: unit (seat generator), the methods generated; unit
(writer, both languages), a generated body written, its `observer`, `at` and `since` set by the writer.
- §687 **mesh-sdk lays out the views reader's findings itself.** (§639, §686) [D-22.] The reader's four findings are
laid out by mesh-sdk, through §686's constructor and setters, and the part supplies only the constructor: `kind`
the finding's; `scope` the writer's own, as every finding it writes; `id` the view's name
`<owner>.<state>.<branch>`, escaped as a key's name is (§73); `severity` `warning`, but `urgent` for
`view-too-large`, which the part cannot read past; `source` `views`; `subject` `{scope: module, id: <the part's
module>, machine: <its node>}`; `summary` and `headline` fixed texts naming the view and what it waits on. The
part-supplied body function goes. How it is checked: unit (`go/views`, and the TypeScript follower), each of the
four written with those fields.
- §688 **The views reader's four kinds are always its writer's.** (§580, §639) [D-23.] The writer of a part that
holds a views reader always owns `view-waits`, `first-view-unanswered`, `view-unsaid` and `view-too-large`, added
to its stated kinds when `Kinds` is set, so its statement covers them (§580). A module whose bundles share one state
`findings` holds a views reader in one bundle only (in G the forge module's TypeScript tools, §196), which the code
review checks. How it is checked: unit (writer), a writer made with `Kinds` states the four with them.
- §689 **The hand-written finding helpers go; mesh-sdk's writer is the one writer.** (§461, §503, §640, §660) [D-24,
mesh-contracts K15.] With the state `findings` holding `#/bodies/Finding` under `<scope>.<id>.<kind>` (§660), the
finding helpers of mesh-contracts' `meshconditionsclient` go, as its `findings` package does (§503): `CheckFinding`,
`FindingFor`, `FindingWriter` and `FindingState`, `FindingVersion`, `Observation` (with its `Key` and `Finding`),
`EncodeFinding`, `DecodeFinding`, `Says`, `FindingBucket`, `FindingsBucket`, `Raise`, `Clear` and `CallUnanswered`.
`CheckFinding`'s rules (the key's parts, the kind, §678's forms) live only in mesh-sdk's writer, which refuses a
finding breaking them before it writes, and which exports that check for the keeper as `views.CheckFinding(b views.FindingBody) error`, in `go/views`, returning the
first rule broken, naming its field (§686; D-35). What concerns conditions
stays: `Key`, `EventFor`, `ShowKeeper`, `SilenceAction`, `Order`, `SilencedAt`, `Escalated`, `ResolverHealer`, the
scope, resolver and severity words, `KindCallUnanswered`, `CallUnansweredAfter` and the package `heard`. The
importers move in G's change: every part that writes findings writes them through mesh-sdk's writer and the
generated type (§686), its texts written into `summary` and `headline` where `Says` made them: in mesh-catalog
`artifact-store-tools`, `claude-code`, `distribution` and `distribution-tools`, `package-registry`, `mesh-build-go`,
`mesh-config`, `mesh-healers`, `mesh-provisioner`, `mesh-self-check`, `mesh-vault`, `mesh-watcher`, `object-store`
and `nfs-server`; in mesh-delivery the assigner, the builder, the checker, the indexer and its tools, the teller,
the status view and the watchman; in `mesh-host` the node-engine; in mesh-tools `node-tools/internal/firstview`. The
keeper (`mesh-conditions`) reads the findings others write into the generated type and judges them with mesh-sdk's
exported check. How it is checked: the code review finds none of the removed helpers at G's heads; unit (writer),
a finding breaking each rule `CheckFinding` held refused before it is written.
- §690 **Who drops the keeper's consumer of a moved finding stream, and the gate's `source` in mesh-contracts.**
(§386, §611, §612, §614, §663, §684) [D-25, mesh-catalog COND9.]
- **The grants part drops it.** The grants part, which makes the keeper's consumers (§386), deletes the keeper's
durable consumer on a part's stream at its first composition from a catalog graph entry of that part that no
longer declares the stream, and makes the consumer on the new stream from the same entry, as §386 does for a
module that declares the events later. The keeper deletes no consumer: when it hears, through its catalog half
reader (§614), the part's new entry, it stops reading the old source and keeps the part's open conditions.
- **The keeper takes the new source.** For a template stream on the seat `mesh-broker`'s `created` of the part's
new `finding-events` entry, for an old section's stream from the catalog half, as §663 has it; it binds the
consumer the grants part makes as a kept call (ADR 0668 §1: a call made again on its schedule until it succeeds,
kept in the keeper's state), so a consumer made after `created` is bound once it exists, the source said waiting
in its log meanwhile; once bound it asks `findings-asked` there once (§663). Past 10 minutes unbound it writes the
finding `consumer-unbound` (scope the keeper, id the part's module), cleared once bound.
- **The message on a template stream.** A finding event on `mesh.bus.<module>.finding-events.<event>.<node>` is
mesh-sdk's event envelope that every event publisher writes, its body under `body`; the keeper reads the module
and the node from the subject's tokens and the event's name from `<event>`. [D-36.]
- **The gate's `source` in the seat's definition.** The seat `mesh-delivery`'s definition in mesh-delivery's
contracts (built there, not in mesh-contracts) gives
`source` the optional input `branch` and two answers: with `branch`, that branch's source revision, as on main;
without, `{branches}`, the list of the branch names of the `source.<branch>` keys the gate keeps (§614), the
field `branches` that §684's long-form `invokes` names; with a fixture of each.
How it is checked: unit (grants part), an entry whose finding stream moved composes the old consumer's deletion and
the new one's making; whole (keeper, grants part, bus module), a part whose findings move to a template is bound on
the new stream after its consumer is made, its condition raised once, and `consumer-unbound` raised past 10
minutes while unbound; unit (`mesh-delivery`'s contract), both
answers of `source` read from their fixtures.
## The operator's O-8, and develop's D-26 to D-31
- §691 **The recovery module's contract, as the developer wrote it where no line spoke.** (§516, §554, §560, §567,
§682, §396) [D-30, mesh-contracts K7 to K9.] Kept as lines: every verb of the seat `mesh-recovery` that acts
(`snapshot-kept`, `restore-kept`, `cancel` and every hand verb of §682) answers one form, an object with exactly
one of `accepted` (the act's id; for `cancel` the cancelled act's), `busy` (the id of the act that runs) or
`error` (why) set; `cancel` answers no `cancelled` field [D-32];
the seat's family is `mesh-recovery.events`, 7 days, for any event naming no `keep`; a point's `digests` is a list
of `{name, digest}`, each a bus object of the point and the `sha256:` digest of its bytes (§567); `kept-points`'
`running` is `{act, step, since, waits-on}`, `waits-on` present only while the act waits, and absent when no act
runs. Corrected: `expiry-ended` has no parts, so it is kept 90 days and not last per subject (the definition
validator refuses `last-per-subject` on an event without parts); §554 and §560 read so. How it is checked: unit
(mesh-recovery's contract), each shape read from its fixtures and `expiry-ended`'s `keep` accepted.
- §692 **`problems` are the manifest validator's strings.** (§668; ADR 0579 §1) [D-26, indexer IX28.] The manifest validator's problems are
plain strings and gain no rule names: `manifest-accepted`'s `problems` is the list of those strings, the same the
verdict writes as warnings, in the same order, and `{rule, words}` is not written. How it is checked: unit
(indexer), a faulted manifest accepted with the validator's strings.
- §693 **`wanted` says a want removed.** (§675) [D-27, indexer IX29.] The seat `mesh-wants`' event `wanted` gains
`removed`, a boolean, true when the assigner removes the want of that node and module (the node left, or the module
was unplaced); `fp` is then the fingerprint last wanted there, and the subject is the one that want was said on, so
the stream kept last per subject keeps the removal as that want's last word. The assigner says it in the same
write that removes the want. Every consumer of `wanted` reads `removed` in G's change: the indexer (§675) removes
the member from `named`; the grants part (G-9 §344) composes no account for that want; every §614 reader removes
that want from what names a kept entry; the node-engine (§683) removes its kept want. How it is checked: unit (assigner), an unplaced module's want said `removed`; unit
(indexer), a `removed` empties that member from `named`; unit (grants part, each §614 reader, node-engine), each
reads a `removed` so.
- §694 **An image's package name is its module's and its artifact's.** (§676, §491) [D-28.] An image artifact's
`package` is `{ecosystem: image, name: <module>/<artifact>}`, its module's name and its artifact's name from the
manifest joined by `/`, unique as module names are; the indexer writes it at take-in, reading no file for it, and
§676's "the manifest's image name" reads so. How it is checked: unit (indexer), two modules with an image artifact
of one name carry two package names.
- §695 **An old state section's cap is its `max-mib`.** (§469, §494, §588, §667, §672, §675) [D-29.] A cap the design
gives an old `state` section is declared in that section's entry of the part's manifest as `max-mib`, the word §469
added to the old section and §494's reader release reads: the indexer's `entry-index` 8, `named` 4 and `proposals`
4, the builder's `publish-queue` 4, `acted` 1 and `waits` 4 (§672; its stream `waits` keeps its 16 MiB), each in G's change of that manifest; the grants part
makes the bucket with it (§494). How it is checked: unit (manifest validator), each of those manifests at G's heads
declares its caps; whole (grants part), a bucket made with its cap.
- §696 **A pipeline wait's `on`.** (§534, §498, §266) [D-31, mesh-contracts CT14.] A wait with `step` `pipeline`
has `on` the module of the runner's new version whose healthy run ends it (the holder of
`mesh-pipeline-runner` the new revision names, read from the catalog half), and `words` names that version's
fingerprint. How it is checked: unit (runner's contract), a `pipeline` wait read with its `on`.
- §697 **`by-caller` reaches the mesh only after the reader release.** (§621, §494, §554) A seat definition that
marks a verb `by-caller` (§554's `mesh-recovery`, §681's `mesh-broker` verbs, every verb §677 gates) is read only by
§494's second reader release, merged and delivered before G's merge, whose publish (kind 1) rebuilds every reader
of seat definitions; those definitions reach the mesh only in G's merge, so the order is the release's before G
and needs no edge of the module graph. A reader of before the release that met one would refuse that definition,
which blocks only new claims of the seat (ADR 0636 §3). How it is checked: G's check reads every definition with
`by-caller` through the release's reader.
## The final fix: develop's D-33 to D-38
- §698 **The node-engine's `first-views` rows and `entry-again`'s answer.** (§683, §422) [D-33.] `first-views`
answers `{first-views, entries, wants}`: `first-views` as today; `entries` a list of `{fingerprint, state, since}`,
`state` one of `asked`, `answered` and `not-found`; `wants` a list of `{module, fingerprint, kept-since}`, the kept
wants of §683. `entry-again` answers as `first-view-again` does: `asked` `{v, fingerprint, why, by, at}` and `read`
`first-views`. How it is checked: unit (the seat's contract), both answers read from fixtures.
- §699 **A state a seat offers as a view says so.** (§616; ADR 0571 §6) [D-34.] A state entry of a seat definition
takes the optional field `view: true`; the seat `mesh-module-assigner`'s states `assignments` and `seat-holders`
carry it. The definition format's schema and mesh-sdk's `seatdef` reader gain the field, read first by §494's
second reader release (§621, §697); the manifest validator refuses a `seat:` view in `consumes` naming a state
without it. How it is checked: unit (`seatdef`), `view` read and an unknown field still refused; unit (manifest
validator), a view of a state without `view` refused.
- §700 **A process reads the router's record of its asks from the bus, under its own name.** (§667, §677; ADR 0259
§3; ADR 0286 §26) [D-37.] The one path: the read of the seat `operator-channel`'s state `asks` under
`<module>.`, which the grants part composes for every user of the seat (ADR 0259 §3; design 46 7-approval-mark
§7), on the process's own account; its value carries the ask's `warrant` (7-approval-mark §14). mesh-sdk's
`operatorchannelclient.Client` is given, as its `Record`, a get of that key on the process's own bus connection
(`go/process`), as the tool runner gives it the runtime's `mesh/seat.record`; `Where` and `Warrant.For` are the
same. The indexer (§667, its *Heard* and *A restart*) and every holder of §677 read so; the verb `asks` stays as it
answers. How it is checked: unit (`operatorchannelclient`), `Where` through a process's get; whole (indexer), a
warrant applied from that read.
- §701 **`resync`'s and `retire`'s classes; `restore`'s one of two.** (§610, §682) [D-38.] The seat
`mesh-conditions`' `resync` is of class `act`, and the seat `mesh-vault`'s `retire` of class `warrant`, since its
definition binds it to the operator's warrant on exactly its node, module and name; each takes its class's default.
The seat `mesh-recovery`'s `restore` input holds `snapshot` and `request` under a `oneOf`, so the schema refuses
both or neither, and the holder answers that refusal as `error`. How it is checked: unit (each seat's contract),
the classes read; `restore` with both refused by the schema.
## The final fix: the builder's D-39 to D-44, and two fields
- §702 **The builder's toolchain wait, in the contract's words.** (§672, §629, §546, §604) [D-39 to D-44.]
- **Its finding's scope** is `build` (a scope the finding layout has), id `<module>.<fingerprint>`; §672 reads so.
- **Its step** is `build`, the contract's word; §672's `building` reads `build`.
- **Its body is the contract's wait.** The seat `mesh-wants`' wait in mesh-delivery's contracts gains `owner`,
`on` and `words` (§629, §512 name them), and `module`, `fingerprint` and `how` as optional fields; the builder
says its `wait-held` and `wait-ended` with that type and keeps no body of its own.
- **The build module of a language** is read through the builder's language mapping as it stands
(`buildrun.BuildModuleOf`, from the seats' `acts` rules in the seat list); §546's words name that read.
- **A superseded wait ends.** When the builder holds an entry of a newer fingerprint of the same module, it ends
the older wait with `how` `superseded` and deletes its key.
- **Two build seats report a toolchain.** §604's "the three build seats' `toolchain-reported`" reads "the Go and
node build seats'": the image build seat's builds name no toolchain and it reports none.
How it is checked: unit (builder), a wait said with the contract's type, scope `build` and step `build`; a newer
fingerprint ends the older wait `superseded`.
- §703 **A stream kept last per subject says so in its manifest, and `running` says `stopped`.** (§695, §396, §365)
A manifest's `streams` entry takes `last-per-subject` (yes or no, no unless set), as a seat event's `keep` does
(§396), read by §494's second reader release beside `max-mib`; every old `streams` section the design keeps last
per subject (the builder's `waits`, §672, among them) declares it in G's change. The node-engine seat's `running`
body says a module no longer held by `stopped: true`, with no `fingerprint`, as §365 has it; the seat's definition
gains the field. How it is checked: unit (manifest validator), `last-per-subject` read; unit (the seat's
contract), a `running` with `stopped` read.
- §704 **The indexer's seed, `unwatch`, its asks and its history, in the contract's words.** (§2, §8, §402, §667)
[D-45 to D-49.]
- **The seed's `steps`.** For each artifact of a module's entry the indexer asks `steps` of the build seat whose
`acts` rule `build` has `on` matching that artifact's language or kind (§203, §324), read from the seat list as
the builder's mapping reads it (§702), with `{module, artifact, fingerprint, fetch}`: the artifact's key in the
entry, the entry's build-input fingerprint and the entry's `fetch` of its source, as the entry carries them.
- **Its findings.** `call-unanswered` has scope `call`, id `<seat>.steps`, cleared when that seat answers;
`seed-waits` has scope `seat`, id `mesh-catalog.seed`, cleared by the warrant and kept after a Decline or an
expiry (§8).
- **`unwatch`'s placements** are read through the seat `mesh-module-assigner`'s verb `placement`, named in the
indexer's `invokes`: a placement names the branch when its `build` is a fingerprint listed by that branch's newest
revision and by no other watched branch's.
- **An ask's lifetime** is the router's approval maximum less ten minutes, as mesh-config's asks are.
- **A branch named `candidate` or `entry`** is refused with the reason `reserved branch name`, which the seat
`mesh-catalog`'s contract gains, not `unprotected branch`.
- **`registration-history`** is declared with no age limit and a cap of 64 MiB (`max-mib`), discarding a new
message when full rather than dropping an old one, so no version is lost unsaid: a registration change whose
history write is refused fails, naming the stream, and past 80 % the indexer writes the finding
`registration-history-filling` (scope `seat`, id `mesh-catalog.registration-history`), cleared under it.
How it is checked: unit (indexer), the seed's `steps` input per artifact; `unwatch` refused through a fixture
`placement`; `candidate` refused with `reserved branch name`; a full history refusing a change and naming it.
## The operator's O-9: a delivery waits on another machine's network
- §705 **A machine whose network names an outside cause holds the delivery there as a wait, not a failure.** (ADR
0677) The operator's answer O-9 of 2026-11-11 about 21:05 CET; the line is written whole in §706 (G-19), which
replaces what stood here.
@@ -0,0 +1,159 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0677"
- "ADR 0668"
- "ADR 0603"
- "ADR 0596"
- "ADR 0564"
- "ADR 0539"
- "ADR 0429"
- "ADR 0241"
---
# 47, milestone G: a delivery's wait on an outside cause, and the writer's own refusals
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§706 and §707 come from the review by architectural domain of lead-47's batch 8 (round 1, 2026-11-11 about 21:20
CET). §706 writes the operator's O-9 whole, once, in place of §705: what counts as an outside cause, when it is
judged, every way the wait ends, its event, its finding and its tools. §707 amends §580 for the finding writer's own
refusals.*
## A delivery's wait on an outside cause
- §706 **A failing check on a machine whose network names an outside cause is a wait that ends by itself, and asks
the operator after 30 minutes.** (ADR 0677 §1 to §4; ADR 0241 §11, §13, §16 to §21, §25 to §27; ADR 0564 §2, §3,
§13; ADR 0596; ADR 0429 §6; ADR 0539 §18; ADR 0668 §1; ADR 0603 §11; §276, §461, §498, §512, §633, §700) The
operator, 2026-11-11 about 21:05 CET at lead-47's terminal (O-9): a delivered module's own failing health check is
the delivery's; while the machine's network names an outside cause, the delivery waits on that machine, says from
the start what it waits on, judges again once the cause clears and goes on by itself, and after 30 minutes asks the
operator to go on or count it as failed. Where the assigner judges a node's health after a want moves (the canary
rule and its hold, ADR 0429 §6, ADR 0539 §18), for a module the delivery moved to a machine M:
- **Where the causes are said.** The node-engine only states M's network in its health report (ADR 0241 §13). The
watchman, holding `mesh-watchdogs`, raises the network findings from every machine's report together (ADR 0241
§16 to §21, observed by it under ADR 0564 §2), and the condition keeper, holding `mesh-conditions`, holds them as
conditions, never held while a module is delivered (ADR 0564 §13 (e)). The assigner hears them only through the
seat `mesh-conditions`: its verb `conditions` and its events `condition-raised`, `condition-changed` and
`condition-cleared` (ADR 0564 §3), named in its `invokes` and `consumes`; it never reads the watchman's `findings`
nor the keeper's state (ADR 0596).
- **What counts as an outside cause for M**, read from a condition's `key`, `raised` and subject (`machine`,
`also`), the key being the only source of an owner (a finding has no owner field):
- `machine.M.<owner>.rewritten`, or `machine.M.rewritten` (a file of no module), whose `<owner>`, the key's
part between M and `rewritten`, is no module the delivery moved to M (ADR 0241 §17, §25; ADR 0677 §1);
- `machine.<x>.unreachable` for another machine `<x>` whose `also` lists M, the machines whose failing parts
point at `<x>` (ADR 0241 §19, §21, §25; ADR 0677 §1);
- `machine.<x>.network` or `machine.<x>.silent` for another machine `<x>` whose `also` lists M: when `<x>` is
down on the record by its own network or its silence, the watchman raises no `unreachable` for it and holds
M's failures toward it under `<x>` (ADR 0241 §20, §21). The watchman (mesh-delivery's
mesh-delivery-watchman) therefore lists in `also` of those two findings the machines whose failing parts point
at `<x>`, as it lists them on `unreachable`; today it names them only in the `network` finding's summary and
not at all on `silent`. A change of `also` rewrites the finding (ADR 0564 §2).
No other condition is one. A `machine.M.<owner>.rewritten` whose `<owner>` the delivery moved to M is that
module's failure (ADR 0241 §26; ADR 0677 §3), and M's own `machine.M.network` holds M as a whole (below); neither
takes this wait.
- **When it is judged.** When a moved module's check fails on M, the assigner reads M's open conditions through
`conditions` with `scope` `machine` and `machine` M, which answers each condition whose subject's `machine` is M
or whose `also` lists M (ADR 0564 §3), as a kept call (ADR 0668 §1). It is read at each judging, never kept as a
copy from the events, since the keeper says no event when only a condition's `also` changes (it says
`condition-changed` for a severity, a resolver, a silence or its words). With an outside cause open, the move
waits. With none open, the assigner settles: it holds its judgement for `outside-settle-seconds` (a setting,
§633, 90 unless set) from the failing check, judging again on each `condition-raised` whose subject's `machine`
is M or whose `also` lists M, since the node-engine states the failing check and the network in one report while
the condition rises only after the watchman's two looks and the keeper's take-in; past it with still none, the
failure is the delivery's, held as the canary rule has it. The settle says no `wait-held`; `assigner_waits` shows
it as `settling` with its end.
- **When the keeper does not answer.** While the read of `conditions` is unanswered, the move waits `on`
`conditions-unread`, said and bounded as below, and is judged when the kept call is answered. A module that holds
`mesh-conditions` itself, as the assignments name, is judged with no read: its failing check is its delivery's
failure after the settle, since the keeper cannot answer for its own failing check.
- **The wait.** The assigner holds that move as a wait, says `wait-held` on `mesh-wants` with `step` `outside` (a
value added to §498's list, apart from a dependent's `step: delivery`), `node` M, `module`, `fingerprint`, `on`
(`rewritten:<owner>`, `rewritten` for no owner, `unreachable:<x>`, `network:<x>`, `silent:<x>` or
`conditions-unread`), `bound` (`outside-wait-minutes`, a setting, §633, 30 unless set) and `clock-from`, the
wait's first start. When the wait stands on another cause, it says `wait-held` again with the new `on` and the
same `clock-from`, so the bound is never restarted (ADR 0677 §2). It writes, through mesh-sdk's finding writer
(§461), the finding `delivery-waits-outside` (scope `delivery`, id `<node>.<module>`), severity `warning`, its
summary naming the module, M and the cause by its condition's key, its `needs` "Nothing for you to do.": a
warning that needs nothing is not sent (design 45, the router's *When*), so the operator's one message is the ask
at the bound. The watchman writes no `delivery-waits` for `step: outside`, so no second condition rises for it.
- **Judged again.** A waiting move is judged again, reading M's conditions as above, on: the `condition-cleared`
of its cause's key; each `condition-raised` or `condition-changed` whose subject's `machine` is M or whose
`also` lists M; each change of that module's have on M; the kept call answered, for `conditions-unread`; and the
assigner's start, for every wait it holds. A `condition-cleared` of a key no wait names is dropped. M leaving an
`also` with nothing else changing says no event: it is heard at the next of these, or at the ask.
- **How it ends**, each said on `wait-ended` with its `how`, the finding cleared at once:
- `healthy`: the module runs healthy on M, the cause open or not; the delivery goes on.
- `cleared`: judged again after its cause cleared, healthy; the delivery goes on.
- another outside cause open at a judging: no end; the wait stands with the new `on` (above).
- `own-failure`: judged again failing with no outside cause open, after the settle; it is the delivery's
failure, and only then does the canary rule hold it (ADR 0677 §1).
- `go-on` or `failed`: the operator's answer to the ask (below), or a release by hand with that `how`.
- `replaced`: a newer want of that module on M replaces the waited one; its own move is judged afresh, and an
open ask is ended.
- **The bound and the ask.** Past `outside-wait-minutes` from `clock-from`, the assigner asks the operator through
`operatorchannelclient`, `about` `<node>.<module>.<fingerprint>`, naming the cause and since when, with two
options: Go on (the module counts healthy there by the operator's word, and the delivery goes on, `how` `go-on`)
and Count as failed (held as the canary rule has it, `how` `failed`) (ADR 0677 §2). It reads the answer from the
router's record of the ask under its own name, on §700's path, never from the decided event's body nor a
caller's word. The wait's end ends the ask. An ask that ends unanswered is raised again at once while the wait
stands. `how` `go-on` is recorded as such, so a Go on is never read as a passed health check.
- **M's own network.** A machine whose own `machine.M.network` is open is held as a whole: no want moves to it
until its `condition-cleared` is heard (ADR 0241 §27; ADR 0677 §3). The assigner keeps that set from its
consumer of the three events and from a read of `conditions` with `scope` `machine` at its start; while that
read is unanswered, wants move as if none were held, since the keeper is itself delivered by wants moving, and a
failing check there meets the judging above.
- **Its tools** (§276's row *delivery on an outside cause*): `assigner_waits` reads each outside wait, settle
included, with its module, M, fingerprint, cause key, `on`, `clock-from`, `bound`, its ask's id and its state;
`assigner_release` takes one with `how` `go-on`, `failed` or `rejudge` (judge it again now) and a `why`, class
`act`, its default, and writes the hand-act log of the seat `mesh-hand-acts` first (ADR 0603 §11).
Why: the operator's answer O-9 and ADR 0677; the review's round 1 asked the mechanism once and whole: the watchman
holds a machine's failures toward a machine down on the record under that machine (ADR 0241 §20), so the
commonest cause, a hub silent or its own network open, is a `network` or `silent` finding and needs M in its
`also`; the keeper says no event for `also` alone, so the read at each judging is the truth, and the settle covers
a condition rising after the check it explains. How it is checked, as cases of the assigner's whole test (a
fixture keeper answering `conditions` and saying its three events): an `unreachable` whose `also` lists M turns a
failing check into a wait said at once with `on` `unreachable:<x>`, `bound` and `clock-from`, and ends `cleared`
on its clearing; one whose `also` does not list M leaves the failure the delivery's; a hub silent and a hub whose
own network is open, each listing M, each end in a wait; an `unreachable` cleared as the hub's `network` rises
keeps the wait with `on` `network:<x>` and the same `clock-from`; a condition raised 30 s after the failing check
turns it into a wait within the settle, and none within 90 s is held as the delivery's failure; a wait judged
again failing with no cause open ends `own-failure`; the keeper silent waits `on`
`conditions-unread` and is judged when it answers; the keeper's own module failing is its failure with no read;
the module turning healthy with the cause open ends `healthy`; a newer want ends `replaced` and its ask; a restart
of the assigner judges each wait again; past the bound the ask, Go on ending `go-on`, Count as failed ending
`failed`, an unanswered ask raised again; a `rewritten` naming a moved module fails at once; M's own `network`
moves no want to M; `assigner_release` with each `how`, each in the hand-act log; the finding
`delivery-waits-outside` written as a warning that needs nothing and cleared at the end, and no `delivery-waits`
for it. Whole (watchman): a machine silent, and one whose own network is open, while two machines fail toward it,
each list both in `also`. Unit (grants part): the assigner granted `conditions` and the three events, and no read
of the watchman's `findings`.
## The writer's own refusals, in every bundle
- §707 **`finding-refused` is the one kind every bundle's writer owns, within its own bundle's key part.** (§383,
§384, §437, §461, §580; design 73 knowledge §1, §1a) §580 gives each finding kind to one bundle. One kind is the
writer's own: mesh-sdk's finding writer refuses a key it cannot compose (a scope outside the seat
`mesh-conditions`' set, an empty id, a kind or `ref` holding a dot, as design 73 knowledge §1 has it) and writes in
its stead, in the observer's own state `findings`, the finding of kind `finding-refused` (design 73 knowledge §1a),
keyed `module.<observer>.<bundle>.<asked-key-escaped>.finding-refused`, `<bundle>` the bundle's artifact `name` in
the manifest's `build`, and the asked key escaped as a key's name is (§73). A module with one bundle has that
bundle's name there too, so the form has no exception. Every bundle's writer owns that kind within its own key
part only: it writes and clears only `finding-refused` keys of its own bundle. `findings-stated` (§384, §437)
gains `bundle`, of the same form, beside `kinds`; a statement of a bundle, with `kinds` or without, covers the
`finding-refused` keys of that bundle and never another's, and the keeper clears from it only the
`finding-refused` conditions of that bundle it does not name. Every bundle of a module writes the one state
`findings` under the module's one account, so the bus grant cannot tell bundles apart: mesh-sdk's writer, made
with its bundle's name, is what keeps each to its own key part. Why: a refusal is that bundle's own fault, so no
other bundle's statement may clear it (the review of design 73, HE-W2R2-2; review round 1 of batch 8). How it is
checked: unit (writer, both languages), a refused key written as `finding-refused` under its bundle's key part,
escaped; a statement of bundle A leaves bundle B's `finding-refused` key; a writer refusing to write or clear
another bundle's `finding-refused`; unit (contracts), `findings-stated`'s `bundle` read; whole (keeper), two
bundles' statements on one node each keep the other's `finding-refused` condition open.
@@ -0,0 +1,361 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0664"
- "ADR 0661"
- "ADR 0659"
- "ADR 0656"
- "ADR 0644"
- "ADR 0619"
- "ADR 0596"
- "ADR 0595"
- "ADR 0593"
- "ADR 0573"
- "ADR 0567"
- "ADR 0564"
- "ADR 0548"
- "ADR 0525"
- "ADR 0505"
- "ADR 0448"
- "ADR 0363"
- "ADR 0283"
- "ADR 0240"
---
# 47, milestone G: pipelines per module: any size, the pipeline makes the check, who may say what, health, recovery, tools, kept data, how G itself is delivered, the choices under ADR 0666 and ADR 0667
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
### Any size
- §57 **A value over the limit is an object.** mesh-sdk's own small object store package (ADR 0595 §9) puts a value over
the bus's per-value limit in the object store beside its state and keeps the object's name in the key; a verb's reply or an event over the
per-message limit carries the object's name (ADR 0666 §16). A replace writes the new object and sets the key by
compare-and-set; the object it replaced stays, as one whose key was deleted does, listed as `key gone` since no key
names it, and only the drop tool removes either, on a person's word: the mesh removes kept data only through `drop`
and `recreate`, each on a person's word (ADR 0595 §4; ADR 0666 §16).
- §58 **One object store per state, granted to its owner.** (§513, §549) Each state asked by the template `state` gets its object store
beside it, `<state>-objects`, a template form of ADR 0595 that the bus module provides and the grants part grants to
the state's owner alone (ADR 0666 §16); a foundation part's large values go in an old `objects` section of its own.
Its size is the template's (ADR 0595 §3). An object store holds no space until a value is written to it, so the many
it adds reserve nothing; the few values over the limit (a delivery record, a catalog graph revision list, a test's
output) are estimated, as a guess, at under 1 GiB across the mesh.
- §59 **An object sent is readable by the modules it is for.** (§533) A reply's or an event's object is written to the sender's
**sent store**, `<module>_sent-objects`: the bus module plans it, with no manifest field, for every module of the new
form that serves a verb or emits an event, and a foundation part declares it as an old `objects` section
`sent-objects` (ADR 0619 §2). Its cap is the objects template's (ADR 0595 §3), 256 MiB (§477), and it keeps an object 7 days, after
which the bus expires it. That age is transport, not kept data: an object sent exists only to carry one reply or one
event, read within its call's bound or its consumer's lag, so its expiry is not a removal of kept data that ADR 0595
§4 holds to a person's word (the design's choice; 7 days is a guess at the longest a consumer lags, read from the
fill tools once G runs). A full sent store is the bus module's `full` and its condition (ADR 0595 §6), and the sender
then refuses the large reply or event loudly, naming the store. The grants part derives from the catalog graph (§442), never from a manifest, a standing grant to read them: the
callers its seat definition names for the verbs it serves, and the consumers whose `consumes` declare its events;
no other module may read them (ADR 0666 §16). The grant follows the catalog graph, so it is written at each `catalog-revision` that changes it,
never per object. A consumer's reader of
the form is delivered before its producer writes it, an edge of kind 4.
- §60 **Its tools come with the package.** The package serves each module that imports it `<module>_state_objects` (the
objects of each state, with their size, the key that names each or `key gone`, and the fill against its cap),
`<module>_state_objects_drop` (drop the objects whose key is gone, on a person's word as ADR 0595 §4 removes kept
data), `<module>_sent_objects` (each sent object, its size, when it was sent and to which verb or event, its age
against the store's and the fill against its cap) and `<module>_sent_objects_drop` (drop sent objects older than an
age a person names); each drop names who asked and is recorded in the hand-act log.
- §61 **Its cost.** Only the modules that import the package are built with it, so no other module is rebuilt for it: a
package published before its users build (ADR 0573 §2, ADR 0667 §3).
- §62 **How it is checked.** Whole (mesh-sdk, against the real bus): values of one, ten and a hundred times the per-value
limit stored, sent as a reply and as an event, and read back whole by a caller and a consumer the manifests declare,
and refused to a module that declares neither; a replaced large value leaves its old object listed `key gone` beside
the new one, and a key deleted leaves its object listed `key gone`, each removed by the drop tool and by nothing else;
a sent object is readable within its 7
days and expired after; each drop without who asked is refused. Tests 69 and 90.
### The pipeline makes the check, and the checker goes
- §63 **Every step runs on parts built from the change.** Every pipeline step that runs a change's code runs on parts built
from the change's own heads, in the build seat's sandbox, with no network and no bus account (ADR 0666 §17, ADR 0448
§2, §4): the composition step composes each machine's declaration with the composer built from the heads and refuses
a change in which two modules own one path; the build seats' steps run lint and tests. A step whose serving part the
change alters runs from the change's build of it, built within 20 minutes or ending `waiting for a build node` or
`did not build`, the first tier of *The check on the change's own acting parts* made the runner's.
- §64 **The run on request.** The runner's verb `run` takes one or more members, each a module at a named commit, runs them
through their pipelines and delivers nothing, replacing the checker's try (ADR 0666 §18). Its callers are the
sessions, as the agents' bus identities the tool runner proves, and the operator, and nothing else, since a run of a
group builds every member and its dependents on the build capacity (§373). The runner says `run-started`, the indexer builds
the run's candidate from it, and the candidate's composition step writes no want (ADR 0656 §9); the candidate is
dropped on that run's `pipeline-ended`. A step that could not run ends `could not run`, never as passed. A
composition step for a node that sleeps passes it with a warning (ADR 0593 §9).
- §65 **The runner is an acting part in the checker's place.** The acting rule's step `check` becomes the runner's step
`pipeline` (*What ADR 0656 leaves to this design*): its own change runs on its own new version, a failing new runner
holds while the old one runs pipelines (ADR 0656 §4), and the pipelines in flight when its holder is replaced are
taken up again from its kept state (ADR 0666 §19).
- §66 **What the checker did, and where it goes.**
| The checker's | From G's delivery |
|---|---|
| the merge check, one status `mesh/check` | the pipeline, its result in `pipeline-ended`, the runner's `show` and the delivery record; no status on the forge |
| the try | the run on request |
| the composition check | the composition step, the assigner's |
| the kept verdicts and the carry | the runner's `kept`, which starts empty, and its carry (ADR 0548 §5); the checker's verdicts go with its buckets |
| the checking program: the manifest validator at the heads, the cycle rule, the composer, the rule names | the indexer's validation before take-in; the indexer's cycle rule; the composition step; `widens` in the pipeline's end |
| the `run` ask, published by the checker's account | published by the runner's account and the indexer's (its validation), each with step keys under its own name |
| its buckets and bus account | removed with the gate's forge merge token after the first pipeline after G's delivery has delivered a change end to end, on the operator's one word, the buckets through `drop` after its snapshot, whose restore is tested first (ADR 0667 §12) |
- §67 **How it is checked.** Whole (runner, assigner): a run on request of `feature/1966-x` delivers nothing, and a call of
`run` from another account is refused; a composition step refuses two modules owning one path; a step whose part
cannot be heard ends `could not run`; a node asleep passes with a warning; a runner stopped mid-step takes its
pipelines up again. Live: once the checker is removed no module claims the seat `mesh-delivery-checker`. Tests 64,
84 and 88.
### Who may say what
- §68 **Callers per verb, in the seat's definition.** Each seat definition names, per verb, the callers allowed to call it,
in its field `callers` (holder roles, modules, the operator, the healers), which the grants part reads as its one
source of caller limits; the runner's verbs are the first (ADR 0666 §20). A verb whose definition names no `callers`
takes the default of its class (§391), so no verb is closed for lack of a list. It replaces the holder's
trusted config value `callers` of the seat `git` and the package seats (design 51), which no holder set for most
verbs. A format its running reader cannot read, so its reader is delivered first (ADR 0667 §4).
- §69 **A step's result from its holder only.** The runner takes a step's result only from the bus account of that step's
seat holder, naming the commit and the build-input fingerprint; a result from another account, or naming another
commit or fingerprint, is refused and said (ADR 0666 §21).
- §70 **A review gates, once it can.** (§482) A code review's result is taken through the verb `review` from the reviewer's bus
account, and in G refused from no one for authorship, recording the caller the bus proved (§482); a review is to gate where a policy says `required` once a record binds the accounts (ADR 0666 §22, which
changes ADR 0659 §2). Until a record binds each forge account to its bus identity no policy may say it (*A module's
registration*; issue 1966, note 157).
- §71 **A widening is said, never refused.** A change that alters any part serving a step (the runner, step detection, the
lint rule, a build seat's test runner, the composer), or widens an acting rule, a grant or a credential, says so in
its `pipeline-ended`, its field `widens` naming each. The running runner computes it, the serving parts from the seat list by fingerprint and the grants through the seat `mesh-grants`' verb `grants-for` (§441), from the sources of the change
against those of the watched branch, never the runner built from the change, so a change cannot hide its own
widening; nothing refuses it for that (ADR 0666 §23, the operator, 2026-11-10). The teller passes `widens` on to every
channel, as it does `recovery-merged`. This is what replaces ADR 0656 §8's naming in the Approve ask.
- §72 **How it is checked.** (§482) Whole (runner, grants part): a forged step result is refused and a review records its proven caller; a
caller not in a verb's `callers`, and any caller but the holder roles and the operator of a verb with no `callers`,
are refused by the bus; a change widening a grant, and one altering the Go build module's test runner, each name it in
`widens`, computed by the running runner, and are delivered. Tests 85 and 86.
### Health and going back
- §73 **The runner's events.** Each on `mesh.seat.mesh-pipeline-runner.event.<event>.<module>.<branch>.<commit or group>`,
a branch or group name escaped as a key's name is, declared in the seat's definition and published by the runner's
account alone (ADR 0666 §24, ADR 0363 §2):
| Event | Says |
|---|---|
| `pipeline-started`, `pipeline-ended` | a pipeline's start, and its end with every step's result, its test counts, the source revision and `widens` |
| `group-ended` | a group's one pipeline's end, on which alone its wants are written |
| `run-started` | a run on request's start, with its members at their commits |
| `step-started`, `step-ended` | each step's start and end: the step, its status in the policy, its outcome, its build-input fingerprint, its test counts, for a carried result what it was carried from, and for a rerun that it is one and the caller the runtime proved; a bypassed step too |
| `wait-held`, `wait-ended` | a step's wait: on a person (approval, code review), on a build node, on the revision view |
- §74 **What a session hears after its merge.** Between its `merge-pull` and the delivery, a session waits with `mesh_wait`
on the runner's `pipeline-started` and `pipeline-ended` of its module, branch and commit, and then on the status
view's `delivered` of the source revision; a failed head reaches it as `pipeline-failed` (*What holds a delivery*).
- §75 **Nothing stands still unseen.** (§496) The watchman times each step's owed progress from `wait-held` and `step-started`,
never by run length; a step or a watched branch's head standing still five minutes is its finding `pipeline-stands`,
raised by the condition keeper and cleared on the step's end, or for a head on its `pipeline-started` (ADR 0564 §2,
§4). A step waiting on a person is said as waiting on that person (ADR 0283), never as a stall; a group waiting for
its members, and a merge behind a pending revision, are governed by their own bounds (ADR 0666 §24). At its start the
watchman seeds its open waits from the runner's `stands`, as it seeds `take-in-waits` from `indexer_pending`.
- §76 **The runner's own health.** Its manifest declares its health check, run outside its process (ADR 0240; its tool
`runner_health`, §468): the runner
answers `show`, and its durable consumers of `step-ended`, `take-in.taken`, `package-published`, `toolchain-agreed`
and `decided` are
each less than five minutes behind; a failed check names which.
- §77 **The runner waits for its view.** With no revision view it says so in `<module>_first_views` and raises `view-waits`
after five minutes, as every part does (ADR 0656 §6).
- §78 **Going back is one act.** A failing new version holds while the old keeps running (ADR 0656 §4); a return to an
earlier build is one value in the placement, the module's `build` in its placement key, set by a placement proposal
(ADR 0666 §24).
- §79 **How it is checked.** Whole (runner, watchman): a step whose worker dies raises `pipeline-stands` within five minutes;
a head with no pipeline raises it and its `pipeline-started` clears it; a step waiting on the operator raises nothing;
a runner restarted mid-step takes its pipelines up again; its health check fails naming a consumer behind. Whole
(assigner): a placement's `build` delivers the earlier build. Tests 76, 77 and 88.
### A recovery fix is an ordinary merge
- §80 **No recovery merge verb.** A fix that recovers the mesh is merged like any change on a watched branch, by a
session's `merge-pull`, and runs its pipeline; no trusted recovery merge verb is built (ADR 0666 §26). The shape this
design sketched for one (a verb `merge` of the seat `mesh-delivery` with `recovery-callers`) is dropped.
- §81 **Named a recovery, said everywhere.** Its author names it a recovery by the seat `mesh-delivery`'s verb `recovery`
(the commit and why), never by a pull request's text: the gate marks the delivery record `recovery` with the caller
the runtime proves, and says `recovery-merged`, which the teller passes on to every channel (ADR 0666 §26). A widening
it carries is said in its `pipeline-ended` like any other (*Who may say what*).
- §82 **How it is checked.** Whole (gate): a merge named a recovery is marked in its record and `recovery-merged` is said;
a recovery named by a description is not. Test 92.
### The tools
§83 Every new state has a tool that reads it and every new act a tool that starts it by hand (ADR 0666 §25), against the
four lines a module's tools answer; every read tool of a store also answers its fill against its cap:
| Part (seat) | Read everything it holds | Do by hand what it does itself | Recover without help |
|---|---|---|---|
| the runner (`mesh-pipeline-runner`) | `show` (one pipeline whole, its steps, step log and outputs), `list` (the pipelines of a module, a branch or a commit), `stands` (what stands still, since when), `why-carried` (where a step's result was carried from), `kept` (the kept results by digest) | `start` (a delivering pipeline for a watched branch's head), `run` (the run on request), `rerun` (one step again) | `accept` (a failed `optional` step, with why), `cancel` (a pipeline, dropping its asks), `ask-again` (a declined `approval`) |
| the indexer (`mesh-catalog`) | `registrations` (with each one's history); `indexer_edges` (an artifact's edges per watched branch), `indexer_order` (the order of a change, its cycles named), `indexer_catalog_difference` (two revisions); `indexer_pending` (a group with its missing members) | `register`, `policy`, `watch`, `unwatch` (each on the operator's Approve) | `indexer_seed_again` (the first registrations' ask raised again), `indexer_take_in_pending` (a group taken in as gathered), `indexer_drop_pending` (dropped) |
| the gate (`mesh-delivery`) | `source` with a branch (`gate_source`: every branch's source revision) | — | `gate_source_again` (say `source-moved` again for a branch) |
| the forge module (`git`) | `git_failed_steps` (the failed `required` steps it holds, by module and fingerprint, with the run that failed each) | — | `git_failed_steps_drop` (drop one, with why) |
| the grants part (`mesh-grants`) | `grants_callers` (the callers each verb admits, read from the seat definitions) | — | `grants_why_refused` (for an account and a verb, the grant it lacks and the definition's line that leaves it out) |
| the build seats | `steps` (the steps detected for an artifact) | `steps` with `again` (detect again) | `drop` (ADR 0664 §4) |
| the builder (`mesh-builds`) | `builder_reserved` (each module's reserved version, §369); the released versions are the catalog's `versions` | `builder_publish_again` (publish the missing artifacts under the reserved version) | — |
| the assigner (`mesh-wants`) | `assigner_why_not` (why a module is not on a machine yet: the pipeline it waits for, its hold, the placement, the node's last `running`) | — | `assigner_release` |
| the watchman | `watchman_views` (how far behind each part's view is, from each part's `view-waits` and first-view tools) | — | each part's `<module>_first_view_again` |
| the operator-channel | `asks` (with each asker's asks held back) | — | `ask-again` (offer a held-back ask now) |
| every module that imports mesh-sdk's small object store package | `<module>_state_objects`, `<module>_sent_objects` | — | `<module>_state_objects_drop`, `<module>_sent_objects_drop` |
§84 Every recover tool writes its act, who and why in the hand-act log of the seat `mesh-hand-acts`. How it is checked:
each tool answers its whole-module test's case, every store's read answers its fill, and `mesh_describe` reads each
(test 93).
### The runner's kept data, and the build capacity
§85 Each kept form names its section or template, its retention and its cap; a state template fixes its own size (ADR 0595
§3), up to 64 MiB, and an old `state` section names its cap in `max-mib` (§469); the step log is an old `logs` section (§470). The numbers are estimates, to be read from the fill tools once G runs: an estimated 20 merges a day
(as *The delivery record*), each starting a pipeline for about 3 modules of its repository whose fingerprint it changes,
and an estimated 2 mesh-sdk publishes a day, each rebuilding about 120 artifacts and starting about 120 pipelines: about
300 pipelines a day, of about 6 steps each.
| Part | Kept data | Section or template | Retention | Cap and growth |
|---|---|---|---|---|
| the runner | `pipelines`: one key per pipeline, keyed by watched branch, source revision and module, its steps, statuses, outcomes, fingerprints, test counts and end count | old `state` section | a pipeline that ended passed on a watched branch, 90 days after its end; any other (failed, superseded, cancelled, a run on request's), 7 days after its end; always while it is its branch's newest for its module; deleted by the runner (§330) | 256 MiB; about 300 pipelines a day of about 4 KiB is at most about 105 MiB at 90 days, under half the cap |
| the runner | `step-log`: one entry per step's start and end, its output, its pipeline and the `kept` digests it wrote or carried, by name | old `logs` section (§470) | by its age, 100 days, ten days past the longest retention it ages (§330) | 128 MiB; about 3,600 entries a day of about 0.2 KiB is about 70 MiB at 100 days |
| the runner | `step-outputs`: a lint or test step's output | old `objects` section | 7 days | 1 GiB; about 1,200 steps run a day (the rest carried) of about 50 KiB is about 420 MiB at 7 days |
| the runner | `kept`: a package's passing result per level, by digest, with `last-read`, written by each carry that reads it | old `state` section | 30 days after `last-read` | 128 MiB; a publish changes the digest of about 1,000 importing packages at two levels, so about 4,000 entries a day of about 0.3 KiB, about 36 MiB at 30 days |
| the indexer | `versions`: one key per module and version, its artifacts' digests, the source revision and its edge entries (§349, §374) | old `state` section | a module's newest 100 versions, never its highest of a `MAJOR.MINOR`; the indexer deletes the older at each release | 64 MiB; about 120 modules of 100 versions at about 3 KiB is about 35 MiB; at the cap it records no release and says `full`, as *A store filling is said before it is full* |
| the indexer | `build-edges`: the newest build's edge entries per module and watched branch other than the first | old `state` section | replaced by the next build; deleted when the branch is unwatched | under 1 MiB |
| the builder | `reserved`: one key per module and `MAJOR.MINOR` (§369) | old `state` section | until its `version-released`, a release of another `MAJOR.MINOR` of the module, or `builder_reserved_drop` | a few KiB; at the cap the builder reserves nothing and says why |
| the assigner | `written`: the last source revision and end count per module and watched branch (§352) | old `state` section | until the branch is unwatched | a few KiB |
| the indexer and the assigner | `inputs`: a `run` ask's input by write time and sha256 (§354) | old `state` section | until the ask's `step-ended`, at most a day | 64 MiB; an estimated 300 asks a day of under 100 KiB each is under 30 MiB; at the cap the asker refuses the ask loudly, naming the store |
| the runner and the assigner | `findings` (§355), read by their own read tools alone; the keeper hears the findings as events (§383) | old `state` section | an entry deleted when its finding clears | a few KiB |
| every observer (§387) | the stream `<module>-findings`: `finding-written`, `finding-cleared`, `findings-stated` (§383) | old `streams` section for a foundation part, the module's `streams` otherwise | 7 days, its oldest dropped when full; the next statement covers a lost one (§384) | 16 MiB; an estimate of a few hundred findings a day of about 1 KiB is under 2 MiB a week |
| the condition keeper | `findings-views`: one key per observer and node, its last stated set, pending keys and open asks (§388) | the form of its `conditions` | deleted when the module leaves the catalog or the node (§365) | under 1 MiB; about 30 observers on four machines |
| the watchman, the keeper | `machine-away`, `machine-back` (§389); `findings-asked` (§390) | the seats' definitions | 90 days; 7 days | 16 MiB each |
| the runner | `aged`: three step-log sequences (§330) | old `state` section | never deleted | bytes |
| the teller | `widens-said` (§335) | the template `state` | until the branch is unwatched | a few KiB; the template's |
| each build module | `detected-steps` (§326, §414) | the template `state` | one key per artifact, dropped by `steps` with `drop` | under 1 MiB; the template's |
| the indexer | `registrations` and its stream `registration-history` | old `state` and `streams` sections | the life of the mesh, never derived again | 64 MiB (*A module's registration*) |
| the indexer | catalog graph revisions, per watched branch | objects in the old form (ADR 0619 §2) | while a record pins it or it is its branch's current | 512 MiB, as *Two graphs, one source of truth*; a second watched branch on a few modules adds a few MiB |
| the gate | `source`: one key per watched branch | its old `state` section, as today | 64 revisions of each key; a branch's key deleted when `registration-changed` says the last registration watching it unwatched it (`unwatch`) | a few KiB |
| the forge module | `failed-steps`: failed `required` steps by module and fingerprint, with the commits each was seen at, and the `seen` keys of §487 | state | an entry deleted once none of its commits is a watched branch's head or the head an open pull request names, judged on each `branch-moved` and each pull request's close | the template's 64 MiB; an estimated 50 failed steps a day of about 0.5 KiB, most gone within days, is under 1 MiB; the `seen` keys under 3 MiB (§487) |
| every module that imports the object store package | `<state>-objects` beside each state | objects | an object no key names, its key deleted or its value replaced, until `<module>_state_objects_drop` on a person's word (ADR 0595 §4, ADR 0666 §16) | the template's; nothing reserved until written |
| every sender of the new form, and a foundation part by its old `objects` section | `<module>_sent-objects` (`sent-objects`) | objects, planned by the bus module with no manifest field | 7 days, transport (*Any size*) | the objects template's cap; full is the bus module's `full` |
| the grants part, for each seat event a definition keeps | `SEAT_<seat>_<event>`, among them `SEAT_mesh-pipeline-runner_*`, `SEAT_<build seat>_step-ended`, `SEAT_git_branch-moved`, `SEAT_mesh-builds_package-published` and `SEAT_mesh-catalog_registration-changed` | a stream the grants part makes with its durable consumers | the seat definition's age (`keep`), 90 days unless the definition names less | the seat definition's cap (`keep`) |
§86 **The build capacity.** A pipeline runs a module's steps once per commit on a watched branch, carrying what did not
change, where the check ran before the merge and carried after each trunk move; a group runs one pipeline per watched
branch; a second watched branch runs the steps of the modules it carries once more. A mesh-sdk publish asks about 120
lint steps and the test steps of the importing packages, about 240 more, the rest carried; a newer publish of the same
package supersedes the older one's rebuild pipelines, so two publishes close together run the rebuilds once. The first
pipelines after G's delivery run every module's lint and tests once, since `kept` starts empty (ADR 0666 §21): about
120 modules, one round of the build capacity, taken in the `check` stream's order; from then they run only what
changed. Before G's delivery the operator is told the first round's estimated length, read from the checker's last
full check of every module (its verdicts' run times). A step queued on `check` behind the build capacity is said
`wait-held` on a build node and is owed no progress while queued; the watchman raises one finding per build seat,
`build-capacity-waits`, when that seat's oldest queued step has waited 60 minutes, cleared when it starts. A pipeline
whose commit is named a recovery has its steps asked on the build seats' stream `delivery`, ahead of the first round. The steps go on the build seats' stream `check`,
after `delivery` and before `recheck`, within each node's build capacity (ADR 0525 §4, §5), and a run on request on the
same stream. How it is checked: live, the build nodes' time per day in the week after G, read from the build modules'
step keys and the builder's build log, against the checker's last week (test 99).
### How milestone G itself is checked and delivered
- §87 **Under the clauses as they stand.** Milestone G is checked and delivered under ADR 0656 §5 and the clauses it changed
as they hold before its delivery: the acting parts (the indexer, the build modules, the assigner, the grants part,
the node-engine, and the checker, which makes G's own check) act from the milestone's heads, each first in its step,
in the order of their ordering edges; the trunk-only want, the checker and the Approve per merge as ADR 0661 leaves it
govern this delivery, no exemption is asked and no delivery goes ahead of it (ADR 0667 §2, ADR 0644 §1).
- §88 **Its other parts as any module.** mesh-sdk's small object store package is a package published before the modules
that use it build; the manifest validator moves with the indexer; the runner is placed like any module and acts only
from the next change on; mesh-delivery's `contracts` and `sdk` move under its `modules/` folder (ADR 0667 §3).
- §89 **Readers first** (ADR 0667 §4): an edge of kind 4 each, which the delivery plan derives: the indexer's manifest
validator for the seat definitions' new fields (`callers`, `acts`, `subject`, `keep`) before any definition using
them; the manifest validator for the manifests' new words (`version`, §368; a `*` emitter in `consumes`, §386; the
finding stream an observer declares, §383, §387) and the grants part that makes a `*` entry's durable consumers
(§386), before the keeper's or any observer's manifest using them; on each node the node-engine that reads `form`
and `catch-up` from the catalog graph before any manifest writing them. The manifest validator the running checker asks for G's own check is the indexer's, so the reading
of these words goes in the reader release of §340, whether or not that validator already takes one of them.
- §90 **The stream maker first** (ADR 0667 §5): the grants part, which makes each kept seat stream and its durable
consumers, before the parts that hear a kept stream.
- §91 **The bus module first** (ADR 0667 §6): the bus module, with `<state>-objects`, the sent store and the grant of each
state's object store to its owner alone, before the modules requiring them, the grants part writing that grant from
the milestone's heads.
- §92 **A bus restart is said first** (ADR 0667 §7): a restart of the bus its new version needs is said to the operator
before it starts.
- §93 **A gap is built, not worked around** (ADR 0667 §8): an order the delivery plan cannot derive from the edges is
built in the delivery system within G, never a step done by hand.
- §94 **A node-engine of the version before applies what it can read** (ADR 0667 §9): it applies every want whose
manifests use no field it does not read, and a want that uses one waits, by the reader edge above, for the new
node-engine on that node.
- §95 **The new assigner and the new merge switch on the first registrations** (§507) (ADR 0667 §10). The new assigner, which
writes a want only from a passed pipeline, is delivered before the forge module's new `merge-pull`, which asks no
Approve, and before the runner: an edge of kind 4 from `mesh-delivery-assigner` to each, declared in their manifests'
`needs` since nothing else says it (the design's choice: without it a merge made with no Approve could reach the old
assigner, which writes wants from the trunk with no pipeline). Both switch on one fact, the first registrations
written, said as `registration-changed`: until it the new assigner keeps the trunk-only want and the new
`merge-pull` refuses every merge, naming the missing registrations.
- §96 **Until then, the operator merges a fix at the forge** (ADR 0667 §11). Until that fact an ordinary change waits for
it, and a fix, an indexer fix included, is merged by the operator's own hand at the forge (ADR 0661 §3) and
delivered under the trunk-only want still in force; `indexer_seed_again` asks the first registrations again. Before
the milestone's delivery every merge goes as ADR 0661 has it.
- §97 **What is removed, and when** (ADR 0667 §12). The checker's buckets and bus account and the gate's forge merge token
are removed only after the first pipeline after G's delivery has delivered a change end to end (test 63), the runner
then making every check in the checker's place (ADR 0666 §19), and only on the operator's word, asked once for all
three. The checker then leaves the manifest validator's list of foundation parts (ADR 0666 §4) and is no longer
placed, so no running module declares its buckets and they
are orphans `orphans` lists; they go through ADR 0595's `drop` on that word, after the stream snapshot `drop` takes
(ADR 0595 §4, §5, §10), kept 90 days as the delivery records are; a restore from that snapshot is tested before the
word is asked. The runner is on that list from G's delivery.
- §98 **Told to the operator before it starts**: G's own merges are asked as ADR 0661 has them (ADR 0667 §2, §11); the one
ask of the seed registrations; the bus's restart (ADR 0667 §7); and later the one word for the checker's removal
(ADR 0667 §12).
- §99 **How it is checked.** Tests 94 to 97, and 100 for the assigner's edge.
### Choices this design makes under ADR 0666 and ADR 0667
| Choice | Why |
|---|---|
| §100 The registrations in the indexer's old `state` section with their history on an old stream, capped at 64 MiB, kept for the life of the mesh | the indexer is a foundation part (ADR 0619 §1 to §3, ADR 0666 §4); a registration keeps every version with who and on whose Approve, and nothing derives it again |
| §101 The runner a foundation part, its kept data in old sections, on the manifest validator's list | the bus module's own delivery waits on its pipeline (ADR 0666 §4) |
| §102 The want's compare-and-set keyed per module and watched branch | source revisions are kept per watched branch, so two branches' revisions have no order; comparing across them would let `main` refuse `beta`'s want (ADR 0666 §9) |
| §103 A second start of a pipeline for the same source revision and module, on `take-in.taken` and `package-published`, writes nothing, by the pipeline key's compare-and-set | the runner starts on both events (ADR 0666 §13, §15); the key it already keeps judges a start in one place, so neither producer knows of the other |
| §104 A refused new indexer revision holds later merges on its branch until a revert lands | the validation runs after the merge and before take-in (ADR 0666 §6), so the refused revision has landed; taking later merges in past it would deliver them on a graph its own validator refused |
| §105 Each check at its row's level, every requirement R1 to R18 keeping its named test | the record's rows name the levels; the test budget and one check per release bound what runs |
| §106 The approval asked as the operator-channel's accepted kind `ask` on its work queue | the seat has no verb `ask`: an ask is its accepted kind, by the asker's own name (design 46 §10) |
| §107 The gate no foundation part, its `source` in the old section it already has | the bus module's delivery waits on no act of the gate's; only the key's layout changes |
| §108 The verbs `register`, `policy`, `watch`, `unwatch`, `registrations` and the event `registration-changed` on `mesh-catalog` | one verb per trusted act, each its own ask naming what moves; readers follow the event (ADR 0596) |
| §109 The seed of the first registrations proposed as one ask, with the statuses the check judged until then and approval `bypass`, raised again by `indexer_seed_again` | registering is a trusted act (ADR 0666 §3), and one ask for the whole seed is how placement was adopted (ADR 0567); a declined or expired seed must not leave the pipelines stopped with no way on |
| §110 Code review refused as `required` until forge accounts are bound to bus identities | a review is refused from its author (ADR 0666 §22), which cannot be told until then |
| §111 The seat `git`'s read verb `branch` and event `branch-moved`, kept on its seat stream | registering reads a branch's protection through the seat `git` (ADR 0666 §3); a branch moves by a merge or an admitted push; a stream keeps a move said while the bridge is down |
| §112 The watchman's finding `pipeline-stands` for a step or a head still five minutes, cleared by the step's end or the head's `pipeline-started` | the record's five minutes (ADR 0666 §24), one finding for both, timed from the runner's events |
| §113 The runner's finding `pipeline-failed`, the author's for two hours, then the operator's | ADR 0666 §24 with ADR 0505 §5, §6 |
| §114 A module's watched branch and its `build` in the placement key, set by a proposal | a placement names the branch and a return is one value in it (ADR 0666 §6, §24); every placement change goes by a proposal (ADR 0567) |
| §115 The gate's `source.<branch>`, its verb `source` and event `source-moved` | one source revision per watched branch (ADR 0666 §6), read through the seat (G's prepare, the indexer's watch of the gate's `source`) |
| §116 The build seats' verb `steps` and accepted kind `step` on `check`; the assigner's kind `step` on `mesh-wants`; `step-ended` with the node in its subject | steps are asked on the serving seat's work queue (ADR 0666 §7); the `check` stream already orders after `delivery`; a node's account says only its own node's ends (ADR 0363 §2) |
| §117 The outcomes, with `accepted`, `superseded` and `cancelled`; an approval expired or refused ends `declined`, replaced `superseded` | every end a step can have is said as itself (ADR 0666 §10) |
| §118 `approval` asked last, its `about` the module and the branch | the operator approves a commit the mesh already judged, the point ADR 0661 §3 kept in one place; one ask per module and branch, a newer commit's replacing it |
| §119 A superseded pipeline's asks dropped by `drop`, and a pipeline superseded only when its module's fingerprint changed | a cancelled pipeline must leave no ask on a stream (ADR 0664 §4; G's prepare); a module the commit did not change has nothing newer to run (ADR 0666 §9) |
| §120 A group's wants only on `group-ended` | a group is delivered whole or not at all (ADR 0666 §9; research 086, R5) |
| §121 The six kinds derived from existing words, `needs` per artifact only where nothing says it | one form of each fact (ADR 0656 §6); the catalog entry's `needs` already exists from G |
| §122 The cycle rule in the indexer's graph | the graph is the indexer's, and the checking program goes with the checker |
| §123 The builder's seat `mesh-builds` and its event `package-published` | the record has the builder say the publication as an event of its seat (ADR 0666 §15); its waits move only when a later change gives the seat its verbs |
| §124 Each state's object store `<state>-objects` through mesh-sdk's small package, sent objects readable by a standing grant derived from the catalog graph, and the package's two tools | one object store per state, granted to its owner; the sender's declared callers and consumers read what it sends (ADR 0666 §16); only its users import it, so nothing else is rebuilt |
| §125 The `run` ask published by the runner's account and the indexer's; the verb `run` called by the sessions and the operator only | the steps and the validation run code from the change, in the sandbox only, and each asker reads only its own step keys (ADR 0363 §2); a run publishes tagged packages, so nothing else may ask one |
| §126 The checker's step `check` becomes `pipeline`; `kept` starts empty | the runner is an acting part in the checker's place (ADR 0666 §19); a step's result is taken only from its seat holder (ADR 0666 §21), so no verdict of the checker's is taken in, at the cost of one round of every module's tests |
| §127 The field `callers` in a seat definition replaces the holders' trusted `callers`, a missing field taking the default of the verb's `class` (§391) | the record makes the definition the grants part's one source (ADR 0666 §20); a field left out must open nothing |
| §128 `widens` in `pipeline-ended`, computed by the running runner over every part serving a step, passed on by the teller | a widening is said and nothing refuses it (ADR 0666 §23, §26); a change must not compute its own |
| §129 `merge-pull` judged from the forge module's own `failed-steps`, heard from `step-ended`, any run at the head's fingerprint counting unless a later rerun of that step passed, the protection read again on every `branch-moved` | the record has it judge from the events it consumes, never by a call, so a head merges while the runner is down (ADR 0666 §11, ADR 0596) |
| §130 The new assigner before the new `merge-pull` and the runner, by `needs`, both switching on the first `registration-changed` | a merge with no Approve must never reach an assigner that writes wants without a pipeline (ADR 0667 §10) |
| §131 The forge bridge's finding `branch-unprotected` | a move of a branch whose protection was removed starts no delivering pipeline and is said (ADR 0666 §6) |
| §132 The verb `recovery` of `mesh-delivery` | a recovery is named by its author as a fact the bus keeps with who said it, never read from text (ADR 0666 §26, research 086 R10) |
| §133 No forge status for a pipeline | the step results live on the bus (research 086 R10), shown by the runner and the delivery record; a status would be a second copy outside it |
| §134 The runner's `pipelines` kept 90 days after a passed end on a watched branch, any other 7 days, its step log 100 days by age, its outputs 7, keyed by source revision | a retention the runner judges from its own facts, never from the gate's records (ADR 0596); the delivery record keeps the step events itself (§34), so nothing it traces needs the runner's key; one nothing delivered has no trace to serve after a week; about 300 pipelines a day must fit its caps, so they are 256 MiB and 128 MiB |
| §135 The forge module's `failed-steps` kept until no head it was seen at is a watched branch's head or an open pull request's | an entry judges only a head that can still be merged; once neither names it, nothing can be refused by it |
| §136 The gate's `source.<branch>` deleted when the branch's last watch goes | a branch no registration watches makes no revision, so its key would name nothing |
| §137 The sent store kept 7 days by age, with no person's word | an object sent carries one reply or event and is transport, not kept data (ADR 0595 §4); its readers fetch it within a call's bound or a consumer's lag |
| §138 A replaced object listed `key gone`, as a deleted key's is | no key names it, and one listing lets the drop tool take both on a person's word, as ADR 0595 §4 and ADR 0666 §16 have it |
| §139 The checker's removal after the first end-to-end pipeline, on one word, its buckets through `drop`, the snapshot kept 90 days | ADR 0667 §12 asks one word for all three; 90 days is the delivery records' retention, the longest a trace reads back |
| §140 The pipeline steps on the `check` stream after `delivery` | a delivery's build is never held by a pipeline, as a check never held one (ADR 0525 §4) |
@@ -0,0 +1,690 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0667"
---
# 47, milestone G — the lead's answers to develop's questions
The operator, 2026-11-11 about 21:30 CET: a develop question is tested against [the principles](../../../00-META/principles.md)
and answered by the lead in one line, without a design batch or a review round for a detail. Each answer here is one line,
names the principle that settles it, and is cited as "design 47 G A-n".
- **A-1** (§686, §640, §687, §689; principle 7, principle 3) `views.FindingBody`'s accessors follow Go's idiom, `Kind()` and
`SetKind(v)` per field; the subject is one pair, `Subject()` and `SetSubject(...)`, with its fields as plain values in the
contract struct's order; the writer is given the generated contract's constructors for `finding-cleared`, `findings-stated`
and `findings-asked` when it is made, never plain maps, so a contract mismatch fails at compile time; `Observed.Findings`
and `Pending` hold `FindingBody` values.
- **A-2** (§655; principle 7, the rule that code shared between modules lives in mesh-sdk) `nats-tools/internal/busarchive`
moves whole with `bussnap` into mesh-sdk as its own package `go/busarchive`, its tests with it; every other importer
(verify, the nightly dump) imports it from there, and no copy stays in mesh-catalog.
- **A-3** (§686, §640, §687; principle 7; replaces A-1) The generated contract types stay as they are, plain structs whose
fields every importer already reads; nothing is generated for the writer and no field is renamed. mesh-sdk's writer is
generic over the contract's body types (`Writer[F, C, S, A any]`, one type parameter per body) and works on their wire
form: it marshals a body, reads and sets `kind`, `scope`, `id`, `subject`, `at` and `since` by their contract names,
and runs `views.CheckFinding` on that form. A body whose wire form lacks a field the contract names fails loudly at its
first write (principle 3) and in the writer's unit test, which runs against the real generated types of
`mesh-conditions`.
- **A-4** (§556; principle 7, and who may do what is no question) The grants part grants a reader of a stream read whole
every subject an ordered consumer of that stream uses: its creation, its next message, its info and the deletion of
its own consumer.
- **A-5** (§614, D-5; principle 7) The branch follower of §614 (registrations, the gate's `source`, adopt and drop of
views, entries by fingerprint kept asked, answered or not found) is built once, in `meshcatalogclient`, and every
part named in §614 uses it; none builds its own.
- **A-6** (§693; principle 7) The grants part keeps reading the assigner's `wants` view and consumes no `wanted`: a want
the assigner removes leaves that view, and the grants part composes no account for it. The `removed` field of §693 is
added to the seat `mesh-wants` for its other readers.
- **A-7** (GR10, §276, §266, §269; principles 1 and 7) The grants part holds the entries of a revision whose accounts its
new version has not yet composed; it keeps the hold in its own state on the bus; `clock-from` is the start of its new
version's first pass, which it knows itself, so it hears no `build.ended`; the bound is §266's granting bound of 10
minutes, a constant of the part.
- **A-8** (§613; principle 7) The template state takes the local name; the gate opens each old section once at its start
by the section's bus name, its own data, and copies a key only when the template lacks that key. No clock or
sequence is compared. Once every key is copied the old section is left to its retirement (§613).
- **A-9** (CT15, §476, §493, §530; principle 8) The `bodies` types are named `TakeInTaken`, `TakeInPending`,
`TakeInMerge`, `TakeInMember` and `TakeInEntry`, as built in mesh-delivery f38e45fd..1c09de8d.
- **A-10** (§276, §498; principles 1 and 7, and who may do what is no question) A holder says a seat event through one
mesh-sdk call, `Conn.EmitSeat(seat, event, body)`, publishing on `mesh.seat.<seat>.event.<event>`; the grants part
grants every holder publish of its held seats' event subjects.
- **A-11** (GR10; principle 7) A wait's `version` is the holder's own running fingerprint, as the indexer names it.
- **A-12** (GR10, §276; principle 4) `grants_release` ends the granting wait: the held entries go on, the wait is said
ended with `how` `released`, and no `acting-part-late` is raised for it.
- **A-13** (§461, §439, §383; principle 7) mesh-sdk's writer forms its three own subjects (`finding-written`, `finding-cleared`, `findings-stated`) itself, in §383's form
`mesh.stream.<module>.event.<event>.<node>`, from its module and node, through one function
`views.FindingSubjects(module, node)`; a module passes no subjects. It hears `findings-asked` on the seat's subject `mesh.seat.mesh-conditions.event.findings-asked` (§387, §390, A-17), never on the part's own stream.
- **A-14** (§580; principles 7 and 8) A finding that told several findings of one kind on one id apart by a `ref` puts that
part into its id (`<id>.<ref>`, each escaped as §73 escapes a name); a kind stays a fixed word of its bundle's list. The
key's scope and id and the subject's scope and id are set equal, as before.
- **A-15** (§614; principle 3) In the branch follower an `entry` the holder refuses is `not-found`; a transport error leaves
it `asked` and is asked again; each part writes `entry-not-found` from the follower's changes. As built in mesh-delivery
ab616fa6.
- **A-16** (§461, §485, §503; principle 7, the rule that shared code lives in mesh-sdk) A stdio module's writer is made by
one mesh-sdk constructor, `views.NewStdioWriter`, generic as the writer is,, which fills `Publish` from the module's own event
publish and `Listen` from its subscription; no module writes that adapter. The tool runner's own finding writer
(mesh-tools' first views) hands in `Publish` and `Listen` from its bus connection, in its one place.
- **A-17** (§461, A-16; ADR 0613 §5, principle 7; corrected) `findings-asked` is always the seat `mesh-conditions`' event,
but a seat's name and contract are mesh-contracts' (ADR 0613 §5), so mesh-sdk names no seat: the writer takes the
subject it hears in one config field `Asked`, which each module fills from the generated `mesh-conditions` contract's
own constant (`SubjectFindingsAsked`), in Go and TypeScript alike.
- **A-18** (§587, §305; principle 7) What G retires leaves no trace: the gate's record type drops the `remove` operation
and `OpRemove` (§587: a removal is no operation), and its `table`, `recheck` and `close` go with their handlers and
tools (§305). `checks` goes too, as §431 retires it with the merge states; the check is the runner's now (G-2).
- **A-19** (§641, §556; principles 1, 7 and 2) `HearSeatEvents` learns that a stream is kept last per subject from the
stream's own info on the bus, with no new parameter; its ordered read at the start filters on the same subjects as its
durable; the read's messages go to the same handler as the durable's, so one path handles both; it names the read
`waiting` and then `read` through one callback option it calls, which the module wires to its first views, so
`process` imports nothing of `views`.
- **A-20** (§613, replaces A-8's way of opening the old section; principles 7 and 2, and no data is lost) The gate keeps
its kept data with the fewest parts that lose none: where the template `state` entry can take the old section's
existing bucket as its own, it does, and nothing is copied; where it cannot, the old section stays declared in
`state` under the local name `<name>-old` bound to its existing bucket, read through `stdio.State` like any section,
copied once by A-8's rule (a key only where the template lacks it), and retired with §613's other old sections.
The developer says which of the two the runtime allows, with the line of code that shows it.
- **A-21** (§518, §345, §457, §643; principles 7 and 3) Each put-chunk and each get call carries `objects`, the store's
local name, as today's put does. The chunk is a constant of `buscalls`, 512 KiB, which fits under the bus's default
maximum payload of 1 MiB whatever the bus is set to; a value larger than one chunk goes by session, a smaller one in
one call as today, so there is one threshold.
- **A-22** (§686, §689, A-14; principle 7) As built in mesh-catalog 9677f200 and b78e90c0: a pending finding's bound is the
module's own check interval; a field the contract's `Finding` lacks (`resolved`, and its words in every module, the watchman's included) is dropped, the keeper saying a clear in its own words; an id is escaped as a
name, and a `ref` goes into the id only where two findings of one kind share an id; old keys are cleared by the
writer's first statement. mesh-sdk keeps no old `views.Finding` or `FindingWriter` beside the generic writer.
- **A-23** (§81, §132; principle 7) The gate's `recovery` marks the delivery record through its ordinary update (the record type's `trace` set-fields operation), with one
field `recovery` holding `by` (the proved caller), `why` and `commit`; no new operation. Then it says `recovery-merged`.
- **A-24** (§431, §172, §592, §158; principles 7 and 4) Once the merge states go, `deliveries`, `show` and `groups` answer
the delivery records as the gate keeps them (G-3 *The delivery record*), filtered by their arguments, with no line
form of their own; `stalled` answers the records whose open wait is past its bound, with that wait. The pull-request
handlers and their effects stay only where G-3 §158 names what the gate owes the forge, rebuilt on the records; the
rest goes with the state table.
- **A-25** (§518, A-21; principle 3) A session put loses nothing a one-call put keeps: its first chunk carries the put's
`description`.
- **A-26** (§687, §689, A-22; principle 7) A part raises no follower's finding itself: every follower is adopted through
its reader (`Adopt`), and the reader raises and clears the follower's four findings through the module's writer, so
the pipeline runner, the indexer and the assigner build none of their own.
- **A-27** (A-16, A-28, A-29; principle 7; corrected) The tool runner is no process module, so it keeps its own
consumer and its four followers as they are and builds no `views.ProcessReader`. It writes the findings it wrote
before (`first-view-unanswered` and the others of its old code, the same kinds) through one `views.HeldWriter` over
its own connection: a small state adapter over the connection's state calls, `Publish` and `Listen` from the
connection, in its one place (A-16); it writes and clears each follower's finding as that follower's status says, as
A-28 has for a single ask. Its JetStream comes from the connection as `bindings.go` already makes it.
- **A-28** (A-26, A-22, §687; principle 7) A part's own follower (mesh-config, mesh-vault) is followed through a
`views.Reader` holding the module's writer and adopted, as A-26. A single ask that is no follower (mesh-build-go's
manifest ask) writes and clears its `first-view-unanswered` finding itself through
`Writer.Write` and `Writer.Clear` with the contract's `Finding`; mesh-sdk exports no helper for it.
- **A-29** (§686, A-3; principle 7, shared code lives in mesh-sdk) A part whose findings are raised as they happen, not
judged afresh, uses one mesh-sdk form of the writer that observes from its own state: it seeds what it holds from the
state's keys before it starts, keeps them under its own lock, and its `Observe` answers them, so no part writes that
adapter (the watchman's copy moves there; the assigner, builder and indexer use it).
- **A-30** (§383, §529, §686; principle 3; corrected) Every module whose writer publishes declares its finding events
as §529 says: a foundation part (the validator's list) in its old `streams` section as `<module>-findings`; every
other part in its `requires` by the template `events` as `finding-events`, its entry listing `finding-written.*`,
`finding-cleared.*` and `findings-stated.*`. None publishes an event it does not declare, and no other part declares
`<module>-findings`.
- **A-31** (A-14, A-22; principles 3 and 7) A module whose old finding keys end in no kind it owns (gitea's package
registry, distribution) deletes those old keys once at its start, in its own code, so no stale finding stays; the
writer's rule is unchanged.
- **A-32** (A-29; principle 7) A part that keeps its open findings in memory (artifact-store-tools, mesh-config,
distribution, gitea's package registry) uses the held writer of A-29, so a restart neither drops nor rewrites them.
- **A-33** (§151, §592, A-9; principle 7) The gate opens one record per `take-in.taken`: its key is the taken body's
`branch` and `source` (`<branch>.<source>`, each escaped as a name); its members are the taken body's `entries`
(`repository`, `pull`), a group's members in one record; its heads are the commits the take-in names for them.
- **A-34** (§291, §15, §592; principle 1) `source.<branch>` moves on `take-in.taken`: the gate writes the taken body's
`source` for its `branch`, then says `source-moved`; nothing else moves it.
- **A-35** (§167, §158, §168; principle 7) The record's changes make the same owed acts the delivery's transitions make
today (the delivery's view, a comment, the note's line), re-keyed on the record; each act said is kept in the record's
`acts` with when it was said, and ended there on `owed.ended`, so `stalled` reads the unanswered ones.
`approval-carried` and `approval-carried-open` are design lines (§542, §592), not left out: they are built, with the
`pull-merged` and `pull-closed` consumes that clear the finding.
- **A-36** (§622, §643, A-21, A-22; principle 7) As built in mesh-sdk a59ccad8: the Go package's `calls` are the four
words its calls need (`ledger-upkeep-calls`, `sent-objects-calls`, `put-sessions`, `finding-calls`), the npm package's
`finding-calls` alone; the npm package makes no session, so its `state.put` refuses a value over one chunk, naming
it. A-22 holds for TypeScript too: the TS follower keeps no plain-field `Finding` of its own beside the writer.
- **A-37** (A-16, A-29, A-32; principles 7 and 3) The held writer has a stdio form, `views.NewStdioHeldWriter`, filling
`Publish` and `Listen` as A-16's does, so no module writes the adapter; and the held writer holds every finding
written through it, its `WriteWire` included, so a reader's findings are in its statement too.
- **A-38** (A-29, A-32; principles 4 and 7) The held writer can be read: `HeldWriter.Held()` answers the findings it holds,
so a part that diffs or looks up its open findings (artifact-store-tools, mesh-config) reads them there and keeps no
set of its own.
- **A-39** (§158, §161 to §167, A-35; principle 7) Which record change makes which act, as the transitions did: every
change of a record (a step's outcome applied, the record ended) says a line on the head commit's note
(`<at> record <key>: <what changed> (<why>)`, one line) and replaces each member pull request's delivery view with
what `show` answers for the record; a comment is said only when the record ends failed or held, naming why. The gate's
own findings (`approval-carried-open` first) go through one held writer over its own `findings` state (A-29, A-37).
- **A-40** (A-29, A-37; principle 7) The held writer seeds what it holds from its state at `Start`, not when it is
made, so every module makes it at once, as it makes the plain writer, and no stdio module waits for `Served` in an
adapter of its own; a `Write` before `Start` is held and written at `Start`.
- **A-41** (§492, §604, §546; principle 1) A part reads another module's data only by asking it, so the checker and the
indexer stop reading the build modules' `toolchains` buckets: they take a toolchain from the builder's own read verb
or event where one serves it today. Where none does, the restored read stays for G, built as mesh-delivery 7a3c312a
has it, and the lead notes it for the next milestone; no new verb is added in G for it.
- **A-42** (§592; principle 8) The gate's `approval-carried-open` has scope `delivery`, a scope the mesh already uses
for the delivery's findings, and id `<repository>.<pull>`.
- **A-43** (A-26, A-28, A-16; principle 7) As built in mesh-catalog d03ffa80 and mesh-tools 4bfae38a: an owner's plain
`<owner>.views.<state>` family is followed by `Reader.Follow`, the follower's `Ask` set after (`Adopt` is for a seat's
events); and the tool runner's held writer is given its JetStream, the writer's own defaults publishing and
listening, so `bus.Conn` gains no publish method.
- **A-44** (§681, §691; principle 7) `snapshot-remove` answers the one acting form of §691 (exactly one of `accepted` with
the act's id, `busy` with the running act's id, or `error` with why), and records its caller as `by` from the proved
caller (`by-caller`), as every acting verb does. mesh-sdk's seat definition reader gains the verb field `chunks`
(§681) in the same change, with the bootstrap copies of `mesh-broker` laid again byte for byte.
- **A-45** (§681, ADR 0675; principle 7 and the standing rule that a process answers no call, mesh-sdk 1171/AC3) The bus
module answers the seat `mesh-broker`'s verbs through its tools bundle, as every bundle answers a seat verb
(`SeatAnswer`, the proved caller from the runtime), and sends a snapshot's chunks and hears their acks from there
(`SeatPublish`, its subscription); the keeper's process does the work that needs the bus and the files (taking,
restoring, removing), handed over and answered through the bus module's own `requests` record, as the bundle hands
the keeper its requests today. Where §681 says "the keeper's process serves", read "the bus module serves".
- **A-46** (§368, §372, §411; principle 7) The `version` G's change writes into each manifest is read once now from the
same source §372 fills from: the highest `published` version over the module's artifacts in the builder's
`versions` (`builder_versions`), as its MAJOR.MINOR (mesh-sdk `0.1`), and `0.1` for a module with none published.
The developer reads it through `builder_versions` and writes it into every manifest of every repository in the
group, one commit per repository, listing the values in the commit.
- **A-47** (§421, §683, §698, ADR 0593 §6; principle 7) The node-engine reads by fingerprint only (§421), so it follows
no branch: it uses the shared follower's entry cache alone (`Entries`, A-5, A-15) over its own `entry` calls on its
link session. It keeps the entries and wants of §683 in its `composed-<node>` bucket, keys `entry.<fingerprint>` (the
entry as `entry` answers it) and `want.<module>` (the want as `wanted` says it), beside the composition. Its other
users of `compose/frombus` keep that reader until their own lines move them; only the node-engine's own reads of the
indexer's and builder's states go in this change.
- **A-48** (§706, §276; principle 7, one way per part) The assigner's outside waits: a state `waits`, declared in the
same form as the assigner's other states, key `<node>.<module>.<fingerprint>`, read by `assigner_waits` and written by
the process and by `assigner_release`; the ask goes the way the assigner asks everything, through its state `asks`
as a new kind `outside` (key `outside.<node>.<module>.<fingerprint>`, options go on and count as failed), its answer
read as the others are; `assigner_release` with `how` releases an outside wait and without it ends a put as today;
a want held for M's own network says `RuleNotHeld` with words naming the open `machine.<M>.network`. As the
developer proposed on mesh-delivery feat/47-G-outside5.
- **A-49** (§518, §576; principle 7) A put or get session's bound is the call's bound the runner already uses,
`launch.CallTimeout` (30 seconds), counted from its first chunk; a session also ends when its bundle's channel closes
(the child's exit), as §518 says. The runner answers `mesh/capabilities` with `{words}` (§576), so a bundle's
capability check passes.
- **A-50** (§421, §379, §466; principle 7) The node-engine makes each artifact's reference from the want's part digest
in the form the store is already addressed by, `artifact-store://<module>/<artifact>/blobs/<digest>`, and the rest
of the resolved manifest (languages, launchers, template texts) from the entry and the parts fetched by digest; its
read of the builder's done key goes.
- **A-51** (§388; principle 7) The grants part moves to the writer only the four kinds §388 names (`view-waits`,
`first-view-unanswered`, `first-view-empty`, `binding-unlisted`), through the held writer of a process (A-29, A-40)
with the foundation stream form of §529; its other conditions stay as they are.
- **A-52** (§200, §388, §614, §684, A-26; principle 7) The grants part's `view-waits` is the one its reader raises for the
catalog half views the branch follower adopts (A-5): the part waits on the catalog half of the revision it composes
for, through those views, and writes no `view-waits` of its own.
- **A-53** (§681, ADR 0675; principles 3 and 7) A snapshot's record never ages out before its file: the bus module's
`requests` state keeps its records 91 days (one more than `snapshot-keep-days`), and the keeper removes a snapshot's
record together with its file at expiry, so no file is left without a record and no record without its file.
- **A-54** (§584, ADR 0363; principle 8) Distribution's old-section stream takes the old form's name beside
`<module>-findings`: `distribution-events`, never the mesh's own `events`.
- **A-55** (§672, ADR 0348 §5; principle 7) The builder's `toolchain-wait-minutes` reaches it the way the mesh fills a
part's settings, as mesh-conditions reads its own (the runtime's settings read at start, the manifest marking it
`runtime`), never through the process's environment.
- **A-56** (ADR 0573 §2, §4; principle 5, everything ships the same way) The locks check treats every `@novox/mesh-*`
package as it treats the SDK's, since the builder builds a module against the latest published version of each mesh
package as it does for Go; the order between the contracts' publish and gitea's build is the delivery plan's.
- **A-57** (§466, §52, §334, §349, §371; principle 7) BL1 is the whole `package-published` emission, built after BL5's
watched-branch half: the builder says it per artifact when it hears the registry seat's `published`, carrying
`resolved` and `toolchain` from the step's result (`dependencies`, §673), `branch` the watched branch of the catalog
revision whose entry the build came from, and `source` that revision, both kept on the build's reservation from the
candidate it built.
- **A-58** (§540, §685, §8; principle 8, one word per thing) An owner change is said as `condition-changed` with a new
`change` value `owner`, added to the seat `mesh-conditions`' enum in mesh-contracts; `words` stays for a change of the
text. `seed-waits` waits on the operator through the finding's own `waiting-on`, which the indexer sets and the keeper
copies, with no rule of the keeper's for that kind.
- **A-59** (§681, A-45; principle 7, and who may do what is no question) The bus module's snapshots directory is made
readable by its tools bundle's account in its manifest (a group read, nothing more), so the bundle reads a
snapshot's file and sends its chunks as A-45 says; the keeper still writes and removes the files, and a protected
snapshot is still never handed out, by the bundle's own check.
- **A-60** (§681, A-45; principle 7) The chunk subjects of a `chunks: true` verb are granted to the principal that
answers the verb: for the bus module, the tool runner's principal for its tools bundle (A-45); the restore subjects of
restricted streams to the keeper's process account, standing (ADR 0675 §2).
- **A-61** (§693, §683, §556, §113's form; principles 1 and 7) The assigner says `wanted` for every want it writes and in
the same write that removes one, on `mesh.seat.mesh-wants.event.wanted.<node>.<module>` (each escaped as a name, §73),
the seat's stream kept last per subject, so each want's last word stands; `revision` is the assignment graph revision
of the pass that writes or removes it, and `source` that pass's source revision. The seat's definition gains the
subject's two tokens and its keep-last setting.
- **A-62** (§704, §402; principles 7 and 8) The seat `mesh-module-assigner` gains the verb `placement`, class `read`:
input `{module}`, answer the module's placements as its `assignments` rows hold them (`{placements: [{branch,
nodes}]}`); the assigner serves it. An `unwatch` of a branch a placement names is refused with the new reason
`placed`, naming the placement. §675's *Kept while named* and §700's read of the router's record are the indexer's
in G, built with these rows.
- **A-63** (§706; principle 7) As built in mesh-delivery 76fce4bd and 640191d4: "a module the delivery moved to M" is
one whose want on M names a fingerprint its have there does not yet run healthy; a wait's `clock-from` is the
failing check's time, also when it first rests on `conditions-unread`; while the keeper has not answered, a first
failing check holds as the settle and becomes `conditions-unread` only after it, and a standing wait keeps its cause
while a new read is out.
- **A-64** (§681, A-45; principles 7 and 8) As built in mesh-catalog 47aaee12: a chunk's body is `{n, of, data}`; a
restore's act id is the id of the request the bundle hands the keeper; `verify` answers `accepted` with that id and
says no event; an unprotected snapshot's restore refuses nothing further; each chunk is acknowledged by a request,
since the runtime answers only calls with a reply. One change: `snapshots-filling` takes an existing scope word,
scope `module`, id `nats.snapshots`.
- **A-65** (§387, §529; principle 1, a module's data lives on the bus) mesh-knowledge's findings go through the writer
like every observer's: it gains the `finding-events` stream and a writer over the `findings` state it already
requires, and its findings no longer live in its database.
- **A-66** (§704; principles 7 and 3) The seed's `steps` call names the artifact by the contract's key
`<module>.<artifact>`, `module` beside it; its `fetch` is composed from the entry (repository, commit, path, the
artifact's paths, and `packages` and `beside` where the entry carries them); the time since a seat first failed to
answer is kept in the indexer's own state, so a restart keeps it, and the finding is written at the next seed attempt
past five minutes.
- **A-67** (§292 and G-5's *wants follow the assignment graph*; principle 7) (c): the design has the assigner write each
want from an assignment graph revision it keeps, so the pass keeps that revision first (assigngraph.Compute and Keep
per catalog revision taken in), and `wanted`'s `revision` and `source` are that kept revision's and its catalog's.
No stand-in value.
- **A-68** (§375, A-66; principles 1 and 7) The indexer keeps what `outdated` compares against in its own state, as it
hears the events: `toolchain.<language>` the newest `toolchain-agreed` per language, and `delivered.<module>` the
newest `version-delivered` per module, in the same own state as A-66's keys; no new state, no field of `VersionEntry`,
no scan of the streams at a call.
- **A-69** (§349; principles 3 and 7) The artifacts published so far at a version not yet released are kept in the
indexer's own state under `pending.<module>.<version>`, beside A-66's keys, and removed when the version is released.
- **A-70** (§572, A-45; the rule that a process answers no call) The builder's tools bundle serves `reserved` and
`builder_versions`, following the catalog half's view itself as other bundles follow views; the builder's process
serves no call.
- **A-73** (§588, §369; principle 7) To publish again, the builder takes each artifact's kind and package name from the
catalog entry of the reserved commit, and sends the registry seat's `publish` with the digest and no parts: the
holder takes the bytes from the artifact store, addressed by digest (A-50). "Not stored" is the holder's refusal;
the entry then ends failed, said with its reason.
- **A-74** (§700; principle 1, no module names another) The indexer reads the router's `asks` through the seat
`operator-channel`: its manifest `uses` that seat, and the bucket is formed from the seat's holder on the seat list
and the contract's `StateAsks`, never a module's name in code; the teller's and the node-engine's `messenger_asks`
constant goes the same way in their own change.
- **A-75** (§654, §610; principle 7, and who may do what is no question) mesh-issues shows the pipeline rows where it
already shows a delivery: its `DeliveryLine` gains `pipelines`, one row per module with its steps, `no run` where the
runner's list answers none. A runner verb's old callers list does not refuse a module: from G no verb names its
callers (§610), so the class's default holds.
- **A-76** (§588, §568, A-73; principle 7) A reservation keeps the fingerprint of the catalog entry it was made from
(`versions.Source` gains it), so publish again reads that entry; the registry seat for an artifact is found from its
package ecosystem as the builder's seat hearing already maps it. A transport fault's class (`no responders`, `name
lookup`, `connection`, `deadline`) is the one the existing retake reads from the call's error (ADR 0664, ADR 0668).
- **A-77** (§373; principles 1 and 7) A wait's bound is a bound key with its own time to live in the waiting part's own
state, as the condition keeper times its two hours (COND7): its expiry is heard as the bus's event and ends the wait
then, failing the step naming the member; no timer in the process and no looking only at the next event.
- **A-78** (§374, §375, §377, §354; principles 1 and 7) For `outdated` and its inputs, the indexer keeps what it hears:
(a) `acted.<acting part>.<dependent>` in its own state, the newest `wait-ended` (ran or released) with the build it
acted for, compared with the dependent's current build (its newest entry's fingerprint); (b) what a delivered
version serves is read from that version's catalog entry (its manifest's `serves` and words); (c) a derived edge is
marked by a field `filled: true` the contract's `VersionEdge` gains, the machines that place a module come from the
assigner's `placement` (A-62), and each node's last `running` from the node-engine's stream read whole at start
(§556); (d) an `inputs` entry is also keyed by its run ask's id, so the build seat's `step-ended` of that ask finds
and deletes it.
- **A-79** (§558, §614, §684, §196, §444; principle 7) As built in mesh-delivery 6d483af2: the gate's `source` is named
in the short `invokes` form (the manifest's `invokes` is a list of names); the catalog half's rows go into the part's
existing `grants_first_views`; while the catalog half is unread the part keeps what the bus loaded and only adds,
which is §684's way past the cycle, so no registered branch holds nothing. The part's remaining reads of the
indexer's `manifests`, `modules` and `verdicts` go too (§196): the catalog half answers them.
- **A-80** (A-66, A-68, A-69, §375, §73; principles 7 and 8) What the indexer keeps as heard (`steps-since`, `toolchain`,
`delivered`, `pending`, `acted`) lives in one state of its own, `heard`, not in `own-build`, which keeps its one key;
every key part is escaped as a name (§73), so a dotted module name parses; a kind-3 edge compares its artifact's
language with `toolchain.<language>`.
- **A-81** (§588, §369; principle 7) As built in mesh-delivery f12883b2: the publish-again queue key is
`<holder>.<module>.<version>`; the holders and their nodes are what the builder hears on `running` (read whole at its
start, §556, so a restart rebuilds them); `running` means not stopped, with a fingerprint; a not-stored artifact is
remembered on the reservation (`Unstored`); the tool's request rides a reasks mode `republish`; a message the state
cannot take is not acknowledged, as the other parts do.
- **A-82** (§7, §428, §704's seed; principle 1, no module names another) The builder finds a build module the way the
indexer's seed does (mesh-delivery d73aeb02): the build seat whose acting rule `build` has an `on` matching the
artifact's language or `kind:image`, and that seat's holder on the seat list. `Task.Module` and every lane key are
that holder's name as the seat list gives it; a plan's artifact, the toolchain sources, `UnpublishedModules` and
`builder_stuck` all derive from it, and `builder_stuck` reads each build seat's steps through the seat (`<seat>.steps`)
rather than a named module's state. The floor's sources are the holders of the seats `git` (tags) and `mesh-catalog`
(requirements) on the seat list.
- **A-83** (§374, A-78, A-80; principles 1 and 7) `BuildEdges.fp` is the fingerprint of the branch's catalog entry at
the revision `source` names, which the indexer holds itself; no field is added to `package-published`. For a build
of another watched branch, kind 1 compares with that branch's newest build, kept as heard in `heard` as
`branch-build.<module>.<branch>` (the digest `package-published` carries), not with a released digest.
- **A-84** (§194 and its candidate's-view line, §348, §473; principle 1) The builder follows no candidate: only a run's
steps read a candidate's view, so the builder learns a candidate's build from the run's build step ask, which names
each entry by fingerprint; it reads each entry through `mesh-catalog.entry` (repository and commit are the member's
head) and needs no pull request number, a build being per fingerprint. Its read of the indexer's `candidates` view
goes.
- **A-85** (§291, ADR 0666 §6, §9, §614, A-83; principle 7) The indexer keeps a catalog revision per watched branch, as
it keeps the trunk's: objects `branch.<branch>.<n>` (the branch escaped as a name) beside `trunk.<n>`, the trunk being
the first watched branch's; the branch follower's per-branch `revision` reads them, and `BuildEdges.fp` is the entry's
fingerprint in the revision whose `source` the build names.
- **A-86** (§354, §375; principle 7) `KeepInput` is called by the indexer's own `input` verb, when a run on request's asker
hands an input; the indexer asks no `run` itself. A kind-3 edge's language is its catalog edge's target artifact's
language, else the one language the entry's artifacts share, as built in mesh-delivery 652285f0.
- **A-87** (§568, §589, A-70, A-76; principle 7) As built in mesh-delivery bd228d51: a fault with no node says `no node`;
`FaultClass` maps `buildstep.PassingCause`'s words onto A-76's four classes. `builder_build`'s `newest: <branch>` is
read by the tools bundle following the catalog half itself, as A-70 has it for `reserved`.
- **A-88** (§610, the main file's item 85; principle 7) mesh-networking's A-N1 (callers named in its seven seat
definitions) is not brought in: from G no verb names its callers (§610), which supersedes item 85's reading.
- **A-89** (§37, §700, A-74; principle 7, one way for every asker) The runner reads its approval ask's warrant from the
router's record of its ask, through the seat `operator-channel` as A-74 has it, never from the decided event's body;
the decided event only wakes it.
- **A-90** (§13, §14, §291, ADR 0666 §6, A-85; principle 7) A non-first branch's revision is made by the same take-in as
the trunk's, taken per watched branch: `sourceFollow` takes `source.<branch>` for every watched branch, and the
source path (`ApplySource`, the catch-up, the heads' take-in) runs with the branch as its argument, writing that
branch's revision (corrected by A-93).
- **A-91** (§26, §37, §700, Q-RN8; principle 7) The router's record value carries its `warrant` as §700 says: the
operator-channel contract's `AsksByAskerIDValue` gains the field. A step the module's policy does not name is no step
of its pipeline, so `Status` has no open question for it. The runner places a pipeline's steps when it starts it and
asks each build seat for its step, as its asking half; that half is built in G.
- **A-92** (§560, A-77, §627; principle 7, and who may do what is no question) The node-engine's bound keys live in a new
state `bounds` of the seat `node-engine`'s definition, declared in its old section as a foundation part's states are
(§627), key `<what>.<id>` with its own time to live; the node-engine's account holds it as it holds its other states.
- **A-93** (§13, §14, §291, ADR 0666 §6, A-85, A-90; principle 7; corrects A-90's trunk) A non-first branch's take-in
is a pure computation from that branch's merged messages straight into its revision (`KeepBranchEntries`): each
module's fingerprint, inputs and graph node at the branch's heads, computed as the trunk's are, with no modules view
or bucket of its own; the trunk's `modules`, `manifests` and graph stay the trunk's alone. The trunk stays the
mesh-wide branch `main` (ADR 0666's default watched branch), named once in the indexer; a registration's own first
branch decides only which of its branches it delivers from.
- **A-94** (§379, §525, §526; principle 1, a part asks for another's data or hears its events) The assigner hears the
builder's `build.ended` event (§379 names it) and reads no builder state such as `build-log`. A want for a trunk
revision's entry takes `version` and its parts' digests from the `version-released` whose `source` is that revision;
a build `build.ended` marks kept (its keeping node) is a kept build, its parts and `kept-on` from that event; a build
of another watched branch is the one whose `build.ended` names that branch, its parts from that event.
- **A-95** (§421, §683, §698, A-47; principle 7) As built in the node-engine's commits 7703162c and 97d3df3e: the entry key is
`entry.<digits>` (a key takes no colon); `want.<module>` holds the want with its own `since`; an entry another node
runs is learned from `running`; the refusal "no entry of fingerprint" is `not-found`; `first-view-again` refuses in
words, the node-engine following no view. Still owed in G: a restart composes the bus module and the indexer from the
kept entries and wants while both are down, without waiting for every input (§683's purpose), and the
`entry-not-found` finding (§614).
- **A-96** (§379, G-4's event table, §529, A-67; principles 1 and 7) The builder says `build.ended` itself, as a module
event on its own stream: a foundation part's old `streams` entry `mesh-delivery-builder-builds`, 7 days, kept last per
subject `build.ended.<module>.<fingerprint>` (each escaped), its body the build log entry's `module`, `fingerprint`,
`made` (artifact → digest), `kept-on` and `branch`; the assigner `consumes` `mesh-delivery-builder.build.ended`. A
want's source revision is the one its pass's kept assignment graph revision was computed from (A-67), matched to
`version-released.source`; `parts` stay keyed `<module>:<artifact>`, the contract's comment corrected to say so.
- **A-97** (§230, §194, §473, A-84, A-91; principle 7) A candidate's build is asked by the runner as a pipeline step of
its own: the runner's asking half asks the seat `mesh-builds` a `build` step naming each entry by fingerprint, and the
builder takes that ask (its one entry point for a candidate) and answers the step's end; `StepRun`'s steps stay lint,
unit and integration on the build seats. With the candidate coming that way, the builder's watch of the gate's
`source` for candidate base merges goes. A kind-3 dependency's `part` is the entry's kind-3 edge, as built in
2dc51626.
- **A-98** (§297; principles 3 and 7) The node agent gains one act, `Env.Stop`: it stops a module's units and containers
and removes nothing, so a revert's data survives; the revert stops the newer build with it, asks `restore-kept`, and
then `Apply` starts the reverted build. `Remove` keeps its meaning.
- **A-99** (§230, A-91, A-97; principle 7) `build` is a step every pipeline has, placed first and required whatever its
policy names, so the runner's step enum gains `build`; each module's pipeline asks the build of its own module's
entry only, and a group's members are built by their own pipelines.
- **A-100** (§379, A-96; principle 7) As built in mesh-delivery a5e1a5e5 and 5a352482: `build.ended` is said for a done
build only; its `branch` is empty for the trunk; its old-section stream keeps 7 days with no keep-last setting (an
old `streams` entry has none), which A-96's "kept last per subject" gives up.
- **A-101** (§532, §521, A-74, A-89; principles 1, 3 and 7, shared code in mesh-sdk) mesh-sdk's `stdio.TopicMatches`
understands the bus's own `>`, so an adopted follower's `<seat>.<event>.>` hears every event, not the first view only.
The operator-channel asker (the seat holder found on the seat list, the ask published, the router's record read for
the warrant, `decided` heard) is one mesh-sdk helper that the builder, the teller, the indexer and the runner use; no
module keeps its own copy. A step's own bound is a key with its own time to live too (A-77), not a timer.
- **A-102** (§22, §24, A-97; principle 7) A part that puts work on a seat's accepted kind names it in `invokes` as
`seat:<seat>.<kind>` (the runner: `seat:mesh-builds.step` and each build seat's `check.step`), and the grants part
composes the publish of that accept subject from it, as it composes a verb's call.
- **A-103** (§532, §614; principle 7, shared code in the follower) When two watched branches list one module, the
module's own registration decides: its first branch is the one read. The `Entry` and `Ready` helpers over the branch
follower, the `<module>_entry_again` tool, the `entry-not-found` finding and the entries rows of
`<module>_first_views` are built once, in `meshcatalogclient` and mesh-sdk's views, and every reader uses them.
- **A-104** (§22, §70, §482, §25, §416, §535, §647, A-70, A-77, A-68; principle 7) The runner's write verbs (`review`,
`rerun`, `accept`, `cancel`) are served by its tools bundle, which writes the state `pipelines` by compare-and-set and
says `step-ended`; an optional step still waiting does not hold a pipeline's end, only a required one does. A step
ask's kept results are the `kept` entries whose `package` is the artifact's package (from the entry); its
`toolchain` is the runner's `toolchain.<language>` kept as heard from `toolchain-agreed`. An unanswered ask is read
from the build seat holder's `ask-taken` as §647 says (not the seat list), its five minutes a key with its own time to
live in a state `bounds` of the runner.
- **A-105** (A-104; principle 7) The runner keeps what it hears of `toolchain-agreed` in a state of its own,
`toolchains` (history 1), key `<language>`, value its language, version and digests; a step ask's `toolchain` is read
there.
- **A-106** (§25, §416, §535, §647, A-104; principle 7) As built on the runner's recover branch (239a6693, e8b69509,
2d9c6591): a kept result counts for an artifact when its package is the artifact's or lies beneath it; the ask's
toolchain carries language and version; only the three build seats' asks are bounded by `ask-taken`; the five
minutes are a constant; an expired step ends `could not run` and the others run on. The runner's write verbs wait on
the operator's answer whether a process may answer calls (then the process writes `pipelines` by compare-and-set
itself); the runner gains a whole-module integration test against the bus in G.
- **A-107** (the operator, 2026-11-11 about 23:45 CET: yes, a process may answer calls; principles 1 and 7; withdraws
A-45, A-59, A-60's bundle principal, A-70, A-87's bundle part and A-104's bundle part) mesh-sdk's rule that a process
answers no call (issue 1171, AC3) is removed: a process answers its seat's verbs directly over the bus, with the
proved caller, through one mesh-sdk call. The part that holds the data answers for it: the bus module's keeper serves
`mesh-broker`'s verbs and sends the chunks itself; the builder's process serves `reserved` and `builder_build`; the
runner's process serves `review`, `rerun`, `accept` and `cancel`, writing `pipelines` by compare-and-set; the chunk
subjects go to the process that answers.
- **A-108** (§630, §214, §406, §516; principle 7) The catch-up is the node agent's: after the restore and the `Apply` of
the reverted build, it calls the point's recorded `{tool, arguments}` as that module's tool on its own node through
the tool runner, as `holder:node-engine` is granted (§406); `restore-kept` carries the snapshot only. §630's "passes
its catch-up to restore-kept" reads "runs its catch-up after it".
- **A-109** (A-107, §681, ADR 0675; principles 6 and 7) A process that answers its seat's verbs declares them in its
manifest's process section as `serves` (mesh-sdk's `ProcessBus` takes the field, its old "a process serves nothing"
gone), and the grants part composes the process account's subscribe of `mesh.seat.<seat>.call.<verb>.*` and
`.tool.<verb>` from it, plus, for a `chunks: true` verb, the publish of `.chunk.<verb>.>` and the subscribe of
`.ack.<verb>.*.*`. A snapshot's chunks are a transfer, published on the core bus with no stream; any caller of
`snapshot-get` receives the chunks (who may hear them is no question), a protected snapshot never.
- **A-110** (A-109; principle 7) A process's `serves` is a list of `seat:<seat>.<verb>` strings, the form of `invokes`
beside it; whether a verb sends chunks is read from its seat definition (`chunks: true`), never declared again.
- **A-111** (§22, §70, §482, A-104, A-107; principle 7) As built in mesh-delivery 210abfc8 and bb51e59a: a rerun's ask id
carries its time; a rerun carries the kept results and toolchain; `rerun` of `approval` is refused naming
`ask-again`; `cancel` drops the asks' bounds and leaves what the build seats already took; the hand act's verb is
`mesh-pipeline-runner.<verb>`; a review ends every running pipeline at that commit and fingerprint waiting on one. A
review given after a pipeline ended passed is refused, as built.
- **A-112** (§633, A-71; principle 7, one way per kind of part) A process reads its settings as the builder does
(A-71): a file its manifest declares, its content `${config:<key>}`, read at its start; so the node-engine reads
`recovery-wait-minutes` from such a file, default 90 when it is missing. A bundle keeps `stdio.Config`.
- **A-113** (§297, §560, §541; principle 3, failure is loud and leaves nothing broken) A revert whose restore fails
after `Env.Stop` starts the newer build again and holds the move, saying why; a module is never left stopped. A form
change whose entries are not yet answered waits for them, never moves without its restore point. The hold says
`wait-held` (§541) as every wait does.
- **A-114** (§693, A-61; principle 7) The seat `mesh-wants`' `wanted` stream keeps the last message per subject with no
age limit (`max-age-days: 0`): it is each want's last word, read whole at a start.
- **A-115** (A-107, A-109, A-110; principle 7; corrects both) `serves` is a field of a process itself, beside its
`bus`, not inside it: the grants part adds the serve subjects to whichever account that process runs on, the
module's own when it declares no `bus`, so a process keeps hearing what it heard. Only a seat's verbs move to the
process: a module tool such as `builder_build` stays its tools bundle's (A-87).
- **A-116** (§636, §616; principle 7) A read of a seat holder's state names the seat, as §616 does for views:
`reads` takes `seat:<seat>.<state>` (route-proxy: `seat:mesh-provisioner.plans`); the grants part resolves it to the
holder on record as it resolves a seat's views, and the module finds the holder through mesh-sdk's seat-list reader
(the holder of the seat), naming no module in code; the tests that held code to a fixed name hold it to the seat form.
- **A-117** (§683, §614, §560, A-95; principle 7) At a start, until its assignments, haves and resolved values are read,
the node-engine's pass composes only the modules whose kept want and kept entry it holds, a kept want standing for
their placement, and leaves every other module as it runs; the full pass follows once the inputs are read. It writes
`entry-not-found` itself through its own writer (kind added, scope `module`, id the fingerprint, escaped). The seat
`mesh-recovery`'s `ended` and `restored` events gain `why` (the hand act's words for a cancel), regenerated, which the
recovery module fills and the node-engine's hold says.
- **A-118** (A-77, A-101, §167's checker that goes with G; principle 7) The shared `Asker` takes optional bound keys;
the builder arms them in its `bounds` state. The checker, which the runner replaces and which goes after G, keeps its
step bound as today and gains no `bounds` state.
- **A-119** (§7, §616, A-82, A-116; principles 1 and 7) The builder's floor takes its requirements from the catalog
half it already follows (each entry's dependencies), so `indexer_requirements` goes; it is woken by the catalog
revisions it already hears, so the forge stream's consume goes; the assignments view is named in the seat form of
§616 (`consumes` `seat:mesh-module-assigner.views.assignments`, `reads` `seat:mesh-module-assigner.assignments`, A-116),
its first view through the seat's verb `assignments`. No module is named.
- **A-120** (A-117, A-95; principles 2 and 7) The early pass takes the node's own record from the statement the
node-engine makes of itself (its tunnel key, sealing key and capabilities), held in memory and built before the
composer's watch starts, without the bus; no copy of the record is kept in the composition state.
- **A-121** (§554; principles 7 and 8) An old section's kept data is named as the bus composer of mesh-sdk names it
when it makes it: a state `KV_` and its bucket name (module and name joined by `_`), a log its log stream name
(`LOG_`, module, `_`, name), a stream its declared name as is, an objects section `OBJ_` and module and name joined
by `_`. The recovery module calls those mesh-sdk names; it writes no name of its own.
- **A-122** (A-116, §324; principle 7) The node-build-image seat's `build` act takes the bound the other build seats
take, `60m`.
- **A-123** (§682, §554; principle 2) `aside` takes its bus names from the catalog entry of the fingerprint the
node-engine's placement runs for the module: what is set aside is the data that runs.
- **A-124** (§682; principle 7) The seat's aside names every bus object of the kept data: `Aside.name` becomes the list
`names` in the mesh-recovery contract, regenerated; no names joined into one string.
- **A-125** (§655) The restore of one stream moves into mesh-sdk's bussnap beside its snapshot, and the bus module
imports it from there; the recovery module's builder moves it, in the same work.
- **A-126** (A-82, A-101; principle 7) Who holds a seat now is read by one mesh-sdk helper beside the seat list
(`seatlist`), through the assigner's `assignments` verb; operatorask and the view call it, and no part keeps its own
copy. The builder's holder at a catalog revision (`versions.HolderOf`) answers another question and stays.
- **A-127** (A-115 over A-109, A-110) `serves` is read only beside a process's `bus`: mesh-sdk's process parse, the
grants part's process-serve composition and the validator's test follow A-115; a `serves` inside `bus` is refused.
- **A-128** (A-116; principle 7) A `reads` in seat form `seat:<seat>.<state>` is parsed by mesh-sdk's state reader as
its own kind, kept apart from a module's read; the validator checks the seat declares the state, and the holder is
resolved where it is read, by A-126's helper.
- **A-129** (§657; principle 4) The seat's `kept-points` answer gains `unrestored`, the asides whose record ended at
`removed`, read from the engine; the tool `recovery_kept_points` shows it, and the health check reads the same.
- **A-130** (A-124, §557; principle 7) The events `set-aside` and `aside-discarded` carry `names` as `Aside` does; the
recovery module's two bounds are the settings `dump-wait-minutes` (default 60) and `point-keep-days`.
- **A-131** (corrects A-119's first view; §616; principle 7) The builder's first view of the assignments is asked of
the view's own first-view tool on its holder (`<holder>.assignments`), the holder found at runtime by A-126's helper
and the builder among the view's callers; the seat-form `consumes` of the view covers it, and the seat verb
`assignments` keeps its own answer.
- **A-132** (A-123, §556; principle 1) The recovery module learns the fingerprint a node runs for a module from the
node-engine seat's `running` event, kept last per subject and read as §556 (a `consumes` of its manifest); no call.
- **A-133** (§657, §567, §554; principles 3 and 7) The recovery module's manifest follows the validator: (a) its health
check is its tool beside the process check the other modules declare; (b) the `digests` state's cap is given in the
template's own form at 4 MiB, never as a second option beside it; (c) the template states `digests` and `findings`
are the module's own by its `requires` and are not listed again in the process's `bus`, which keeps its own account.
- **A-134** (A-16, A-26, A-28; principles 7 and 8) The licence manager's `binding.changed` carries the consumer as its
further part (`binding.changed.<consumer>`), the one form of G's seat events; mesh-vault and claude-code adopt it
through their views reader, and mesh-vault's two followers share its one writer.
- **A-135** (corrects A-133(b); ADR 0595 §3; principle 7) The state template takes no cap: `digests` declares none and
the template's fixed cap stands; §567's 4 MiB is the size expected, not a setting.
- **A-136** (§483, §496; principle 1) The watchman's read of the build seats' steps goes, as §483 says: it times a
running ask from the build seat's step-progress event; its `reads seat:node-build-*.steps` and the holder lookup
for it go with it, and no holder event is needed.
- **A-137** (§681, A-123; principle 2) Before a keeper's snapshot is restored, the recovery module sets aside the bus
names of the restore's module and kept data from the catalog entry of the fingerprint the node is running; for an
unprotected snapshot, the `stream` snapshot-get answers.
- **A-138** (§557, §551, A-77, A-130; principles 1 and 7) The wait on snapshot-restored and the expiry are bounds as
well: settings `snapshot-restore-wait-minutes` (default 90) and `expire-every-hours` (default 24); every bound of
the module may be a fraction, so a whole test sets seconds; the expiry runs on a bound key's expiry, not a timer.
- **A-139** (§681; principle 7; code shared between modules is in mesh-sdk) The chunks of a seat verb's answer are
received with mesh-sdk's own form (`buscalls`), the one the bus module sends with; where mesh-sdk has no receive,
it is added there, never built in the recovery module.
- **A-140** (§681; principles 3 and 7) After its cancel is asked at the bound, the recovery module waits one more bound
of `snapshot-restore-wait-minutes` for that act's snapshot-restored; none heard is "answers neither". A keeper that
answers `busy` ends the act failed before any aside, naming the keeper's running act.
- **A-141** (§562; principles 1 and 7) The recovery module learns of a move from its own stop: from the stop signal
on it answers every new call with an error naming the stop, cancels the running act so its own failure path runs,
and ends every accepted act not ended `stopped` with its ending event; a restart is the same.
- **A-142** (§496, §602; principles 1 and 7) A `step-ended` said while the watchman was down reaches it on its
start through its durable consumer of EVENTS, and ends the stuck ask then; nothing re-reads the steps and no
expiry is added.
- **A-143** (A-77, A-138; principle 1) The recovery module's dump wait and snapshot-restored wait are bound keys in
its state `bounds` as well, their expiry heard on its watch; no timer stays in the process.
- **A-144** (ADR 0625 §4 over §614 and §670's placing; principle 7) A client imports only its own seat's contract,
mesh-sdk and its domain SDK: the key codec `meshcatalogkey` moves into mesh-delivery's domain SDK, imported there
by the builder and the catalog client, and the source-branches follower's use of the `mesh-delivery` seat's types
moves into the domain SDK too; the catalog client keeps the mesh-catalog contract and the domain SDK only.
- **A-145** (§621, §494, §697, ADR 0633 §6; principle 7) The second reader release carries only §621's words and what
the trunk validator needs to judge the new indexer's manifest and the `by-caller` definitions; §529's finding-stream
rules and A-116/A-128's seat reads are G's own validator rules, judged by the change's validator, and stay in G.
- **A-146** (A-145, §529, A-30; principle 5) The second reader release also carries §529/A-30's exception for a
foundation part's kept old sections, and only that, since the trunk validator must accept the new indexer manifest.
- **A-147** (A-123, A-132, A-137; principle 3) While a module runs at two fingerprints (a move under way), a recovery
act on its kept data is refused, naming both; the caller asks again once the move has ended.
- **A-148** (A-145; principle 7, one release one check) A trunk fault that fails a release's own check rides that
release as its own commit, as fix 1975's distribution test did: the second reader release carries the builder's
by-hand answer fix (an ask is answered as put only after its put; issue 1126, ADR 0668 §1).
- **A-149** (§634, §527, A-120; principles 2 and 7) The node-engine knows its own name at start from its applied
store: the record of the module whose fingerprint is its own `MESH_FINGERPRINT`; the finding writer, discovery and
the vault login use it. Only on a first start with no such record do they wait for the first composing to name it.
- **A-150** (§563, A-107, A-115; principles 1 and 7) restic's `night` process, which dumps, gets a `bus` of its own
(its own broker account, as the recovery module's process has) and says item-started and item-ended and reads the
recovery module's events itself; no local request to the tools runtime, and the backup does not move into it.
- **A-151** (§465, ADR 0614 §2; principle 7) The three build modules `require` the provision `artifact-store` to read a
group member's build, with a `binds` file as go-package-registry's (`at`, `serves.port`), and read the blob by the
store's own path `<at>:<port>/v2/<module>/<artifact>/blobs/<digest>`; they never write into it, so ADR 0614 §2
holds, and their manifest tests assert the read.
- **A-152** (§472, §492; principles 3 and 7) The toolchain's `digest` is the sha256 of the toolchain program the build
module runs (the `go` or `node` it finds), computed at its start and again when the toolchain's version changes; no
field is added to the provision.
- **A-153** (§559, E §1; principles 7 and 8) The tool runner resolves a `deliver: new` subscription's key
`<emitter>.<event>` to its subjects with mesh-sdk's `buscompose.EventSubjects`, the one rule from an event name to
its subjects (a seat's event subject or the module's); a pattern with a wildcard is refused with `{error}`.
- **A-154** (design 46 approval mark §4, §16, §19; principles 7 and 8) The approval mark's three findings take scope
`machine` and the id `<node>.<module>`, `<node>.<ask id>` or `<node>.approvals`, escaped as a key's name, with the
subject of the tool runner on that machine, as the runner's first-view findings already do.
- **A-155** (§561, §563, §302, A-77; principles 1, 2 and 3) restic's put-off wait for a restore is a bound key in a
state of its own on the bus, bounded by the setting `put-off-bound`: the key is the put-off record `backed-up` reads,
its expiry raises `backup-waits` and ends the wait, so the night never holds its lock without a bound and no file
or timer keeps it. BKP7 is not built: §555 gives `restore` its class's default with no own-item limit.
- **A-156** (§610, A-82, design 51 *The move*; principle 7) Once distribution's `callers` list goes, the windows'
driver takes "every build module" as the modules that claim a build seat (`node-build-*`) in the catalog half it
follows, the one source A-82 gives; the holder's own per-verb `Allowed` checks go with the list, since §610's class
defaults are applied where the call is read, and the refusal of an unproven caller is that reader's.
- **A-157** (design 46 approval mark §16, §19, ADR 0668 §1, A-77; principles 1 and 3) Each try of `warranted` is a
`DoKept`; past its bound the call is made again when the hand-act seat's holder is heard again (the seat-holders
view the runner follows) and at each start; `call-unanswered` is raised on the expiry of a five-minute bound key per
ask, not a timer. A failed read or write of `approvals` is made again at the next marked call and at the next
membership change the runner hears.
- **A-158** (design 46 approval mark §16, §677; principle 8) A marked call's answer is `{answer, approval}`: the tool's
own answer and the line naming the approval, the form a process holder's `gated-ended` has, so a caller reads one
form from both gates. A-157's bound key lives in the runner's state `bound` as `<node>.unanswered.<ask id>`.
- **A-159** (§466, §472; principles 3 and 7) A build's `resolved{package, version, digest}` names what was used: its
`version` is the commit the ask names, and its `digest` the sha256 of what was laid (the Go module cache zip, the
npm tarball), or the member build's digest when it was laid from `builds`.
- **A-160** (§64, §190, §321, A-85, A-93; principle 7) A run's candidate is keyed by its run id `run-<id>` from the
runner's `run-started`, beside `try-<n>`; each member {module, commit, branch} maps to its repository by the
registrations and to the branch's revision view as its trunk; its manifests and fingerprints come from the same
branch take-in the indexer computes for any commit (its files read through the seat `git`), so no checker preview
is heard.
- **A-161** (§522, §81, §170; principle 1) An event carries what its hearers need to say it: `recovery-merged` gains
`module` and `branch`, and a forge-made record's `record-opened` gains `repository`, `pull`, `commit` and `branch`,
in their contracts and from their emitters; the teller makes no call per message.
- **A-162** (amends A-100; §575, A-94, ADR 0596; principle 1) The builder's `build.ended` is said for every end of a
build, carrying `outcome` (done or failed) and, when failed, `error`; the assigner's hold says "failed: <error>"
from it and "is being built" while no `build.ended` of that fingerprint is heard. The assigner never reads the
builder's `builds` bucket; its read there (put.go:85, the manifest's `reads`) goes.
- **A-163** (§543, §544; principles 7 and 8) The asker in an ask's accept subject is the asking seat's own name
(`mesh-pipeline-runner`, `mesh-catalog`, `mesh-wants`, `mesh-builds`, `mesh-delivery-checker`), not a nickname, so
steprun checks it against the callers the build seat's definition lists, read from the seat list; no table of its
own and no name map. A body's kind is the word the seat definition gives; a queue keeps its own name.
- **A-164** (§468, §76, §495, A-115; principles 4 and 7) The runner's process writes each durable consumer's oldest
unacknowledged age into a state of the runner's own when it changes; the bundle's `runner_health` reads that state,
its own module's, and asks no seat verb of itself; tools stay the bundle's (A-115).
- **A-165** (§610, A-156; principles 3 and 7) The class defaults of §610 are applied where a seat's verb is answered:
mesh-sdk's seat server (`conn.AnswerSeat`) and the tool runner's `serveSeats` read `seatdef.EffectiveCallers` and
refuse a caller outside them, the unproven one included, before the holder runs; a holder keeps no caller check of
its own.
- **A-166** (A-164, §76, §495; principles 1 and 7) The runner keeps a state `consumers` (history 1, a key per
consumer) holding the time its oldest unacknowledged message was delivered, written when a message is delivered or
acknowledged; `runner_health` works the age out as now minus that time when it is read, so nothing is read on a
timer.
- **A-167** (§354, §63, §22, §64, §357, §298; principles 1, 2, 3 and 7) For the assigner's composition step (AS5): its
`run` builds the node-engine's composer (`mesh-compose`) at the fingerprint the node-engine runs; each machine's
input is asked of that node's engine by `compose-candidate`; the machines are the nodes the branch's revision places
the module on; a node is asleep when the watchman's `machine-away` is heard for it; `mesh-compose` reading
`MESH_RUN_INPUT` and answering the run's output is the node-engine's, a row of its own. An entry held for want of a
provider (AS14) carries a field `held` naming the candidates, never only a log line. A revert (AS17) is a want that
goes back to an earlier fingerprint, which the assigner computes itself; a kept data's `form` is read from the
catalog entry's kept declarations (`catalogue.KeptDeclarations`). AS14's named seat and contribution edge, and
AS17's ask of the operator, go to a design line.
- **A-168** (E §33, design 46 approval mark §5; principle 3) For a marked wait on a module the runner does not serve,
the marks are the union of the newest entry of every followed branch; where the runner serves the module, its
served entry is added to that union. A mark is never lost.
- **A-169** (§579; principles 2 and 7) A pins state keys a revision by the revision object's own name (the assigner's
`trunk.<n>`, the indexer's `<branch>.<revision>`), value `{records, missing?}`, and holds two more keys in the same
form in every holder of pins: `made`, written once by create-only when the state is first used, and
`pins-stated`, the `at` of the applied event; pins count only once a `pins-stated` later than `made` is applied.
- **A-170** (§600, §481, §415, A-163; principles 7 and 8) Every ask to a build seat rides `accept.<queue>.<asker>`;
its kind is in its body only, as §600 says. A build seat definition's `stream` names only the queue, and the
generated accept subjects follow; the kind in the subject goes. §543's main subject takes `build` and `run` as
written, the checker's asks riding it until it goes.
- **A-171** (§579, A-169; principles 2 and 8) The `pinned` event's `catalog` entries name their branch: each is
`{branch, revision}`, in the mesh-delivery seat's definition and from the gate that says it, so a holder keys the
pin `<branch>.<revision>` from what the event says.
- **A-172** (§653, §431, A-161; principles 1 and 7) With the Approve per merge gone, the teller's merge-ask path goes
whole (its reads of the gate's `asks`, its handlers and their tests); the teller keeps `answers` and `answered` for
the assigner's asks only. A view's answer reaches it by the view's event, as §431 says, never by reading the
view's state. The gate fills `recovery-merged`'s `branch` from the branch it merged into and its `module` from the
catalog half's registrations; while no path of the gate opens a forge-made record, the teller says none.
- **A-173** (§507, §512, §532, A-103; principle 7) The view derives each watched branch's source on its own: the
source row and `delivered` per branch, a repository's reach from the source of the first branch of its
registration (before the first registrations, the branches `source` answers), and `view_tiers` with no revision
answering per branch; no branch is named in its code.
- **A-174** (§64, §190, A-160, A-93; principles 2 and 7) The indexer keeps a state `runs` (key `run-<id>`, value the
members and the modules whose pipeline ended), written on `run-started` and on each of the run's `pipeline-ended`,
removed with the candidate, so a restart loses nothing. A member on a branch its registration does not watch takes
the trunk's revision view as the base of its take-in; one on a watched branch takes that branch's.
- **A-175** (§512, §431, A-43, A-172; principles 1 and 7) The teller says its `answers` as a plain views family
`views.answers`, and the view says its own `answers` the same way; each follows the other's by mesh-sdk's
`ProcessReader.Follow` (first view through the owner's tool, then its views family). That is the "event" A-172 and
§431 mean; no own follower and no module-event adopt are added.
- **A-176** (A-166, §579, §76; principle 7; code shared between modules is in mesh-sdk) A process writes its durable
consumers' facts (stream, name, acknowledged sequence, pending, last event applied and its `at`, and when its oldest
unacknowledged message was delivered) into its module's own state `consumers` (history 1, a key per consumer) through
one mesh-sdk helper, after each message it applies; the runner and the indexer use it, and their tools read that
state.
- **A-177** (design 46 approval mark §4; principle 7) mesh-sdk's compiled `catalogue.Verb` gains `NeedsApproval`,
copied by `SeatsFromDefinitions` from the definition, so the validator and every reader of the seat list read a
verb's mark from one place.
- **A-178** (A-175, A-43; principles 7 and 8) The view says its `answers` as other owners say theirs: a read tool
`view_read_answers` (mesh-sdk `views.ReadTool`, in the view's tools bundle, the teller among its callers) and a
marks state `view-marks` (history 1), as the assigner and the indexer have; the teller's side is the same, with
`teller_read_answers` and `teller-marks`.
- **A-179** (refines A-165; §610, §336; principles 3 and 7) Where a seat's verb is answered, an unproven caller is
refused, and so is every caller when the verb's effective callers are empty; a proven caller is taken as among
them, since the grants part composes a verb's call subject only for the callers its effective list names, so the
bus holds the rest out. `seatdef.DefaultCallers` follows §610 (act, warrant and accept: operator, sessions and
invokers).
- **A-180** (§64, §420, A-160, A-174; principles 1 and 7) The runner places a run's pipelines when it hears the
catalog seat's `catalog-revision` for the candidate `run-<id>` (a candidate's revision, its subject naming it),
reading the candidate's entries and fingerprints through the seat's `revision` verb with the candidate's group;
`<id>` is a random 12-hex id the runner makes, so two runs of one commit are two pipelines.
- **A-181** (amends A-100 and A-162; §556; principles 1 and 2) The builder's `build.ended` stream keeps the last
message per subject (one per fingerprint), and the assigner reads it whole at its start as §556 reads a kept
stream, then hears it; so what was built, and a failed build's error, survive its restart and it reads no builder
bucket. `assigner_put`'s fingerprint-prefix form goes: a put names the whole fingerprint.
@@ -0,0 +1,379 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-12
decisions:
- "ADR 0666"
- "ADR 0656"
- "ADR 0596"
- "ADR 0567"
- "ADR 0565"
- "ADR 0540"
- "ADR 0633"
- "ADR 0579"
- "ADR 0571"
- "ADR 0564"
- "ADR 0539"
- "ADR 0330"
- "ADR 0429"
- "ADR 0259"
- "ADR 0126"
- "ADR 0112"
- "ADR 0084"
---
# 47, milestone G: the provider a requirement points to, the ask of a held revert, the edge an acting rule gives, the take-in of every watched branch, and the seats of the status view's and the assigner's views
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§708 to §710 answer the assigner's develop questions on build-map rows AS14 and AS17 (2026-11-12), which the lead's
answer A-167 ([G-20](G-20-lead-answers.md)) sent to a design line: which field names the seat whose holder provides a
requirement, which field says a requirement carries contributed values and the edge's kind word (§357), and the form
of the assigner's ask when a revert is held (§300). §711 answers the indexer's develop question on build-map row IX8
(2026-11-12), which the lead's answer A-212 (G-22, the lead's second answers file) sent to a design line: which acting steps
give an edge of kind 5 (§47), between which entries, and what a catalog entry's `acts` holds. §712 to §715 answer
the indexer's develop questions on rows IX5 to IX7 (2026-11-12), which A-213 sent to design lines: the validation's
run before take-in, what holds a pending revision while it runs, and how a watched branch other than the trunk gets
its merges, its pending revision and its groups. §716 answers the lead's question of 2026-11-12 (A-279) that no
principle settles: where the gate keeps a group's members and each member's base before the take-in. §717 answers
developer J27's question of 2026-11-12 on A-210: which seat the status view's answers and the assigner's asks are
views of, and how a follower starts before their holder is known. Each is the
design's choice within the record it cites, tested against [the principles](../../../00-META/principles.md).*
## The provider a requirement points to
- §708 **The named seat is the seat that delivers the requirement's provision; no catalog entry field names it.**
(ADR 0330 §2; ADR 0126 §3; ADR 0084 §3, §4; ADR 0571 §4; design 27 *A module provider*) This completes §357's
order for the assigner's `Compute`. A catalog entry's `requires` stay bare provision names. For each requirement
R of a placed module on node N, in this order:
1. **The named seat's holder.** The seat whose definition's field `delivers` (mesh-sdk `seatdef`) is R, among the
seat definitions the entry's catalog graph revision names in its `definitions` (each read whole through the
seat `mesh-catalog`'s verb `seat-definitions`, as every part reads the catalog half). Its holder is read from
the same revision's placements, from their `holds`, as the assigner's state `seat-holders` keeps them: the key
`<seat>` for a seat held once for the mesh, `<seat>.<N>` for a node seat. A seat that delivers R and that no
placement holds holds the entry, naming the seat; no other step is tried (design 27, step 1).
2. **A pin.** The placed module's `bindings` entry for R, else N's placement `pins` entry for R.
3. **The provider on N.** The one module placed on N whose catalog entry's `provides` names R.
4. **The only provider.** The one module placed on any node whose catalog entry's `provides` names R.
Each step that answers gives the entry the edge of kind `provisioning`, its `node`, `module` and `fp` the
provider's and its `provision` R. Where no step answers and more than one candidate is placed, the entry carries
`held`, a list with one item per requirement held: `requirement` R, `seat` (the unheld seat, step 1 only) and
`candidates`, each `{node, module}`, sorted; the assigner writes no want from a held entry, so the node keeps what
it runs, and the field is read with the entry through the seat `mesh-wants`' verbs. An optional requirement
(ADR 0330 §3) with no candidate is left absent: no edge, no hold. A hard requirement with no candidate is held
with `candidates` empty. Why: the seat definition already carries `delivers`, and the assigner already keeps who
holds which seat from the placements, so nothing new is declared and no part outside the assigner's inputs is read
(ADR 0656 §2; principles 1 and 7); a hold is said in the graph, never only in a log (principle 3; design 47 G
A-167). How it is checked: unit (assignment graph), one row per step, the seat's holder winning over a provider
on N, an unheld delivering seat holding the entry with `seat` named, two candidates and none on N held with both
named, an optional requirement with none left absent.
- §709 **A requirement carries contributed values where the manifest's `contributes` names it; its edge is of kind
`contribution`.** (ADR 0656 §2; §174, §175, §357, §442) This completes §357's contribution edge. The catalog entry
gains `contributes`: the requirement names the manifest's `contributes` object has as keys, sorted, its local
names not carried; the indexer fills it, since it alone reads a manifest (§442). `requires` keeps naming every
requirement, those only `contributes` names included, as mesh-sdk's `catalogue.Manifest.Wants` lists them, since
contributing to a requirement is requiring it. For each requirement in `contributes` that §708 resolves, the
consumer's entry carries, beside its `provisioning` edge, an edge of kind `contribution` to the same provider, its
`provision` the requirement. The kind word is `contribution`, the glossary's and the seat `mesh-wants`' `Edge`
`kind` (provisioning, contribution or acting); the assigner's `EdgeContribution` joins `EdgeProvisioning` and
`EdgeActing`. The `Edge` fields' descriptions in the seat `mesh-wants`' definition become: `provision`, for
provisioning and contribution, the requirement; `seat`, for acting only, the seat whose rule gave the edge. A seat
contribution (the manifest's `contributions`, a piece given to a seat's holder on the node) gives no edge in G.
Why: the manifest already says which requirements carry contributed values, and the edge kind already has its word
in the glossary and the contract (principles 7 and 8). How it is checked: unit (indexer), a manifest whose
`contributes` names `route` gives an entry whose `contributes` and `requires` both name it; unit (assignment
graph), that entry gets a `provisioning` and a `contribution` edge to the route's provider, and an entry with no
`contributes` gets none of kind `contribution`.
## The ask of a held revert
- §710 **The assigner asks the operator about a held revert as it asks about a failed backup.** (ADR 0539 §16;
ADR 0259 §3, §4; ADR 0564 §2; §297, §300, §363, §461, §541, §700, §706) This completes §300. When a node's have
says the phase `held` for a revert, with the reason `restore point missing` (§300, a missing point or no
`restored`) or `restore point damaged` (§567), the assigner:
- **Asks**, through mesh-sdk's `operatorchannelclient` on the seat `operator-channel`, as its other asks are made,
an ask of the new kind `revert`, kept in its state `asks` under `revert.<node>.<module>.<fingerprint hex>`, the
fingerprint the reverted build's; like the backup ask it needs no proof of who answered, so the view may answer
it. Its headline: "The revert of <module> on <node> has no restore point". Its explanation names the reverted
fingerprint, the kept data, its form and the node's reason in its own words, and says "<module> keeps running
its newer build until you answer. Started on the live data, the older build reads data written in the newer
form." Two options, each of level acknowledge: `hold`, "Keep it held", does "the node keeps running the newer
build, and the revert waits"; `live`, "Start on the live data", does "the node starts the older build on the
live data as it is, with nothing restored". It expires after the assigner's `AskLasts`, `OnExpiry` "the module
keeps running its newer build". The answer is read from the router's record of the ask (§700).
- **Raises the condition**, through mesh-sdk's finding writer (§461), with the finding `revert-held` (scope
`delivery`, id `<node>.<module>`), so the condition keeper's key is `delivery.<node>.<module>.revert-held`;
severity `warning`; its summary "The revert of <module> on <node> is held: <reason> (<kept data>, form <form>);
the ask about it waits for your answer"; its `needs` "Nothing for you to do.", so the operator's one message is
the ask (§706).
- **On the answer**: `live` writes the node's want again with the want's new field `revert` `live` and `words`
"the operator answered \"live\" to the revert ask (asking <n>)", as the backup's answer is written; the node
agent, reading `revert` `live` from its own want and from nothing else, starts the reverted build with no
`restore` and no aside taken, calls no catch-up tool, and applies as any want. `hold` writes nothing: the hold
stands. The finding is cleared once the reverted build runs, or when a newer want of that module on that node
replaces the held one, which also drops the ask (§363). An ask that ends unanswered is raised again at once
while the hold stands; one answered `hold` is not asked again.
- **Its tools**: `assigner_read_asks` reads the ask and `assigner_read_findings` the finding, both existing;
`assigner_ask_again` raises it again by hand.
Why: ADR 0539 §16 has the assigner ask, raise the condition and write the want again naming the answer for a hold
before an apply, and the backup ask is that shape already built (principle 7); the finding writer is the one way
G's parts raise a condition (§383); the ask alone reaches the operator, as O-9 had it for §706 (principle 3, the
hold is still loud in the condition). How it is checked: whole (assigner, a fixture node agent saying `held` with
`restore point missing`): the ask under `revert.<node>.<module>.<hex>` with both options and the finding raised;
`hold` writes no want and the newer build keeps running; `live` writes the want with `revert` `live` and its
words, and the finding clears once the have names the reverted build; a newer want drops the ask and clears the
finding; an unanswered ask raised again. Unit (node-engine): a want with `revert` `live` starts the reverted build
with no `restore` call; unit (`meshwantsclient`): a `revert` other than `live` is refused.
## The edge an acting rule gives
- §711 **An acting rule gives an edge of kind 5 from the acting part to each entry it acts on, in the steps that act on
a delivered version.** (ADR 0656 §1; ADR 0666 §14, §19; §47, §49, §203, §265, §266, §270, §324, §374, §378, §535,
§569, A-208) This completes §47's row 5 for the indexer, which writes a catalog entry's edges (A-208).
- **The entry's `acts`.** The indexer fills a catalog entry's field `acts` when it makes the entry: for each seat
its manifest's `claims` names, each acting rule that seat's definition carries, among the definitions the entry's
catalog graph revision names in its `definitions` (read through the seat `mesh-catalog`'s verb
`seat-definitions`, as §708), as one item `{seat, step, on, bound}`: `seat` the claimed seat, `step`, `on` and
`bound` as the definition writes them, `bound` left absent where the rule names none (its step's default, §266,
is the reader's). Items sorted by `seat`, then `step`; a module that claims no seat with a rule has no `acts`. It
is what the runner (§535) and the builder (§546) read, and what the edges below are derived from; nothing else
writes it. The contract's description of `step` gains `pipeline` (§203).
- **The steps.** `grant`, `compose`, `place` and `check`, and from G's delivery `pipeline` in `check`'s place
(ADR 0666 §19; §65), each give kind 5: a grant or a credential, a composition, a placement, a verdict, each made
for the dependent's new version (ADR 0666 §14). `build` gives kind 3 (§47, §267), never kind 5. `take-in` and
`validate` give no edge: an entry is in a revision only once the indexer has taken it in and validated it, so
such an edge would always be met (principle 7).
- **Between which entries.** For each entry B with such a rule, and each other entry A of the same revision that
the rule's `on` matches (`every`; a language, A having an artifact of that language; `kind:image`, A having an
artifact of kind image, §324): one edge `kind` 5, `from` B's delivered artifact, `to` A's delivered artifact (the
artifact the entry's first process resource runs, else its first, as kinds 4 and 6 have it), `word` `acts`, `why`
`<seat> <step>`, `stale` false (it only orders, §378), `declared` false. No edge goes to B itself, nor to an entry
B's new version is built from or needs: one with a path of edges of kinds 1 to 4 to B in that revision (a
package, a fetched artifact, a build module or base, a provider), which B's running version acts on (§270). An
entry with no artifact has no edge. A rule of a seat no entry claims gives none; the step's own holder says that
(§233, §535).
- **How it is met.** The edge is met when B's new version has acted for A's build, as the holder's `wait-ended`
says and §374 records; for `compose` per node, by the assigner (§268). It is a delivery edge, so a cycle of it
with kinds 2, 4 and 6 only is delivered as one step, and one with kind 1 or 3 is refused naming it (§49, §569).
Why: the rule already says the step and the entries, and the artifact a delivery orders is the one kinds 4 and 6
already name, so no manifest word is added and no module is named in code (principles 7 and 8); a cycle left by a
rule is refused loudly at the check (principle 3). How it is checked: unit (indexer's graph), an entry claiming a
seat with rules `grant` on `every` and `build` on `go` carries both in `acts` with its seat; a grants part of rule
`grant` on `every` gives kind 5 to every other entry but none to itself nor to the build module and package it is
built from; a rule `compose` on `kind:image` gives edges only to entries with an image artifact; a rule `build`
gives no kind 5; an entry with no rule has no `acts`.
## The validation before take-in, and the take-in of every watched branch
- §712 **The validation is one `run` of the indexer's manifest validator over two cases, refusing only what the revision causes.**
(ADR 0666 §5, §6; ADR 0579 §2; ADR 0578 §2; §16, §206, §338, §354, §467, §481, §493, A-213) This completes §16's ask.
For a pending revision of watched branch B (§713), the running indexer asks the seat `node-build-go` the accepted
kind `run`, on `mesh.seat.node-build-go.accept.run.indexer` (§481; its callers §338), its step key
`indexer.<node>.<id>` (§467), `<id>` `validate-<s>-<n>`, `<s>` the stream sequence of the newest merge the revision
covers and `<n>` the count of asks for it, from 1. Its job:
- **`program`**: the indexer's own program `cmd/mesh-judge`, under the Go module the `package` of the indexer
entry's Go artifact names, at that entry's `path`, built at the revision's head of that entry's `repository`;
the indexer entry is the entry of B's revision being validated that claims the seat `mesh-catalog`, else, when
B watches no indexer, the trunk's current revision's. No repository or module name is fixed in code (ADR 0666
§5); the program's own directory is the indexer's own code.
- **`heads`**: none, since the manifest validator reads only its request; **`args`**: none; **`reader`** "manifest validator of
<B> at <s>".
- **`input`**: the manifest validator's request, served by the seat `mesh-catalog`'s verb `input` and kept in `inputs` as §354
has it, any size (ADR 0666 §16): case `change`, every module of B's current revision by its entry's manifest,
each one the pending merges carry replaced by the merged message's manifest bytes, one added for a new
directory; case `trunk`, every module of B's current revision as it stands (the name is the validator's, not the
branch); `seats` the definitions the merges' trees carry (the main file, *Validated with the seat definitions
its merges carry*); `packages` and `predefined` as the take-in's own manifest validator has them; no `machines`.
- **Its verdict.** The revision is refused when the case `change` refuses a manifest the revision adds or changes
(its digest differs from the `trunk` case's) with a problem the `trunk` case lacks, or names a problem `(between
manifests)` the `trunk` case lacks (ADR 0579 §2). A problem only in unchanged manifests refuses nothing; their
entries keep their verdicts (ADR 0579 §1). A refusal sets the pending value's `refused` to each manifest and its
problems, said in `take-in.pending` (§493).
Why: the manifest validator already answers two cases for exactly this rule, as the checker ran it, and the build seat's `run`
is the one way a program built from heads runs (principle 7; ADR 0666 §6). How it is checked: whole (indexer, a
fixture Go build module): a revision whose changed manifest gains a problem stays pending, `refused` naming it; a
revision whose unchanged manifest has one is taken in; the job names the indexer entry's repository, path and the
revision's head, and no heads.
- §713 **A pending revision names its ask, and resumes on the ask's end; a run that could not validate is no
refusal.** (ADR 0666 §6, §18, §24; ADR 0633 §5; §16, §18, §19, §24, §269, §483, A-213; the main file *A wait is
bounded, said and recoverable*) This completes §16's wait, and amends the main file's *A revision that names another
indexer moves only that indexer* in that every revision now waits there for its validation. Every merge of a watched branch is first written to
`pending` as the main file has it (key `m.<sequence>`, or a group's key, §715), its value gaining `validation`:
`ask` (the `run` ask's id), `asked` (when) and `input` (its digest). The indexer puts the ask, acknowledges the
merged message, and the take-in ends there. It hears the end through a durable consumer of its own of the seat
`node-build-go`'s event `step-ended` (§24), taking only ends whose step key starts with `indexer.`, and reads the
answer from that step key; at its start it reads each pending value's `validation` and its step key, so an end said
while it was down is not lost. An end whose ask is not the one the pending value names now is dropped, written in
its journal. A later merge of B while an ask is out joins the pending revision (the main file) and is asked anew
with the next `<n>`. On the end:
- **accepted**: the revision is taken in as the main file has it, by the indexer it names (§16), and its keys go;
- **refused**: it stays pending with `refused` (§712), asked again only as part of a later merge of B, as a
revert is (§19);
- **could not validate**: the step ended `could not run`, the program exited other than 0, or its answer holds not
both cases: it stays pending with `validation`'s new field `failed`, the build seat's words, said in
`take-in.pending`, never as a refusal and never as passed (ADR 0666 §18; principle 3). It is asked again by a
later merge of B and by the indexer's new tool `indexer_validate_again` (key; class `act`, §391's default callers,
written in the hand-act log), which also reads the ask's state.
An ask no build node takes waits on the work queue, said by `build-capacity-waits` after 60 minutes (§483); every
case is timed by `take-in-waits` at 60 minutes (§19). `indexer_pending` shows `validation` whole. Why: a take-in that
holds no call open survives a restart from its own state, and only an answer the manifest validator gave refuses (principles 1,
3 and 4). How it is checked: whole (indexer): a restart while the ask is out takes the revision in on the end; a
`could not run` end leaves it pending with `failed` and `indexer_validate_again` asks `<n>`+1; an end of a replaced
ask is dropped.
- §714 **Every watched branch's merges come on the forge bridge's stream, each naming its branch.** (ADR 0666 §6,
§11; §11, §13, §18, §493, A-90, A-93, A-208) This completes A-208's per-branch take-in. The forge bridge publishes
the merged message of every landed commit of every watched branch the registrations name, heard on `branch-moved`
and read by the seat `git`'s verb `branch` as §11 has it, the trunk's included, since from G no merge is the gate's
(ADR 0666 §11). The merged message (`forgemsg.Forge`) gains `base`, the watched branch it landed on; the forge
bridge's filters to the trunk alone (its merged message and its forge-made merge) go. The indexer's `pending` keeps
its key `m.<sequence>`, the stream's sequence being one across branches, and its value names `base`; a merge waits
only behind a pending value of its own `base` (§18), and is taken in by A-93's take-in of that branch. A value
written before G names no `base` and is the trunk's, the only branch the forge bridge published before G. The
gate's `source-moved` takes nothing in. Why: one take-in for every branch, from the stream every merge already
comes on, and no second source of the same merges (principles 1 and 7). How it is checked: whole (forge bridge,
indexer): a merge on `beta` publishes a merged message with `base` `beta`, pending behind a pending `beta` revision
while a `main` merge is taken in.
- §715 **A group gathers per watched branch, and its end names that branch.** (ADR 0666 §13; ADR 0633 §5; §42 to
§46, §293, §714, A-208) This completes §43 and §44 for a branch other than the trunk. A group's members on watched
branch B are those whose `base` in the seat `mesh-delivery`'s verb `groups` is B (its `Member` carries `base`). The
forge bridge sets the group on each landed member's merged message of any watched branch, and publishes
`forge.group.merged` (`forgemsg.ForgeGroupEnd`, gaining `base`) once `groups` says every member with that `base`
has landed, once per `base`. The indexer gathers the members in `pending` under `g.<group>.<base>`, each part
escaped as a key name, and takes them in as one source revision of B on that end, after §712's validation; a key
`g.<group>` written before G is the trunk's. `indexer_pending`, `indexer_take_in_pending`, `indexer_drop_pending` and
`bridge_group_end_again` take the `base`; a group that never ends on B is said at 30 minutes (§45). Why: a group
is delivered on the branch its members land on, read from the one verb that knows its members (principles 1
and 7). How it is checked: whole (forge bridge, indexer): two members landing on `beta` give one `forge.group.merged`
with `base` `beta` and one `beta` source revision; a group whose `beta` member never lands raises `take-in-waits`
at 30 minutes.
- §716 **The gate keeps a group's members from their announcement, in its states `deliveries` and `groups`, heard on
the seat `git`'s pull events.** (ADR 0666 §13; ADR 0656 §7; ADR 0633 §4, §5; §42, §43, §45, §151, §156, §613, §715,
A-236) This completes §42 and §715: ADR 0666 §13 has the gate form a group and its group verb say every member, and
before G's delivery the gate kept a group from the announcement in `deliveries` and `groups`, which §613 already
moves to template states under the same names; §716 has G's gate go on writing them. The gate's manifest `consumes`
the seat `git`'s `pull-updated` beside the `pull-merged` and `pull-closed` it consumes, through its one durable
consumer, and `invokes` `seat:git.open-pulls` and `seat:git.pull`. Each event carries the pull request's
repository, number, head, `base` and head branch (`branch`).
- **What it writes, in today's forms.** On `pull-updated` the gate writes the delivery under its id
`<repository>@<first 12 of the head>`, escaped as a key name, as today, with `repository`, `number`, `commit` (the
head), `base`, `branch`, `group` and `state` `open`; a newer head of the same pull request takes the older's place
in its group and the older key is deleted. Pull requests with one head branch form one group, keyed by that branch
escaped as a key name, its `members` the delivery ids, as today (§42): a group of one with no member in a record is
no group: its key is deleted and its member's `group` cleared. `pull-merged` sets the delivery's `state` to `merged`. A delivery copied from
before G (§613) in another state of the old table counts `open`. Only the gate writes either state.
- **Who deletes it.** The gate: a delivery on `pull-closed` of a pull request not merged; a delivery once the record
that carries it opens (§151's take-in, or §156's switch), its `base` copied onto the record's member (`RecordPull`
gains `base`) and, for a member with no delivery, the take-in's `branch`, said in its journal; a group key once no
member remains outside a record. A deletion is made in the old section too (§613).
- **How `groups` answers.** A group is answered from its records and its `groups` key together, each member once,
a record's first: a record's member with its copied `base` and `state` `merged`, a kept member with its delivery's
`base`, `head`, `state` and `open` while not merged. So before any take-in the verb names every member and its
base, and the forge bridge reads them as §715 has it (A-236).
- **When a member has landed.** For §715, a member has landed once `groups` says it `merged` and the bridge has
published its merged message. The bridge asks `groups` at each such message it publishes and at each
`pull-merged` it hears of a group it holds open (`bridge_groups`), so the end follows whichever comes last, never
a timer.
- **At its start**, after §613's copy, the gate reads `open-pulls` once and writes each open pull request it lacks
or keeps at another head. For each delivery it keeps `open` that the answer does not name, it reads `pull`: merged
sets `merged`, closed deletes it. A read that fails is said in its journal and made again at the next start. Until
then, a group whose end never comes is said by §45's 30-minute `take-in-waits`, and the indexer's
`indexer_take_in_pending` takes it in as gathered.
`merges` stays as §156 has it, read by the switch alone. Why: the gate forms the group and its verb says every
member (ADR 0666 §13), from its own states heard on the seat `git`'s events. It keeps the two states and forms it
already holds, adding no state and no new tool (principles 1, 4 and 7). A missed event is put right from the forge's
own record at the start, and a group that stays open is said (principle 3). How it is checked: whole (gate), two
pull requests on one head branch, bases `main` and `beta`. `groups` names both with their bases before any
take-in; a newer head replaces its member; a `pull-closed` before the merge removes it. A `take-in.taken` of the
`main` member opens a record with `base` `main`, and the `beta` member stays in `groups`. A `pull-merged` missed
while the gate was down is set from `pull` at its start. Whole (forge bridge): the group's end is published once
both the member's merged message and its `pull-merged` have come, in either order.
## The seats of the status view's and the assigner's views
- §717 **The status view's answers are a view of the seat `mesh-delivery-view`, the assigner's asks and wants are
views of the seat `mesh-wants`, and a follower finds each by its seat and starts without it.** (ADR 0540 §1, §6;
ADR 0429 §3; ADR 0567 §5, §6; ADR 0571 §6; ADR 0565; ADR 0539 §16; ADR 0596; §7, §200, §428, §532, §616, §699,
§710, §614, A-126, A-131, A-175, A-178, A-210, A-295) This answers developer J27's question of 2026-11-12, met building A-210
and A-296 (G-22): A-210 has a part find another module as the holder of the seat it holds, but the status view
claims no seat while the teller follows its `answers` by its module name, and the assigner holds two seats while
the teller follows its `asks` by its module name. It completes A-175 and A-178 for both views.
- **The seat `mesh-delivery-view`.** ADR 0540 §1 names the seat the status view holds, `mesh-delivery-view`; G
defines it. Its definition is in the Delivery domain's contracts module, `seats/mesh-delivery-view/` beside the
domain's other seats, with a states fixture as theirs have: `scope` mesh, `grain` whole, `domain` Delivery,
`decision` ADR 0540. It serves no verb and declares no seat event and no `family`: the status view's tools stay
its own (one seat, many tools), each caller reaching them at the holder A-210 finds, and its events `delivered`,
`moving`, `settled` and `stuck` stay the holder's own on the subjects ADR 0540 §1 names, so every `consumes` of
them keeps its entry and a part that builds their subject in code builds it from the holder A-210 finds. It
declares one state, `answers` (template state, history 5, `view: true` by §699), whose one key form is
`<ask key>.view`, `<ask key>` a key of the seat `mesh-wants`' state `asks` of a kind that needs no proof of who
answered, and whose value is the status view's answer: `v`, `asking` (the asking it answers), `option`, `words`.
Only the holder writes it, the person's answer in the web view, and only the holder deletes a key: once the ask's
key has left `asks`, heard on the `asks` view it follows, so no answer outlives its ask. The status view's manifest `claims` the seat with scope mesh and an empty `serves`. Its
first-view tool of `answers` is named for the state, `answers` (mesh-sdk's `views.ReadTool`, the teller among
its callers, A-131's form), in place of A-178's `view_read_answers`, which goes.
- **The seat `mesh-wants` owns `asks` and `wants`.** Every kind the assigner keeps in its state `asks` (hold,
restore, backup, record, outside, revert and a placement proposal's `assignments`) is an ask whether what a node
runs moves, and ADR 0567 §6 names the states of `mesh-module-assigner` without `asks`, so `asks` is a state of
`mesh-wants`: its definition gains `asks` (template state, history 5, `view: true`), its key forms the eight kinds'
keys as the assigner's `asks` package writes them, its value the ask as the assigner composes it. Its `wants`,
which the definition already declares, gains `view: true`. The holder's first-view tools are named for the
states: `wants`, as it exists, and `asks`, new, in place of `assigner_read_asks`, which goes.
- **The followers name the seat.** The teller's manifest `consumes` `seat:mesh-wants.views.asks` and
`seat:mesh-delivery-view.views.answers` in place of `mesh-delivery-assigner.views.asks` and
`mesh-delivery-view.views.answers`, and its `invokes` loses `mesh-delivery-assigner.assigner_read_asks` and
`mesh-delivery-view.view_read_answers`, the seat-form `consumes` covering each first view (A-131). The status
view's manifest `consumes` `seat:mesh-wants.views.asks`, `seat:mesh-wants.views.wants` and
`seat:mesh-module-assigner.views.assignments` in place of the three `mesh-delivery-assigner.views` entries, and
its `invokes` loses `assigner_read_asks`, `assigner_read_wants` and `assigner_read_assignments`. Neither part's
code names the assigner or the status view (§7, §428).
- **What the seat form puts on the bus.** A seat's view is said where its holder says it, on the holder's own
subject `mesh.mod.<holder>.event.views.<state>`, and the grants part resolves a `consumes` entry
`seat:<seat>.views.<state>` to that subject for the seat's holder on record, granting nothing while the seat has
none (§616). The subject is module-scoped, so a follower must know the holder, and follows it as it moves.
- **A follower finds and re-finds the holder from the seat-holders view.** mesh-sdk's `views.ProcessReader` gains
`FollowSeat`, given a seat and a state, and the holder function A-295 gives a part, which answers from what the
part last heard of the seat `mesh-module-assigner`'s view `seat-holders`. The part follows that view as the tool
runner does (§616): the module that claims `mesh-module-assigner` is read from the catalog half the part follows
(§614, the trunk's entry whose manifest `claims` the seat), so no code names the assigner's module; the view is
named `seat:mesh-module-assigner.views.seat-holders` in the part's `consumes`, its first view asked of the
claimant's tool `seat-holders`, and a catalog revision naming another claimant drops it and follows the new one
(§532). `FollowSeat` returns at once a picture that reads as empty, its follower not ready, and never fails for
want of a holder. At each change of the seat-holders view it reads the seat's holder: on a first holder it follows
as `Follow` does after `Start` (§532), the first view asked of the holder's tool named for the state, then its
views family; on another holder it drops the view of the old one and follows the new one, the picture handing on
the new holder's state whole; with no holder it keeps the picture it has and follows nothing. Nothing is asked
again on a timer. While no holder is followed the follower's status names the seat in place of its owner, waiting
since its start or since its holder left, and past five minutes the reader writes §200's `view-waits` naming the
seat and the state, cleared when a holder's first view arrives. The teller's two follows and the status view's
three go through `FollowSeat`, each apart, so the teller raises the assigner's asks whether or not the status
view's answers are read (issue 1906, as today); the teller gains the catalog half's reader of §614 for this. The
builder's and the checker's own loop for the assignments, buildrun's `AssignerHolder`, which asks every 5 seconds,
goes: both follow `assignments` through `FollowSeat`. `seatlist.HolderOf`'s call stays for a caller that asks
once, as A-210 has it.
Why: ADR 0540 §1 already names the seat the status view holds, and §616 and §699 give a view named by its seat;
`asks` goes where the kinds it holds belong, its proposal kind kept with them so the teller follows one view. The
holder is heard from the seat-holders view as A-295 has it, never asked on a timer, and a moved holder is followed
as it moves (principles 1, 3, 7 and 8). How it is checked: unit (mesh-sdk `views`), a `FollowSeat` with no holder
returns at once and writes `view-waits` naming the seat past five minutes; a seat-holders change naming a holder
takes its first view from `<holder>.<state>` and hears its family; a second change naming another holder drops the
first and follows the second; no ask is made between changes. Unit (manifest validator):
`seat:mesh-delivery-view.views.answers` and `seat:mesh-wants.views.asks` are accepted, and a seat view of a state
without `view` is refused. Whole (teller): started before any module holds `mesh-wants`, it raises an assigner's
ask once the holder is placed, and takes an answer the status view writes. The code review: no module name of the
assigner or the status view in the teller's, the status view's, the builder's or the checker's code, and no
timer that asks for a holder again.
@@ -0,0 +1,588 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-12
decisions:
- "ADR 0667"
---
# 47, milestone G — the lead's answers to develop's questions, continued
The answers of [G-20](G-20-lead-answers.md) go on here, since that part reached its size bound: each one line, naming
the principle that settles it, cited as "design 47 G A-n".
- **A-182** (§31, §350, §119; principles 2 and 7) A superseded step's ask is dropped where it stands: a taken step
keeps the node `ask-taken` named, and the drop is asked of that node's holder of the build seat; an untaken ask's drop
is asked of any holder, which records it in the seat's shared drop key, so no holder takes it. Steps on `mesh-builds`
and `mesh-wants`, whose seats serve no `drop`, are only ended superseded.
- **A-183** (design 46 approval mark §4, §23; principles 3 and 7) A manifest that marks a verb nothing enforces is
refused by the validator wherever it runs, as every rule's problem is; the indexer's "set aside to wait" is only its
reading of a field it does not yet know (the reader edge of §23), so no new waiting verdict is added.
- **A-184** (clarifies A-170; §543; principle 3) Until main's subject goes, it also takes the checker's `check` and
`recheck` asks, so no ask that runs today is refused by G; a queue is only `delivery`, `check` or `recheck`, and
steprun's `AskKinds` lists no `step` queue.
- **A-185** (A-180; principle 7) The catalog client (`meshcatalogclient`'s catalog follower), which already hears every
`catalog-revision`, hands a candidate's revision it does not keep to a hook its caller sets; the runner sets it to
place a run's pipelines. mesh-sdk's event hub keeps its one handler per pattern.
- **A-186** (A-179; principles 3 and 7) `conn.AnswerSeat` takes the seat's definition from its caller, which reads it
from the seat list it already follows; a verb the definition does not hold, or a definition not yet read, is
refused for every caller and said so, so a holder answers only once its seat list is read.
- **A-187** (A-176, §76; principle 3) The consumer ledger keeps a message's first delivery time: a message delivered
again (asked again, or after a restart) keeps the time it was first delivered, so a message that never settles
grows old and §76's check fires for it.
- **A-188** (A-172, §170, §151; principles 3 and 7) A take-in writes its entries (each with its module) into the
gate's record, so `recovery-merged`'s `module` is filled from the record; no forge-made open path is built, since
G ends the mark (§151). A record of the old milestone form, made before G, says no branch.
- **A-189** (§376, §374, A-181; principles 1, 2 and 7) At its start the assigner reads whole, as A-181 reads
`build.ended`, the streams of its own `version-delivered` and of the catalog seat's `version-released`, so it says
each version delivered once and knows the released ones. A dependent whose want changes fingerprint waits, by an
edge of kind 2, 4 or 6, for its dependency's newest released version on the dependent's branch not yet said
delivered; on a branch where no version is released there is no wait.
- **A-190** (ADR 0668 §1, §2; principles 3 and 7) A kept call is kept only while what it serves still stands; for the
indexer's four: `messageVia` while the commit's take-in is still pending, `stepsVia` while the candidate is held,
`sourceVia` while the branch is watched, `versionsVia` while the module is in that branch's newest view. Once
that ends, the call stops and is not said unanswered. `pin-missing` takes scope `module`, id
`mesh-delivery-indexer.pin.<key>`.
- **A-191** (§33, §540; principle 3) A `pipeline-failed` finding whose commit no pull request merged, or whose `pull`
read fails, is written with no `owner` (so it reaches the operator at once) and says why in its explanation; a
failed read is said and the owner is written at the next step end of that commit.
- **A-192** (ADR 0596, ADR 0603 §8, §499, §541, §520; principles 1 and 7) The builder says `wait-held` and `wait-ended`
for a key queued behind a package, in the one wait form with its `part`, and the watchman times it from those; its
reads of the builder's `builds`, the node-engine's `have` and `mesh-host.health` go, replaced by the node-engine's
`running` (with `healthy`) and its wait events.
- **A-193** (§64, §73, §373, §420, A-180, A-185; principles 2, 3 and 7) For a run: `run` writes each member's
pipeline at once (key `<member branch>.<id>.<module>`, running, no steps, `Run` its id), and the candidate's
revision places its steps by compare-and-set; `pipeline-started` is said when the steps are placed, with the
fingerprint and the candidate's revision; `run-started`'s subject names one member's module, branch and commit, or
for several the first member's module and branch and `run-<id>`; a member naming no branch is refused. The hook is
called once per candidate revision at the catalog follower's one subscription, whatever branches are followed, so
a runner following none still hears it. As §373 says, a run builds each member and its dependents: the dependents'
pipelines come from the candidate's entries whose fingerprints the members change.
- **A-194** (principle 4; §64) The indexer's state `runs` and its run candidates get tools as every kept thing does:
`indexer_runs` reads them and `indexer_run_drop` drops one by hand (its hand act written first); a run id is
`run-` and the runner's random 12-hex id, which the indexer's wider pattern accepts.
- **A-195** (§65, §266, §276, §534, §19, A-77, A-149; principles 2, 3 and 7) The runner's `pipeline` wait is a field on
each held pipeline's record (`held`: since, and the fingerprint awaited), no new state. A take-in changes the runner
when its own module's entry (its own name, A-149) gets a new fingerprint; every pipeline started after that take-in
is held. The pipelines' records are on the bus, so whichever runner holds the seat when the wait ends starts them:
the wait ends on the node-engine seat's `running` for the runner's module at the awaited fingerprint with `healthy`
true, on any node; its 30 minutes are a bound key, whose expiry starts the held pipelines and writes a finding.
- **A-196** (§553 with A-91; principle 7; the operator, 2026-11-09: merges need no approval) An empty status makes a
step `optional`, as §553 says, except the approval step: a policy that names no approval status asks the operator
nothing, as A-91 says; only a policy that names approval asks for it.
- **A-197** (ADR 0596, A-192, A-149; principles 1, 3 and 7) What only the node-engine knows of itself it raises as its
own findings through its finding writer: a have's failed or removed phase and its detail, its kept-data findings,
an unreachable tunnel, and every part of its health report (machine network, rewritten network, machine units,
relogin needed, directory used as found, node account fallback). The watchman stops reading `node-engine.have` and
`mesh-host.health` and raising those; it keeps only what it sees across parts.
- **A-198** (§677; principle 7; shared code is in mesh-sdk) The act's form, `ActDigest` and the ask's form move from
`operatorchannelclient` into mesh-sdk's `go/operatorask`, which the client re-exports, so the seat server in
`go/process` holds the gate with no import of mesh-contracts and no cycle between the two.
- **A-199** (A-185, A-193; principle 7) mesh-sdk's `ProcessReader` gains `AdoptEvent(seat, event, hear)`, a seat event
heard on the one subscription with no picture, made at once if no follower made it; meshcatalogclient's
`WireCandidatesProcess` adopts `catalog-revision` through it and hands each candidate revision to the hook.
- **A-200** (§502, A-188; principles 7 and 8) A taken-in delivery record's `milestone` is `<branch>.<source revision>`,
so the watchman ties a `record-opened` to the `take-in.taken` of that branch and source revision by its milestone;
`record-opened` gains nothing, and a record is covered only by what is late for its own revision's entries.
- **A-201** (§189, §190, A-160, A-174; principle 7) The assigner keeps a candidate's assignment graph per run (key
`run-<id>`), made when the candidate's revision is heard and dropped on that run's last `pipeline-ended`, as the
indexer keeps the candidate; the gate's `heads-replaced` drops nothing in the assigner.
- **A-202** (§31; principles 2 and 7) A pipeline's record and `pipeline-started` say their cause: an optional
`published` {artifact, version} set when a package's publish started it; a newer publish of the same artifact on
the same branch supersedes the running pipelines whose `published` names that artifact at an older version.
- **A-203** (design 46 approval mark §6, G §195, §424; principles 1, 7 and 8) The withheld path's forms: the tool
runner's announcement carries `fingerprint` (its own `MESH_FINGERPRINT`) beside `capabilities`; the node's record
gains `runner-capabilities` and `runner-fingerprint`, its merge into `capabilities` staying for placement; the
assignment half's per-node item gains `readings` and `fingerprint`, its digest over all of it, read from the node
record the assigner holds; the grants part follows the assignment half by `mesh-wants`' `revision` and its
`assignment-revision` event; its user list carries `withheld: [{module, subject, why}]`, and the seat `mesh-grants`'
verb `withheld` takes `{node}` and answers `{node, modules: [{module, subjects: [{subject, why}]}]}`.
- **A-204** (§498; principle 2) The assigner keeps which put waits it said in its own state on the bus, so a put
that ends while the assigner is stopped gets its `wait-ended` at the next start.
- **A-205** (§441, §195; principle 7) `grants-for` reads its candidate's catalog half through the catalog seat's
`revision`: for a run, with `group` `run-<id>` (the candidate's newest); for a source revision, with `branch` and a
new optional argument `source`, answering the branch's revision taken in from that source revision. It answers the
permissions the composed memberships gain and lose with the candidate's entries in place of the branch's newest.
- **A-206** (A-195, §65, §534, A-181; principles 3, 4 and 8) The runner's held pipelines are released by hand through
a seat verb `release` of `mesh-pipeline-runner` (class act, its hand act written first), beside `start` and
`ask-again`, its tool `runner_release`; the bound's expiry writes the finding `pipeline-waits` (scope `module`, id
`<branch>.<id>.<module>`); the wait's `on` gains the value `runner version`; its `clock-from` is the `build.ended`
of the awaited fingerprint, heard as A-181 says.
- **A-207** (§86, §81, §415, §71, §275, §335, §441; principles 1, 2 and 7) The runner keeps a state `recoveries`
(commit to why) written when it hears the mesh-delivery seat's `recovery-merged`, read at a pipeline's start and
resume; a recovery heard after its pipeline started asks nothing again. `widens` is computed from two reads: the
catalog seat's `revision` of the candidate against the branch's newest (the serving parts' entries and fingerprints,
found by holder from the seat list; the acting rules `step`, `on` and `bound` and the credentials the entries
declare), and the grants part's `grants-for` (the permissions); no other verb.
- **A-208** (ADR 0656, §558, §605, §16, §293, §600, §18, §104, §44-§46, A-93, A-190; principle 7) The indexer writes a
catalog entry's edges of kinds 1 to 6, as the catalog graph is its own; the long form of `invokes` is read by the
second reader release's `InvokeFields`, which G merges. Every watched branch's take-in uses the trunk's machinery,
per branch: its own pending revision (holding only that branch) and its own groups. `stepsVia` is open while the
entry's fingerprint is still in the branch's newest revision; `messageVia` while a pending entry names the merge or
a take-in of that commit runs.
- **A-209** (design 46 approval mark §13, §677, §73; principles 3 and 7) A digest in a key is escaped with mesh-sdk's
`keys.EscapeName`, the one escape form for every name in a key, in both gates: `<node>.act.<escaped digest>`. The
kind (a test's in-memory state accepting a key the bus refuses) is fixed where it lives: mesh-sdk's in-memory state
for tests refuses every key the bus would.
- **A-210** (§7, §428, A-126, A-131, ADR 0596; principles 3 and 7) A module that calls another module's tool or hears
its module events finds that module as the holder of the seat it holds, with `seatlist.HolderOf`, when it calls,
asking again after a failure and never stopping its start for it; a read of another module's bucket goes (each by
its own row). A tool's callers list names no module: it takes the holders of the seats `node-tool-runner` and
`mesh-healers` from the seat list when a call is checked, beside `operator`.
- **A-211** (A-204; principles 2 and 7) The assigner's said put waits live in a state of their own, `put-waits`
(history 1, key `<node>.<module>`, value the wait's id and the put), so no form is shared with §706's waits and the
record outlives a supersede.
- **A-212** (§47, §432, A-208; principle 8) An edge of kind 2 comes from a `needs` reference to another module's
artifact: that is what an artifact "fetches by reference at its start", so the referenced artifact is published
before the referring one delivers. Kind 5 goes to a design line (which acting steps give it, and between what).
- **A-213** (corrects A-86; §16, §354, §206) The indexer does ask node-build-go's `run` for its validation of a
revision before its take-in, as §16 says; A-86 holds only for `KeepInput`, which the indexer's `input` verb calls
for a run on request's asker. The run's input and job, what holds the pending revision meanwhile, and how a non-trunk
branch's pending revision and groups are made go to design lines.
- **A-214** (§366, §623; principle 7) `show` and `kept` read each store's fill through the bundle's state call
`mesh/state.info` (§623), as the forge module's read tool does; the runner's process writes nothing for it.
`pipeline-failed` takes scope `merge`, id the commit.
- **A-215** (§71, §275, §335, A-207; principles 3 and 7) For `widens`, the pipeline's own revision (the candidate) is
compared with the branch's newest revision: an acting rule widens when an entry's `Acts` gains a rule, or a rule's
`on` now names more, or its `bound` is longer; a credential widens when an entry's manifest names an own secret or
provision of a credential it did not name before; permissions come from `grants-for`.
- **A-216** (amends A-181; principles 2 and 7) The builder's `build.ended` stream, kept last per subject, has no age
bound, only a cap in bytes: one end per fingerprint is the record of what was built, so a module unchanged for any
time is still known built after a restart.
- **A-217** (§298, §300, A-167, A-132; principles 2 and 7) A want reverts when its fingerprint is one the module had
in an earlier assignment graph revision than the one it now runs from, by the revisions' own numbers; the running
manifest is the catalog entry of the fingerprint the node-engine seat's `running` names, read through the catalog
seat's `entry`, and `FormChanges` compares it with the want's entry.
- **A-218** (A-206, §696; principle 3) `pipeline-waits` is cleared when the node-engine seat's `running` says the
awaited runner fingerprint healthy; A-206's `runner version` holds over §696's wording.
- **A-219** (refines A-198; §677; principle 7, one form for each thing) Only `Act` and `ActDigest` move into mesh-sdk's
`go/operatorask`; the ask's types stay the generated seat contract's. The gate in `go/process` takes the operator
channel's calls (ask, read the warrant, record) as an interface its caller passes, which `operatorchannelclient`
implements over the generated types, so no form is copied and no import cycle arises. The seat client's
`wait: held` lives with `process.Conn`'s call, as there is no `go/seats`.
- **A-220** (§71, §441, A-167, A-207, A-215; principle 7) The serving parts `widens` compares are the holders of the
seats `mesh-pipeline-runner` (the runner), `mesh-catalog` (step detection), each step's own seat (the build seats
that run lint and tests) and `node-engine` (the composer, A-167), found by `seatlist.HolderOf`.
- **A-221** (§509, A-208; principle 8, one word per thing) On every forge message `branch` names the watched branch
the move changed, on `merged` as on `pushed`; a merged pull request's own branch is kept as `head` (a group is
known by its head branch); `pushed`'s head before is `before`.
- **A-222** (A-193, A-199, §350, A-77; principles 2 and 3) An adopted event whose handler fails is asked again by
the bus, not acknowledged; at its start the runner reads, for each run pipeline with no steps yet, its candidate's
revision through `revision`, so a placing lost while it was down is taken up. A run's rerun asks with the
candidate's catalog half. An unanswered drop is said `call-unanswered` on a five-minute bound key's expiry.
- **A-223** (corrects A-221; principle 8) A merged pull request's own branch is `head-branch`; `head` stays the head
commit, as everywhere in the forge messages.
- **A-224** (§677, A-219; principle 7; code shared between modules is in mesh-sdk) The approval gate is one package,
mesh-sdk `go/approval`: the tool runner's gate (`node-tools` `internal/approval`) moves there and both the tool
runner and `go/process`'s seat server use it, each passing the operator channel as an interface (A-219); no second
gate is written.
- **A-225** (A-173, §532; principles 2 and 7) The view keeps each repository's reach in its own state on the bus, so a
restart does not lose it; its history is the source revisions heard since its start, re-asked through `source` on
each `source-moved`, laid over the reach it kept.
- **A-226** (A-167, §354, §64, §63, A-174; principles 2 and 7) The composition step asks each node's
`compose-candidate` with the key the step's pipeline names: a run's candidate `run-<id>`, or for a branch pipeline
the branch's revision `<branch>.<revision>`, which the node-engine reads as it reads a candidate. It runs
`mesh-compose` once per node-engine fingerprint among the machines (the change's build where the change alters the
node-engine, §63), the program read from that fingerprint's catalog entry through `entry`, each run's request
joining the inputs of the machines on that fingerprint.
- **A-227** (withdraws A-221 and A-223; §714, §715) As design line §714 says, a merged message gains `base`, the
watched branch it landed on; `branch` stays the pull request's branch and `head` its head commit, as today;
`pushed` keeps `branch` for the branch pushed and gains `before`.
- **A-228** (A-219, A-224; principle 7) Everything the gate reads beyond itself crosses into mesh-sdk `go/approval` as
an interface in the gate's own terms, its caller translating from the generated contracts: the catalog's entries
(module, fingerprint, serves, manifest), a finding writer (raise and clear by kind and id), the seat holders (a
holder function), and the operator channel (ask, record, and claim with the warrant and the act). The gate's own
kept form `Kept` moves into `go/approval`, which `operatorchannelclient` re-exports.
- **A-229** (§708, §709; principle 7) As §709 says, a catalog entry's `requires` names every requirement as
`catalogue.Manifest.Wants` lists them, optional ones included; a reader that means "required, not optionally" (the
module graph's `Requirers`, the assigner's halves) tells the optional ones apart by the entry's manifest
(`IsOptional`).
- **A-230** (§554, §565, A-124; principle 7) A restore point's `Point` gains `names`, the bus names it took, one per
bus object as `Aside`'s; the recovery module fills it when it takes a point, and the grants part reads
`points[].names` and `asides[].names` from `kept-points`.
- **A-231** (§544, §601, A-163, A-170; principles 3 and 7) An asker names the queue it asks in its manifest's `invokes`
as `seat:<seat>.accept.<queue>`, read by one mesh-sdk `catalogue` helper that the validator, the indexer's kind-6
edge and the grants part share; every asker of a build seat gains that entry in G, so the named workers stand from
G's delivery and the purge finds nothing to remove.
- **A-232** (§712 with §600, A-163, A-170; principle 7) The validation run rides `accept.check.<asker>` with its kind
`run` in the body and the asker the indexer's seat name `mesh-catalog`, as every ask does; its id is the usual
form (`NewID`), and the revision's sequence and try number are kept in the pending value's `validation`. §712's
subject and id are read so.
- **A-233** (A-181, A-189; principle 7) mesh-sdk's `HearStream` gains a form that reads an age-kept stream whole from
its first message by an ordered consumer at every start, then follows the durable; the grants part grants that
ordered consumer on every heard seat kept stream, as it grants `STREAM.INFO`, with no manifest word.
- **A-234** (§711, ADR 0656 §6; principle 2) The indexer fills each catalog revision's `definitions` (the seat
definitions it was taken in with, by digest) when it keeps the revision, and the `acts` fill reads from them; until
then reading the trunk's from its `seat-definitions` state stands.
- **A-235** (A-201, A-174, §190, RN9; principles 1 and 7) The runner says `group-ended` with group `run-<id>` once every
pipeline it placed for the run has ended, as it says a group's end; the assigner drops its candidate graph on it, and
the indexer drops the run's candidate on it in place of counting members itself. The kept object's key stays the
seat's form `candidate.<group>.<heads>` with group `run-<id>`.
- **A-236** (§509, §714, §715, §11, A-227; principles 2 and 7) A `pushed` message's new head is `head`; the bridge
keeps each watched branch's last head written in its own state `heads` (key `branch.<owner>.<name>.<escaped
branch>`), `before` read from it and its start read comparing with it; `pushed` carries the same take-in inputs as
`merged`. For §715 the mesh-delivery seat's `groups` member gains `base` in the contract, which the gate fills as
§716 has it (from the seat `git`'s pull events, before any take-in) and the bridge reads.
- **A-237** (§712, §354, ADR 0666 §16, A-139; principle 7) A run's input is any size: the indexer keeps it in its state
`inputs` with the state's `-objects` store holding a large value (mesh-sdk's `stateobjects`), and serves it through
its `input` verb answered in chunks (`buscalls.SeatChunks`, a verb marked `chunks`); `RunInputBound` goes, and the
holder takes the input by that verb.
- **A-238** (§712; principle 7) A run ask may name no head: `RunJob.check` takes an empty `Heads` when the program
is named, and the holder lays an empty `MESH_RUN_HEADS`; the validation's program is built from the indexer's
entry alone.
- **A-239** (design 46 approval mark §6, H-21; principles 3 and 7) The node-engine's `composition` answer gains
`withheld` [{subject, why}] (the module's entry of the grants part's `withheld` for this node) and
`withheld-unknown`, the words "withheld unknown: the grants part does not answer" when the verb fails; its
manifest's `uses` gains `mesh-grants` beside the `invokes` of `seat:mesh-grants.withheld` H-21 added.
- **A-240** (§354, ADR 0596; principle 1) The assigner waits on its composition run's answer by hearing the build
seat's `step-ended` for that ask, never by reading the seat's step state.
- **A-241** (§712, A-234; principle 3) The validation's `seats` are the branch's revision's seat definitions (A-234)
with the merges' changed definitions laid over them, so a revision that touches no definition is still judged
against the whole seat list.
- **A-242** (§512, §186, §376, §378; principles 1, 2 and 7) The assigner's `difference` fills `Tiers` and each
change's `after` (the kind of edge that orders it), computed in its compare; the view orders nothing itself. A
`view_tiers` call for a source revision compares the kept assignment revision whose catalog for that branch is that
source revision with the revision kept before it. The view keeps a state `waits` (key owner, step, node, module)
written by its process from every part's `wait-held` and `wait-ended` it consumes, read by its own read tool.
- **A-243** (corrects A-226's joining; A-237, A-238; principle 7) The composition step runs one request per machine,
each carrying that machine's input; no inputs are joined. A-237 holds for every run's input (the assigner's and
`mesh-wants`' `input` too): any size, kept with an `-objects` store, answered in chunks. The composition run names
no head (A-238); its program is the node-engine fingerprint's entry (repository, commit, directory) with the
composer's package.
- **A-244** (§677, A-186, A-219; principles 7 and 8) `wait: held` travels as a bus header on the request, never as an
argument, so no verb's input and no act's digest changes; the holder's held answer is a reply field `held`
{asked, call} beside result, error and node. `process.Conn`'s seat call sends the header on every seat call and
waits for `gated-ended` only when the reply says `held`. A process holder applies the seat definition's
`needs-approval` alone (the definition `AnswerSeat` is given); it reads no catalog entry.
- **A-245** (§298, §368; principle 7) §298's order edges are held under the assigner's rule 7, as its acting waits
are; a change of the assigner's state forms bumps its manifest's version once, in the commit that changes them.
- **A-246** (§376, §498, A-211; principles 2 and 4) A dependent's delivery wait is kept in a state of its own,
`delivery-waits` (key the module, value the `wait-held` said), read by `assigner_waits` beside the others;
`assigner_release` {module, step: delivery} writes the hand-act log first and ends the wait `released`.
- **A-247** (§713, A-232; principles 1, 2 and 7) The pending value's `validation` gains `accepted` (the seq it was
accepted at), so a revision held for another indexer is never asked again; `validation` sits on the newest pending
value of the revision alone. The answer is read from the `step-ended` event's own output and output object, never
through the build seat's `steps`; the ask is put through the delivery SDK's `buildrun.Asker`.
- **A-248** (A-163, A-232; principle 8) `buildrun.Asker` gains the asker its subject names (the caller's seat name,
A-163), empty meaning the builder until the asker rename at the join; the ask's own `Asker` field is left as it is.
- **A-249** (A-237; principles 7 and 8) A bundle takes a chunked seat answer through its ordinary seat call: the tool
runner receives the chunks with `buscalls.SeatChunks` and hands the bundle the whole answer, so no bundle receives
chunks itself. mesh-build-go's `inputOf` calls the mesh-catalog seat's `input` that way and decodes that verb's
own answer form.
- **A-250** (§376; principles 1, 2 and 4) `assigner_delivered_again` writes the hand-act log, then an ask in the
assigner's own state `delivered-again` (key the module, value {version, by, why}) and answers "asked". The
assigner's process hears that write on its own state at once, says `version-delivered` again when every present
placing machine runs the version healthy and deletes the ask, else keeps it with its reason, read by
`assigner_waits`.
- **A-251** (§17, §383, §437; principle 8) The finding `branch-unprotected` has scope `merge`, id the owner,
repository and branch escaped as one key name (`<owner>~<repository>~<branch>`), severity warning, and that scope
and id as its subject.
- **A-252** (§354, A-167, A-174, A-226, A-243; principles 1, 2 and 7) The composition step's machines are the nodes
whose placement of the module names the step's branch in the seat's assignments (no branch read as the trunk); a
run on request on an unwatched branch takes the nodes placing the module on the trunk. Each request's cases and seats
come from the step's catalog revision through mesh-catalog's `revision` (each entry's manifest digest and verdict,
its `definitions`), depth compose, the one machine, one case `change`. A machine's node-engine build is what the
assigner keeps from the node-engine seat's own events, never read from the node-engine's bucket; where the step's
revision's node-engine entry differs from the trunk's, that build serves every machine. One run ask per machine.
- **A-253** (A-249; principle 8) The tool runner hands a bundle a chunked answer as the verb's own answer with the
whole file added as the member `file` (base64).
- **A-254** (§712, §354, A-240, A-247, ADR 0596; principles 1, 2 and 7) A build seat's `step-ended` of a run carries
the run's answer (its standard output's last line): inline as `output` up to the event bound, else as
`output-object`, the name of an object the build seat keeps, read through the seat's verb `output` answered in
chunks. Every build seat (`node-build-go`, `node-build-image`, `node-build-node`) says it the same way, from the
shared step runner; no asker reads the seat's step state.
- **A-255** (§677, A-77, A-244; principles 1, 7 and 8) The header is `x-wait: held`. A process holder's
`gate-unanswered` is a bound key with its time to live in the `approvals` state, as the tool runner's `bound` is,
never a clock wait. A process holder's act names no node, so two instances of the holder raise one ask.
- **A-256** (corrects A-251's separator; principle 8) The finding's id is the owner, repository and branch joined by
`/` and escaped as one key name (`keys.EscapeName`); a key holds no `~`.
- **A-257** (A-249, A-253, A-254; principles 7 and 8) The tool runner sends a bundle's answer of a verb marked
`chunks` in chunks, from the whole file the bundle answers in its member `file`, the mirror of A-249: no bundle
sends chunks itself.
- **A-258** (§298, A-210; principles 1 and 8) §298's "bus module" is the holder of the seat `mesh-broker`, and its
"`node-backup` holder" the holder of the seat `node-backup`, each read from the seat list as every holder is: the
assigner names no module.
- **A-259** (§298, A-245, §266, §268; principle 7) §298's three order edges are acting waits of the assigner, kept
in its state `waits` under `order.<node>.<module>.<part>` beside `acting.`, said as `wait-held` when the want is
held and `wait-ended` (ran, released, replaced) when it moves, with the step `placing`; `assigner_waits` reads
them and `assigner_release` releases them as it does the acting waits. No new mechanism.
- **A-260** (§298, ADR 0633 §1, A-181; principles 1 and 7) For §298's third edge the trunk's head is the highest
source revision (`Src`) among the trunk branch's module values the assigner holds; the wait ends when a
`take-in.taken` of the trunk branch with `source` at or above it is heard. The assigner consumes
`mesh-delivery-indexer.take-in.taken` (a `consumes` entry), heard as it hears `build.ended`; a wait still held
after a restart is said again as `wait-held` and ends at the next such take-in.
- **A-261** (A-252, A-234; principles 2 and 3) A composition request's `Seats` carry, for each (seat, digest) of the
step's catalog revision, the definition whose digest it is: from the revision's `definitions` once A-234 fills
them, until then from mesh-catalog's `seat-definitions` where the trunk's digest matches; a digest neither holds
ends the step `could not run`, naming the seat and the digest.
- **A-262** (§496; principles 1 and 7) The assigner says a running composition step's progress once a minute with a
ticker inside the running step, stopped at its end, as mesh-delivery-sdk's `steprun` says its own beat (pull.go);
no bound key and no new key form.
- **A-263** (§712, §676; principles 7 and 8) §676 writes `package` (the Go module its `go.mod` names) for every Go
artifact, a `bundle` too, since a bundle is built from that Go module; §712 reads it there.
- **A-264** (§677, §697, A-186; principle 7) The event `gated-ended`, the verb `approvals` and the by-caller form of
a marked verb are added in mesh-sdk's `seatdef.Resolve`, which the seat generator and every reader of a definition
(the seat list, the grants part, the validator, `AnswerSeat`) go through: one place, one definition for all; the
seat files stay as written.
- **A-265** (A-264, design 46 approval mark §20; principles 4 and 7) A-264 holds for every marked definition. Where a
tool bundle holds the seat (the login shell's `execute`), the tool runner, which already holds that bundle's gate,
answers the marked verb by caller and serves `approvals` for the seats its bundles hold, as `AnswerSeat` does for a
process; no holder changes and no mark moves.
- **A-266** (A-264, A-265, ADR 0132; principle 7) `approvals` is the gate's verb, served wherever a marked verb is:
`CanHold` and the validator's same rule ask no holder to name it in its claim's `serves` or its tools.
- **A-267** (§496, §481, §22, A-170; principle 7) The composition step's `step-progress` carries `asker` `runner`, the
one asker that sends the accepted kind `step` on `mesh-wants`; the asker rename after join 11 (A-163, A-170) reads
it from the accept subject's asker token instead.
- **A-268** (corrects A-263; §712, §676, §491; principle 7) §676's `package` stays for package artifacts only, since a
bundle's Go module named as a package collides under §491's package names when two modules share one `go.mod`.
§712's job names the indexer entry's repository, `path` and the revision's head, and the program's directory;
`mesh-build-go` reads the Go module from the `go.mod` it builds there, as it does for every build.
- **A-269** (§509, §714; principles 7 and 8) A `pushed` message's `head` stands where a `merged` message's `merge`
stands: pending refs, the newest load and the revision's head key on it alike.
- **A-270** (A-255, §677; principles 3 and 7) A holder's approvals state is declared with `key-ttl` beside §677's
48 hours and 64 MiB, since its bound keys `unanswered.<call>` need it; the validator refuses a holder of a marked
verb whose approvals state lacks it.
- **A-271** (A-265, §677; principle 7) The tool runner's `approvals` answer has the form of `AnswerSeat`'s, read from
the runner's own record of the calls its gate holds; it keeps no `call.` keys of its own for it.
- **A-272** (§677, ADR 0596; principle 1) The `GateChannel` over `process.Conn` asks and reads an ask only through
mesh-sdk's `operatorchannelclient` on the seat `operator-channel`, never through the router's bucket.
- **A-273** (corrects A-272; §700, A-74, A-89; principles 1 and 7) The `GateChannel` over `process.Conn` is §700's:
`operatorchannelclient.Client` given, as its `Record`, a get of the key under `<module>.` in the seat
`operator-channel`'s state `asks` (the bucket formed from the seat's holder on the seat list and `StateAsks`, A-74)
on the process's own connection; the verb `asks` stays as it answers. The ask is published by a new `go/process`
method that publishes on a seat's accept subject (`mesh.seat.<seat>.accept.<queue>.<caller>`), on the a3g line,
as `Emit` publishes an event.
- **A-274** (§529, A-13, A-30, §584, §627; principles 1 and 7) The held finding writer takes its subjects from the
part's own binding: when the process was handed a binding of the local name `finding-events` (an `events`
template its manifest requires), it publishes under that binding's subjects
(`mesh.bus.<module>.finding-events.<event>.<node>`); with no such binding it publishes on the old stream
(`mesh.stream.<module>.event.<event>.<node>`). The module passes no subjects (A-13 holds) and the SDK keeps no
foundation list.
- **A-275** (§558, A-102; principles 3 and 7) The node-engine's self-composition reads a manifest's `invokes` with
mesh-sdk's `catalogue.readInvokes`, which reads the short and the long form alike; no part keeps its own reader of a
manifest field, and a form it cannot read is refused, never taken as no seats.
- **A-276** (A-218, A-206; principles 3 and 7) A `pipeline-waits` finding names the awaited runner fingerprint in
one evidence entry `awaited <fp>`, written when the finding is raised; the runner clears exactly the findings whose
entry names the fingerprint its node-engine says `running` healthy. Its summary is never parsed.
- **A-277** (§677, A-270, ADR 0595; principles 3 and 7) A state's `max-mib` is an option of the template `state`
(the second reader release reads it from 1 to 8192, and since mesh-delivery #206 the grants part carries it onto the
bucket). The manifest validator refuses an `approvals` state that lacks `key-ttl`, whose `ttl-seconds` is not 172800, or whose
`max-mib` is above 64; with none declared the bucket takes the default 64 MiB.
- **A-278** (A-179, A-186; principle 7, and code shared between modules lives in mesh-sdk) The seat-definition
function `AnswerSeat` takes is written once, in mesh-sdk's `seatlist` (the definition of a seat from the list a part
follows; none until the list is read, none for a seat it does not hold), and every holder passes it. A part that
serves a seat verb follows the seat list with its grant, as the pipeline runner and mesh-recovery now do.
- **A-279** (A-236, §715, §716; principles 1, 3 and 7; corrected four times, this form stands) A group member's `base`
is its pull request's base branch as the seat `git` says it in `pull-updated`, kept by the gate on the member's
delivery in its state `deliveries` from the announcement, and copied onto the record's member when the take-in
opens the record. Its earlier forms (the base on the `merges` entry the Approve makes) are dropped: from G no
Approve makes an entry, and the gate writes `merges` no more (§156). Design line §716 says it whole: the event, the
states and their keys, who deletes them, and how `groups` answers.
- **A-280** (A-274, §529; principles 1 and 7) A tool bundle's finding writer (`views.NewStdioHeldWriter`) changes
nothing: the tool runner already publishes an emitted event on the subject of the first stream of the module's
membership that carries it (`node-tools` `bus.eventSubject`). The grants part lists a module's required
`finding-events` stream in its membership with that subject and the finding events, so a bundle's findings land
under its binding as a process's do; with no such stream they stay on EVENTS.
- **A-281** (§156, §716; principles 2 and 7) The switch runs once, as §156 says: after it has carried today's state in,
the gate writes the key `switch.done` (the gate's fingerprint and the time) in the state that holds its records, and a
later start that finds it runs no switch, so the deliveries §716 writes never become records at a start. A revert's
restore brings back the state from before G without the key, so delivering G again switches again, still safe to run
twice by the record's key.
- **A-282** (A-115, §544, issue 1930; principles 3 and 7; corrected once) An ask's step bound key is never deleted
to be made again. `stepBounds.Arm` creates it if absent; when the step moves, the waiter writes it again by an update
against the revision it read, so the bound still counts from the step's last movement (issue 1930); a waiter whose
update loses to another's reads the key again and takes the newer bound as armed.
- **A-283** (A-277, ADR 0595; principle 7) The holder of the seat `mesh-broker` makes a template `state` bucket with
the `max-mib` its requirement declares, the default cap where none is declared, as the grants part does for a
state section; one cap rule for every state bucket.
- **A-284** (ADR 0573 §2, ADR 0500 §5; principles 5 and 7) In a group's check, a TypeScript member is built
against the group's own heads, as a Go member is: the check packs every `@novox/mesh-*` package a member's
merge changes (the SDK's `lib` artifact given its path, and a member's new packages such as another member's
contracts) by the form ADR 0500 §5 gives the SDK, and serves them to every member that imports them; the
latest published version is used only for a package the group does not change.
- **A-285** (A-284; principles 7 and 8) The fetch of a build step carries the group's packages as one list,
`group-packages` (beside `packages`, which stays the Go mesh packages' versions), each a package of a member at
its commit with `path`, its directory in that member (empty for the member's root); the single `package` field
and its tagged form go. The build module packs each and places it by
its `package.json` name in every member that names it, in place of the registry's. The SDK's `lib` artifact names
its paths (`package.json`, `package-lock.json`, `tsconfig.json`, `src`) in mesh-sdk's manifest, so the indexer
holds them and a change to them is a changed package.
- **A-286** (issue 1921; principle 3) The checker previews and judges every member whose fetch merged, whatever its
later steps ended with; only a member whose head does not merge, or whose fetch did not answer, stays
unpreviewed, and the check says which of the two. A check step that errors after its fetch merged (a level that
does not compile or generate) ends failed with its answer all the same: its fetch and the levels so far, beside
its error, so the checker previews from it. A failed test or a failed level never hides the manifest validator or
the composition.
- **A-287** (principle 4) A module's kept announcement names every state its manifest declares, so each can be
read by its tools; gitea's `KEPT` gains `failed-steps`, `idle` and `transfers`.
- **A-288** (principle 3) A refusal's test holds what the refusal must say, not its earlier wording: the one tool
that may do the refused act and that it runs on the operator's Approve of that call.
- **A-289** (§371; principles 3 and 8) A package left unpublished by a build that reserved no version (§371's other
watched branch, §505's manifest without MAJOR.MINOR, a candidate) counts as done, and the builder's unpublished
count holds no package at all: a reserving build's package is published or its build fails, and a done key is never
built again under a reservation, so no seat republishes a package (principle 7). The count holds images only.
- **A-290** (A-254, §467; principles 3 and 7) A step's key in its build module's steps state keeps the step's answer
only within the state's bound per value; an answer over it, a check's or a run's alike, is kept as the object
`output.<ask>` in the module's outputs store and the key names it, and every reader (the checker, the asker) reads
the answer from the key or the object it names. No step ends passed in one place and failed in another.
- **A-291** (A-254, A-290; principle 7, and code shared between modules lives in the SDK) A process receives a seat
verb's chunked answer through one helper of mesh-sdk's process package, which subscribes to the call's chunks
before it asks and answers the whole body; the checker, the indexer, the assigner and the recovery keeper (whose
own receive moves into it) read a step's answer kept as an object through it.
- **A-292** (§590, issue 1975; principles 1 and 3) The node-engine's first statement keeps the bootstrapped seats (no
'not yet'); every later change is said as `manifest-reads-changed` at once. A say the bus does not take is said
again within the same link through the SDK's bus retry until the bus takes it, and each failed attempt is written
to the engine's journal with the event's name; the held-for-the-next-link form stays only for a link that is lost.
- **A-293** (§421, §628, ADR 0593 §6, issue 1966 U1/U8; principles 1 and 3) In by-entries mode the node-engine still
says a placed module's newest build that ended failed as a refused key with the build's words: it hears the
builder's build-ended event for that fingerprint (an event, not a read of the builder's builds), so no reader waits
on a build that will never be done; a queued or running build stays unpublished. A placed module's newest build is
the last trunk build-ended event (one naming no branch) the engine heard for that module, from the builder's
build-ended events read whole at start and then followed, as the assigner hears them (A-181, §556): a failed end
writes the refused key `<node>.<module>.<fp>` with the event's words, a later done end of the module (at any
fingerprint) deletes the refused key such an end wrote, so the state never says refused for a done build, and an
end naming a branch moves nothing.
- **A-294** (corrects A-267; §600, A-170; principle 7, the seat definition is the one source) The seats mesh-wants and
mesh-builds are not build seats: their accepted kind `step` stays without a token (not `by-caller`), and the
composition step's step-progress names its asker as the one caller its seat's definition lists, read from the seat
list; only the three build seats carry the asking seat's name as a token.
- **A-295** (A-210; principles 1 and 7, one shared mechanism in the SDK) A tool's callers list names a seat as the entry
`seat:<seat>`, resolved in mesh-sdk's verb callers by a holder function the part is given; that function follows
the assigner's seat-holders view (as the watcher does) and answers from what it last heard, never one call per
checked call; each part's list becomes the operator, `seat:node-tool-runner` and `seat:mesh-healers`.
- **A-296** (A-210, A-131; principles 1 and 2) Where A-210 makes a part find another by the seat it holds, the part's
manifest takes the seat forms in the same change (`seat:<seat>.views.<state>` for a consume, the seat's verb for an
invoke), and a part names its own buckets from its credential's module, never from a constant. Which seat a module
that holds none (the delivery view) or several (the assigner) is found by is a design line (§717).
- **A-297** (A-232, A-111; principle 7, one form per fact) The pipeline runner's step ask id takes the usual ask id
form: base 36 of the pipeline's start time in milliseconds, then six hex digits of the sha256 of the pipeline, the
artifact and the step, so the same placement makes the same id at every start (and the bus dedupes on it); a rerun
uses its own start time.
- **A-298** (§717, §614, §616, A-295; principle 7, one shared mechanism) The claimant of a seat is read from the
catalog half in one place, the catalog seat's client (`Branches.Claimant(seat)`), and one follower there follows
the claimant's seat-holders view, drops and follows again on each catalog change, and answers the SDK's holder
function; every part calls it once and FollowSeat builds on it. The assigner answers its own holder function from
its own seat-holders picture. The seat-holders first-view tool names its callers in A-131's module form for now
(the teller holds no seat to be named by); naming them by seat waits for every follower to hold one.
- **A-299** (§717, corrects its count; principles 3 and 7) The seat mesh-delivery-view's state `answers` takes one key
form per kind of mesh-wants' `asks` forms with `.view` after it (`hold.<module>.<fingerprint>.view`,
`restore.<node>.<module>.<fingerprint>.view`, and so for each), so its generated reader reads every real key; the
kinds are the seven the asks package writes (hold, restore, backup, record, outside, revert, assignments), not eight.
- **A-300** (§717, A-131, A-210; principles 4 and 7) The status view's process follows the wants by its seat-form
first view; its tools program keeps the invoke `assigner_read_wants`, because the view's tools read a want by its
key and its history (`view_tiers`' since of a want, `assigner_want`), which a first-view tool (pages after a
position) does not answer; `assigner_read_asks` and `assigner_read_assignments` go as §717 says.
- **A-301** (§717, A-131; principle 7, one form per seat view) The status view's first-view tool `answers` is mesh-sdk's
first-view tool (the one with a callers list, as the assigner's `asks` and `wants` are), its callers the teller in
A-131's form; §717's "views.ReadTool" reads as this.
- **A-302** (§717, A-298; principles 1 and 7) The indexer follows the assignments through FollowSeat as well, the
claimant of mesh-module-assigner read from its own catalog (it holds the catalog seat), so buildrun's 5-second
holder loop goes for every part.
- **A-303** (§717, A-302; principles 2 and 7) The status view names its followed pictures in the seat form
(`<seat>_<state>`), the reach's watches (`reachBuckets`, its history watch included) too, since they match the names
the followed pictures are told; where a name must be the holder's real bucket (only the watchdogs' stuck key, which
matches what the watchman writes), the view takes it at run time from the holder FollowSeat follows, through one
accessor of the SDK's process reader; no code names the assigner's module. *Corrected 2026-11-12: it first named the
reach history's bucket names as real buckets too, for kept entries' sake; the view's kept reach holds no bucket name.*
- **A-304** (ADR 0525 §4, A-284, A-285; principles 7 and 1; the operator's yes, 2026-11-12) The checker holds no
step: within each of its two phases (every module that is not TypeScript, then the TypeScript modules after
`groupPackages`) it puts every ask of the phase on the check stream at once and then waits for all of them, so the
build nodes pull them at their own capacity; a declined ask is put again as today, and the phase's verdict is judged
in the same order as before, whatever order the asks end in.
- **A-305** (ADR 0227 rule 8, ADR 0240 rule 2; principle 7) The pipeline runner's process declares the health `unit`,
as every other process of mesh-delivery does; `runner_health` stays a tool and judges nothing for the node-engine.
- **A-306** (§626, §419; principle 7) A Go module that G creates has no count at a commit of main, so §626 does not
hold it; the test budget (at most 50 integration and 500 unit tests) does. A test added to a module that exists on
main counts under §626, a contract's generated `TestFixtures` included.
- **A-307** (corrects A-239; §635; principle 7) The node-engine's manifest gains no `uses`: its `invokes` of
`seat:mesh-grants.withheld` gives the served-verb edge, as §635 says; A-239's last clause (`uses` gains
`mesh-grants`) is withdrawn, and the rest of A-239 holds.
- **A-308** (§628, §421, ADR 0596 §2; principle 1) §628's list of the indexer's states the node-engine still reads is
whole: `candidates`, `manifests` and `verdicts`, for compose-candidate alone. Its watch of the indexer's `modules`
(selfcompose shadow.go) goes; what it took from a module's record it reads from the catalog half's entry the want
names (§421).
- **A-309** (A-290, A-254, research 071; principles 7 and 2) A build module's `outputs` objects live as long as the
step key that names them: the store's age is the steps state's time to live (two days), its cap 256 MiB, and the
bus's own age removes an object; nothing else deletes one. The milestone's lifetime list gains this line.
- **A-310** (§615, §506; principles 7 and 3) A compose request's manifest named by digest also names the fingerprint
whose catalog graph entry holds it: `composer.Manifest` gains `fingerprint` beside `digest`, and the checker, the one
part that sends digests, fills it (a pull's `Fps`, else the trunk revision's entry of that module). The Go build module
reads the entry by `seat:mesh-catalog.entry` with that fingerprint, takes its manifest whole, and refuses the request,
saying which module, when the entry's `manifest-digest` differs or a digest comes without a fingerprint. A manifest
sent whole (`raw`, the assigner and the indexer) is read as today.
- **A-311** (design 34 every-event §19; principle 7) The tool runner's mesh_wait keeps its floor of one second; its test runs the
independent one-second rows as parallel subtests, and no clock hook is added to `bus.Conn` or the wait.
- **A-312** (§421, §628, ADR 0596 §2; principles 1 and 3) For a module an assignment candidate adds to a node that has
no want for it there, the node-engine's shadow asks `seat:mesh-catalog.module` for the trunk's fingerprint when it
composes that candidate, then takes that fingerprint's entry by `seat:mesh-catalog.entry`; a call, no watch, no
picture of the catalog. Its manifest invokes `seat:mesh-catalog.module`; a module the answer gives no fingerprint is
held, the verb's reason named.
- **A-310's shape:** `composer.Manifest` is `{Raw, Digest, Fingerprint}` (wire `fingerprint`), and `composer.Fill`'s
getter takes the `Manifest`.
- **A-313** (corrects A-309 and A-290's "outputs store"; §58, §547, ADR 0666 §16; principle 7) A step answer over the
steps state's bound is kept in the steps state's own objects store, `steps-objects`, which the build module's
`requires` sizes with the state option `objects-max-mib: 256`; it lives as every `<state>-objects` does (removed on
replace, an object whose key is gone kept for the drop tool), so no `outputs` require and no age option for the
objects template are added. A-309's lifetime line is withdrawn.
- **A-314** (A-313, §58, §547; principle 7) The three build modules' `steps` moves from the old `state` section into
`requires` as a state with `ttl-seconds: 172800` and `objects-max-mib: 256`; its bucket stays `<module>_steps`, so
the seat's `steps` state is served from it as before. The step runner names the store through
`catalogue.StateObjectsName("steps")`, and the manifest validator accepts `objects-max-mib` on the state template.
- **A-315** (test 36, §637, §555, §405; principles 2, 3, 5 and 7) Test 36 takes the fingerprints of before G from the
mesh, not from a file: during G's check the trunk is before G, so it asks `seat:mesh-catalog.module` for the
indexer, the assigner, the gate, the backup holder and the bus module, and fetches each build by its fingerprint
through `seat:oci-artifact-registry` under the checker's account, as §637 says; the address of both is the bus the
check gives the test. Each fetched build is started the way every module is, by the node-engine's own apply of a
want on the test's bus, under the module's own account; the test starts no bare process. Where the seat does not
answer, the test fails, saying which build it could not fetch and why; it never skips.
- **A-316** (§379, §421, §628, A-312; principles 2 and 7) A node-engine composes a candidate only from what its asker
gives it, as it composes a want. The assigner's candidate `<proposal>.<node>` gains `added`: by module, for each
module the candidate adds to that node without a want there, the `version` and `parts` the want it would write
carries, in the same wire forms (the seat format reuses nothing across repositories, so the assigner's seat
defines the two properties itself and a fixture holds them to the want's), made by the same code that writes wants. The node-engine's shadow builds such a module from that
entry and its `added` parts; a module with artifacts the candidate gives no parts is held, saying so. Nothing reads
the builder's states or asks the registry for it.
- **A-317** (A-316; principles 1 and 7) A proposal's candidates have one writer, the assigner's main process (the
passer), which holds the heard builds and released versions: on hearing a proposal it writes each node's candidate
with `added` filled, as `layAgain` does. The verbs' tool process writes the proposal alone and no candidate, so no
node-engine sees a candidate before its parts. If a verb's answer needs the candidates, it waits on the passer's write
(an event or the state's watch), never a timer.
- **A-318** (A-210, A-296, §428, §717; principle 7, and a milestone's scope is fixed at its start) A part reaches
another module's state or verb in the seat form wherever that module's seat declares it (A-210). Where no seat
declares it, or where a process has no seat-form follower for a raw state (the SDK's FollowSeat follows views
only), the read keeps its module form in G; giving those seats the states and verbs, and processes a seat-form state
follower, is the work after G this list names. The provisioner's `provisions` is declared by its seat, so it is read
in the seat form (an earlier draft of this answer listed it wrongly). In mesh-delivery at 81cb2d7a:
- the builder (no seat state or verb): builds/builds.go:50 `BuilderModule`, builds/log.go:21 `BuildLogRead`
(`build-log`), builds/versions.go:14 `BuilderVersionsAddress` (`builder_versions`); view-tools main.go:477, :486;
view main.go:180 and view.go:692, checker main.go:279, grants buscompose/state.go:361 (`BuildsBucket(BuilderModule)`);
indexer main.go:515; the view manifest's `mesh-delivery-builder.builds` and `.build-log` reads;
- the grants part's composition inputs: buscompose/names.go:12-16 and their buckets, inputs.go:271 (`nats`),
buildbacklog.go:13;
- the view process's raw states (no seat-form follower): `node-engine_have`, `mesh-delivery-watchman_presence` and
`_stuck` (main.go:180, :285-287, internal/view/names.go) and view-tools main.go:477, :482 that share those reads;
the indexer's `candidates` and `taken` (`mesh-delivery-indexer.views.*`; `taken` undeclared, no state of
mesh-catalog is a view); the teller's `answers` (the teller holds no seat); the gate's and the watchman's states;
- mesh-enrolment main.go:148, :149, :178 (the assigner's `assignments` view and `assigner_read_assignments`; a stdio
module's views reader has no FollowSeat);
- the indexer's tools `indexer_read_*` and `indexer_pending` (the seat serves no such verb): the manifests' invokes
name the module, the code takes the module from the seat's holder.
- **A-319** (§698, A-312; principles 1 and 7) The node-engine's `entry-again` asks again every entry cache the
node-engine keeps, its own composer's and its shadow's, so an entry once answered not-found in the shadow is taken in
on the same act; the shadow re-answers its candidates on that answer. No cache is polled.
- **A-320** (the operator, 2026-11-12: "an integration test is not an e2e test, it should still mock all external
dependencies, but test the internal wiring"; withdraws §637 and A-315) The check runs each module's unit and
integration tests and nothing more. Test 36 is proven in two parts: each module's integration test proves its own
wiring of the undo with every other module a stand-in (the node-engine's existing test at
`cmd/mesh-host/recovery_integration_test.go` is that form), and the whole undo end to end belongs to the test lab (the
operator, 2026-11-12: paused until its performance is fixed), not to a check and not to a live node. No check
fetches or starts real builds.
- **A-321** (A-210, A-303; principles 3 and 7) The watchman awaits a followed state by its seat and state
(`mesh-wants`, `wants`) and resolves the bucket name at each use through the process reader's holder accessor; a start
never waits for a holder. A seeded pulse, stuck key or finding of a bucket not yet resolved is kept, and closed only
after the late read of the resolved bucket.
- **A-322** (A-298, A-321; principle 7) A message on a process's event hub has one taker. The watchman replaces its
catch-all of seat events by one handler per seat it consumes; for the seat `mesh-catalog` it takes each event it
consumes except `registration-changed`, which the branches follower takes, and whatever the watchman needs of that event
it takes from the follower. The SDK's hub keeps one taker per message.
@@ -0,0 +1,374 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0666"
- "ADR 0656"
- "ADR 0648"
- "ADR 0645"
- "ADR 0644"
- "ADR 0641"
- "ADR 0639"
- "ADR 0636"
- "ADR 0633"
- "ADR 0621"
- "ADR 0619"
- "ADR 0618"
- "ADR 0615"
- "ADR 0614"
- "ADR 0603"
- "ADR 0597"
- "ADR 0596"
- "ADR 0595"
- "ADR 0571"
- "ADR 0567"
- "ADR 0564"
- "ADR 0548"
- "ADR 0493"
- "ADR 0441"
- "ADR 0437"
- "ADR 0421"
- "ADR 0363"
---
# 47, milestone G: the delivery record, what the gate owes the forge, the state table, the two graphs and a change's graph difference
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
## The delivery record, and what the gate owes the forge
### The delivery record
*ADR 0656; ADR 0595, ADR 0596, ADR 0597, ADR 0603 §7, ADR 0493 §3, §11, ADR 0633 §4, §5, ADR 0441 §1, ADR 0548 §8,
ADR 0564 §2, ADR 0363 §2.*
§141 From research 085's milestone the gate, holder of the seat `mesh-delivery`, keeps one **delivery record** per
milestone's delivery, a ledger instance (ADR 0595's template `ledger`, ADR 0597) it alone writes (ADR 0656). It
holds:
- §142 **the group's state**: the heads, the verdicts, the checks and the asks, which `merges` holds today; from milestone
G's delivery the pipelines' `step-started`, `step-ended` and `pipeline-ended` in place of the verdicts, the checks and
the asks, kept as long as the record (ADR 0666 §9);
- §143 **the trace**: the milestone, its issues, its pull requests, their commits, and the graph revisions the delivery
moved between;
- §144 **what happened to each entry on each node**: for every entry of the graph difference, taken in, built, checked,
composed, wanted, running healthy, held (with the hold's reason, so a reader of the record sees why an entry stands
still without asking its holder), failed (with what failed), or away (ADR 0656).
§145 It holds no tiers, and only the gate orders work by it, so it is no second copy of the keys, the objection research
063 had to a stored plan (research 085, Findings 10). Its fields, retention and tools are this design's choices under
ADR 0656.
- §146 **The outcomes arrive as events, through a durable consumer of the gate's own.** The gate's manifest `consumes`
each step event of *What each step says* (below), and `wait-held` and `wait-ended` of each holder. The consumer
resumes from the last event applied; an event applied twice changes nothing; an event is applied only when it comes
from its owner's account; and running healthy is taken from the node-engine seat's `running` event, never from a
have read across the seam (ADR 0596).
- §147 **It ends when every present node runs its entries**, and the gate says `record-ended` on the seat `mesh-delivery`
with the record's milestone and its nodes away. It names the nodes away, each with since when, and ends without
waiting for them, as a node away holds nothing (ADR 0603 §7).
- §148 **A record open too long is a finding.** (§502) The watchman writes the finding `record-open` for a record still open 24
hours after its merge (a delivery of the whole mesh takes under an hour, so a day means something stands still) and
clears it on `record-ended`; the condition keeper raises it (ADR 0564 §2). The finding names the entries still held
or failed, read from the record's own events. It is written only while no other condition covers the record: while
an entry of it is held by a wait that raised `acting-part-late`, or a store that holds it raised `full`, that
condition already tells the operator why the record is open, and a second one for the same cause would only be noise.
- §149 **A node away that wakes adds its outcomes.** When it wakes and catches up by its wants (*A change's graph
difference is its delivery*, below), its outcomes reach the record through the same consumer as any other, and the
record shows the node no longer away.
- §150 **An Approve given holds at its heads, until G's delivery.** An Approve the operator gave for a group's heads holds while those heads
stand: a check of the same heads asked again, after a main move or for any other reason, records its verdict in
the record and asks the operator nothing again (issue 1972). The Approve is withdrawn only as ADR 0441 §1 has it,
when a member stops being mergeable, and a carried verdict is read as current (ADR 0548 §8). From G's delivery no
Approve is asked per merge; an `approval` step's answer is the runner's, kept in the record as its step's end
(ADR 0666 §10).
- §151 **From G's delivery, a record for every landed merge.** A session merges through the seat `git` and the gate merges
nothing, so every merge is one the gate did not make: the gate opens a record for each source revision of a watched
branch the indexer takes in, a group's members in one, and `forge-made` no longer marks a record apart. A merge its
author names a recovery is marked `recovery` (*A recovery fix is an ordinary merge*).
- §152 **A small fix has a record of its own.** A small fix is a milestone of one issue (ADR 0493 §3), so it has a
delivery record like any milestone; nothing is merged outside a released milestone (ADR 0493 §11).
- §153 **It pins the graph revisions it points at.** The gate says, as the seat `mesh-delivery`'s events `pinned` and
`freed`, which catalog and assignment graph revisions a record starts and stops pointing at, and as
`heads-replaced` that a candidate's heads were replaced or its pull request closed. Only the gate's account may
publish them (ADR 0363 §2). The indexer and the assigner hear them through a durable consumer each, declared in
their manifests' `consumes`, which resumes from the last event applied after a restart, so a `freed` said while one
of them was down is still applied; neither reads the gate's state (ADR 0596; *Two graphs, one source of truth*,
below).
- §154 **A record is kept for 90 days by default**, the gate's setting `record-retention`. Ninety days covers every trace
the operator has asked of a past delivery so far, which went back weeks, never months; the setting can keep records
longer. When the retention removes a record, the gate says `freed` for each revision it pinned.
- §155 **Its size, its cap and its growth.** A record's state of 30 modules on four nodes is about 30 KiB, against the
bus's 256 KiB value limit (research 085, Findings 8). The ledger keeps every operation beside that state: 30 modules
on four nodes with seven outcomes each is about 840 outcome operations a record, at about 0.2 KiB each about 170 KiB,
so a record costs about 200 KiB with its snapshot. At an estimated 20 deliveries a day (a guess from the days of
research 085, not a measurement) the ledger grows about 4 MiB a day, about 360 MiB at the default retention; its cap
is 768 MiB, twice that, so a busier month than estimated still fits. Removing a record at its retention removes its
operations and its snapshot with it, so the ledger's size follows the retention, not the mesh's age.
- §156 **The switch carries today's state in, once.** At the new gate's start, `merges` and every group in flight become
records, and from then on the gate writes `merges` no more; `turns`, which nothing writes while the check gates no
merge, is dropped with the hold (*Nothing merges into a repository under check*). The switch is safe to run
twice: a group in flight becomes a record keyed by its milestone and heads, and a switch that finds that record
already there leaves it as it is, so delivering milestone G again after a revert makes no second record and asks
nothing again. A revert of the gate is the delivery system's restore of its kept data (*A module's kept data on the
bus, backed up and restored with its delivery*).
- §157 **The forge bridge asks the seat for a group.** It reads a group's members from the seat `mesh-delivery`'s verb
`groups`, never the gate's `merges` state, both to set the group on each merged message and to publish a group's
end again (ADR 0656, which changes ADR 0633 §4 and §5; ADR 0596). Its account holds no grant to read `merges`.
- §158 **What the gate owes the forge is said, not watched, and a merge made at the forge has a record too** (*What the
gate owes the forge*, below).
- §159 **Its tools**, against the four lines a module's tools answer:
| Line | The gate |
|---|---|
| read everything it holds | `gate_record` (one record whole: the group's state, the trace, each entry's outcome per node, the nodes away), `gate_records` (every record with its fill against the value limit, and the ledger's fill against its cap), `gate_consumer` (its durable consumer: the last event applied per family, the events waiting, the age of the oldest) |
| do by hand what it does itself | `gate_record_repair` (for one record, asks each owner for the outcome of each entry missing one through that owner's read verb: the indexer's `take-in` read, the builder's build read, the checker seat's check read (from G's delivery the runner's `show`), the seat `mesh-wants`'s `entry`, and the node-engine seat's verb `composition` of that node; applies the answers as the consumer would) |
| recover without help | `gate_record_end` (end a record left open, a node gone for good or an entry that never runs, with the reason, recorded in the hand-act log of the seat `mesh-hand-acts`) |
| one seat, many tools | the seat `mesh-delivery`'s verbs, with `groups` answering a group's members and the acts said for it |
- §160 **How it is checked.** In the gate's whole-module test: the gate stopped and restarted in the middle of a delivery
leaves the record complete with no outcome counted twice; the events `pinned`, `freed`, `heads-replaced` and
`record-ended` are said; a `freed` from an account other than the gate's is refused by the bus; a group approved
before the switch is not asked again after it, nor after milestone G is delivered again following a revert; a record ends once every present node runs its
entries, naming the nodes away; a held and a failed entry are kept with their reason; a record open past 24 hours
raises `record-open`, cleared by `record-ended`; a node away that wakes adds its outcomes and is no longer shown
away; a group's heads approved and checked again ask the operator nothing again; a small fix's merge gets a record
of its own; a record past its retention is removed and frees its revisions; the tools read a record, list the
records with their fill, and a repair completes a record missing an outcome through the owners' read verbs. In the
forge bridge's whole-module test, a group's members are read from `groups` and its end published again from it, and
a read of `merges` by its account is refused. Live: one call of `gate_record` traces an issue of the registry
milestone to each node's running build, also after six later merges; a node away is named with since when and its
outcome taken on waking; a record of 30 modules on four nodes stays under 256 KiB; a group checked again at old
heads after a main move is not asked of the operator again (issue 1972).
### What the gate owes the forge
*ADR 0596 §1, §2, ADR 0656 §7, ADR 0437 §8, ADR 0441 §5, ADR 0633 §1, §4, ADR 0363 §2, ADR 0564 §2; from research 085's
milestone, this design's choices under ADR 0596 and ADR 0656 §7.*
§161 The forge bridge today performs what the gate's states `merges` and `owed` ask (a merge, a status, the delivery's
view, a comment, a line on the commit's note) by watching those keys (ADR 0437 §8), and the forge module writes the
checker's `mesh/check` from a watch of the checker's state `owed`: reads of another module's bus state that ADR 0596
§2 ends when their owner moves. From research 085's milestone:
- §162 **Every act is an event, under one id.** The gate says each act it owes as the event `owed` of the seat
`mesh-delivery`, on `mesh.seat.mesh-delivery.event.owed.<repository>.<number>`, carrying the act, the pull request,
the head and an id; the checker says its status the same way, as the event `owed` of the seat
`mesh-delivery-checker` on `mesh.seat.mesh-delivery-checker.event.owed.<repository>.<number>`. Only the owner's
account publishes each (ADR 0363 §2). The subject per repository and pull request lets the bridge, a person and a
test read one pull request's acts alone. The seat events are kept on each owner's stream `owed`, 7 days and at most
64 MiB (an estimated 20 deliveries a day, with about 15 acts of about 1 KiB each, is about 2 MiB over 7 days).
- §163 **Every act is safe to repeat at the forge.** A status and the delivery's view replace what stands; a merge of a
pull request already merged at that head is answered performed; a comment and a line of the note carry the act's
id, and the seat `git`'s verb skips an id the pull request or note already holds. This is chosen over recording the
id before the act: an id recorded and then not performed (the bridge stopped between the two) would be lost, while
an act repeated changes nothing. The bridge's own state `performed`, a key-value state in the new form (ADR 0595),
form 1, one key per id with the outcome and when, kept 7 days as the stream is, its size the one its template fixes
(ADR 0595 §3), is therefore a
saving and a read, never what keeps an act from happening twice.
- §164 **Every outcome is said, and the gate records it.** The bridge says each act's outcome on its own stream `forge` as
the event `owed.ended`, with the id and one of `performed`, `refused` (the forge's own words), `could not be
performed` (the forge did not answer within the call's bound, with the error) or `undecodable` (an act it cannot
read, with why): an act it cannot decode is said, never only logged. The gate hears the stream `forge` through its
durable consumer, writes the outcome into the delivery record, and says an act's id is answered only once an
outcome is recorded. The checker does the same for its own acts: it hears `owed.ended` for its own ids, records each
outcome in its own state (it watches only its own state), says again at its start, under the same id, every act of
its own still unanswered, and lists one unanswered past 15 minutes in its own stalled read.
- §165 **What is unanswered is said again, and stalls by a bound.** At its start the gate says again, under the same id,
every act its records hold unanswered. An act unanswered 15 minutes after it was said is listed by `stalled`, which
the condition keeper raises as the stalled line (ADR 0421 §7, ADR 0564 §2): fifteen minutes is many times a
forge call's time, and short beside the check's hour.
- §166 **A group's merges follow one another, until G's delivery.** The gate says a group's next `merge-pull` only once the previous member's
merged message is heard (ADR 0441 §5), so a member the forge refuses holds the rest, and none is merged ahead.
- §167 **From G's delivery the gate owes no merge, and the checker nothing.** (§510) A session calls `merge-pull` itself (ADR 0666
§11), and a group's members land one by one, the group ending when every member has (*Groups*); the checker's `owed`
goes with the checker, and the runner owes the forge nothing, a pipeline's result living on the bus. The gate still
owes the delivery's view, a comment and the note's line, performed by the forge bridge as above.
- §168 **The bridge catches up from the seat.** At its start the bridge resumes its durable consumer and also reads
`groups`, which answers, per group, the acts the gate said with their ids and their recorded outcomes; an id said
and not answered is performed. Its tools: `bridge_owed` (the ids it performed with their outcomes, and its
consumer's backlog: the last event applied, the events waiting, the age of the oldest), `bridge_owed_again`
(perform one id again now) and `bridge_owed_drop` (drop one id with its reason, written in the hand-act log of the
seat `mesh-hand-acts`, and said as `owed.ended` with the outcome `dropped`).
- §169 **The switch carries what is owed.** (§542, §592) At the new gate's start (*The switch carries today's state in, once*) every key
of `owed` the old bridge has not performed becomes an act of its record, with an id made of the key and
its revision; the new bridge counts as performed every id whose key and revision the old bridge's own bookkeeping
names, so nothing is done twice and nothing dropped. A key of `merges` becomes no act: §542 says it as
`approval-carried`, and §592 has the whole merge path from G's delivery.
- §170 **A merge made at the forge has a record too, until G's delivery** (from it, §151 and §592). A merge the forge reports for which no record holds a `merge-pull`
of that pull request at that head (a person merged it in the forge's own page, as on 2026-11-07 and 11-09) is taken
in like any other merge (*Taken in by the indexer it names*), and the gate opens a delivery record for it, marked
`forge-made`, with no Approve, so its trace and its outcomes on the nodes are kept like every delivery's and no
later merge waits behind a merge the gate never heard. The gate says it as the delivery's `transition` with the why
`forge-made`, which the teller passes on to the operator (the operator, 1921's note of 2026-11-07: "a merge made in
the forge is taken in like any other").
- §171 **How it is checked.** In the forge bridge's whole-module test: an `owed` said while the bridge is stopped is
performed once after its restart; the bridge stopped between an act and its `owed.ended` performs it again on its
restart and the forge holds it once (the comment and the note by their id); an act said twice is performed once; an
undecodable act is said `undecodable`; `bridge_owed`, `bridge_owed_again` and `bridge_owed_drop` answer, the drop
writing the hand-act log; its account cannot read the gate's or the checker's state. In the gate's: an act left
unanswered is said again at the gate's start under its id and listed by `stalled` after 15 minutes; the merge path
of G's gate is checked as §592 says. Acceptance tests 53 and 51.
## The state table, from milestone G's delivery
§172 **From milestone G's delivery the gate keeps no merge states.** With no check before a merge and no Approve per merge
(ADR 0666 §10, §17), nothing is `proposed`, `ready`, `rejected` or `held` before a merge: a commit's states are its
pipeline's steps, kept by the runner (*Pipeline steps and the policy*). The table, the gate's `table`, `recheck` and
`close` and the one key per pull request go with the check; `stalled` lists the acts owed to the forge past their 15
minutes and the records `record-open` names.
## Two graphs, one source of truth
*ADR 0656, which supersedes ADR 0645 to ADR 0648; ADR 0595,
ADR 0596, ADR 0603 §2, §6, §7, ADR 0615 §1 to §6, ADR 0621 §1, §2, ADR 0614 §3, ADR 0567 §5, ADR 0571 §8, ADR 0614 §4, ADR 0618 §2, ADR 0619 §1 to §3, ADR 0636 §1,
§3, §4, §7, ADR 0639 §7, §8, ADR 0564 §2, ADR 0363 §2.*
§173 From research 085's milestone. One graph answered two questions, which modules exist and which version runs where,
and one module running as several placed versions would have given one entry two shapes and one key two writers; two
graphs answer them, the second computed from the first, so there is one source of truth (ADR 0656; research 085,
Findings 11). How each is kept, read and retained is this design's choice under ADR 0656.
```
manifests ──► indexer (mesh-catalog) ──► catalog graph revision ──┐
├──► assigner (mesh-wants) ──► assignment graph revision
placements (mesh-module-assigner) ───────────────────────────────┤ │ │
node capabilities (node-engine seat) ────────────────────────────┘ │ └─► wants, from a trunk revision only
▼
a change's assignment graph − the current one = its graph difference = its delivery
```
- §174 **The catalog graph is the indexer's.** The indexer, holder of the seat `mesh-catalog`, keeps the modules as their
manifests declare them; it is today's module graph, and *module graph* stays its other name (ADR 0656). At each
revision it holds one entry per module: its bases, its packages, the toolchain version and recipe version of the
build module that builds it as ADR 0603 §2 has them, its provides and requires, its invokes and serves, and its
fingerprint, with the rules the seats declare (*An acting part's new version acts first*, below).
- §175 **The assignment graph is the assigner's, computed and never written by hand.** The assigner, holder of the seats
`mesh-module-assigner` and `mesh-wants`, computes the module versions placed on nodes, with the edges between
versions, from the catalog graph, the placements and the nodes' capabilities, and nothing else writes it
(ADR 0656). Each entry is one module version on a node; its edges are provisioning, contribution, and the acting
parts' versions that are to act on it (ADR 0656), whose rules and versions come in with the catalog graph. It is
kept, but it is no stored plan: it has one writer, no part orders work by a stored plan, and any revision is computed
again from the inputs it names and compared with the one kept, by a tool (below), so a kept revision is a checked
derivation, never a second source of the kind research 063 removed (research 085, Findings 5).
- §176 **Each graph is kept per revision, and each revision names its inputs** (ADR 0656). The indexer keeps a catalog
graph revision for each source revision it takes in; the assigner keeps an assignment graph revision for each change
of its inputs, naming the catalog graph revision, the placements and the capability readings it was computed from.
Both parts are the foundation (ADR 0619 §1), so both keep their revisions as objects declared in an `objects`
section in the old form, each with a cap it declares there, never in the new form (ADR 0619 §2), and neither waits
on the bus module to start; the manifest validator refuses either manifest that declares them in the new form
(ADR 0619 §3). An assignment graph revision is announced by an event of the seat `mesh-wants` and served by its
verbs. This replaces ADR 0603 §2's one key per module with five past values as the graph's form (ADR 0656, which
changes ADR 0603 §2); the keys are written no more, and a revert of milestone G restores them from their backup
(*Reverted*, below).
- §177 **A revision is made by a merge, and also with no merge.** The indexer makes a catalog graph revision when it
takes a source revision in. The assigner makes an assignment graph revision on each new catalog graph revision, on
the seat `mesh-module-assigner`'s event `assignments-changed` (ADR 0567 §5, ADR 0641), and on the node-engine seat's event
`manifest-reads-changed`, which a node-engine says when the capabilities it states change and when its node returns
(ADR 0614 §4, ADR 0618 §3, §4; §590), so the assigner follows an event and never reads a node record across the seam (ADR 0596). A
placement change is traced like a merge. At its first start with no revision kept, the indexer takes in the head the
gate's source names for every repository by its catch-up path and keeps that as revision 1, and its verb `revision`
answers that it waits until then (lead-47's ruling in G's prepare: a first start has no merge to make a revision).
- §178 **A revision is kept while a record pins it or it is current.** A revision of either graph, and the input revisions
it names, is removed when no delivery record pins it and it is not current: neither trunk's newest revision nor the
newest of an open candidate. Pins are counted per revision, so a revision two records point at stays until both
have said `freed`. The indexer and the assigner remove one only on applying `freed` or on a revision ceasing to be
current, never on a timer and never by reading the gate's state (ADR 0596; *The delivery record*, above). A trace
must read every revision it moved between, and a revision neither pinned nor current is read by nobody.
- §179 **An unchanged entry is stored once.** A revision is kept as its list of entries' digests, and each entry as an
object of its own named by its digest, so an entry that two revisions share is stored once; an entry object is
removed when no kept revision lists it and no want or running build names its fingerprint (§443). Consecutive revisions differ by the few entries a delivery moves, so this
keeps the growth to what changes. It is chosen over a larger cap because whole revisions would grow with every
revision whatever changed, and a cap would only delay the same fill.
- §180 **The caps and the growth** (estimates, to be read from the fill tools once the milestone runs). A whole assignment
graph revision of about 120 modules on four nodes is under the bus's 256 KiB value limit (its unit test), and a
catalog graph revision's list of about 120 entries about 4 KiB, its entries, each carrying its manifest whole
(§443), about 15 KiB each. A record pins the revisions before and after its
delivery, and consecutive records share one, so at an estimated 20 deliveries a day about 20 revisions of each graph
a day stay pinned. A delivery moving 5 modules on four nodes adds about 20 assignment entries of about 1 KiB and a
list of about 15 KiB, and 5 catalog entries of about 15 KiB and a list of about 4 KiB: about 0.7 MiB a day of assignment graph and
1.6 MiB of catalog graph, about 63 MiB and 144 MiB at the records' default retention of 90 days. The caps are 512 MiB
for each, many times the steady state. Placement-only and capability-only revisions are pinned by no record and go
when they stop being current.
- §181 **A store filling is said before it is full.** The indexer, the assigner and the gate each say their fill against
its cap in their read tools, and the event `filling` of their seat when a store passes 80 % of its cap; the watchman
writes the finding `store-filling` naming the part and the store, the condition keeper raises it (ADR 0564 §2), and
it clears when the store falls below 70 %. A full cap refuses loudly, naming the part and the store: the indexer
keeps no new revision and says the merge pending with the reason `objects full`, the assigner keeps its last
revision and writes no new want, and the gate opens no new record and holds the merge; each says it as the event
`full` of its seat, which the condition keeper raises as urgent. When a removal brings the store back below its cap,
the part says `room` of its seat, takes the refused work up again (the pending merge, the next revision, the held
record) and the condition clears; a condition that never clears would hide the next fill. Nothing is dropped to make
room: a removal is only the rule above, and the setting `record-retention` or a larger cap is the operator's.
- §182 **What happened is not in the graph.** An entry says what is to run and what it waits on; whether it was built,
wanted or runs healthy is said by the parts as their events and kept in the delivery record (ADR 0656).
- §183 **The want stays the one record a node reads.** The assigner writes `want.<node>.<module>` from a trunk
revision's assignment graph only; a node-engine reads its want, never the assignment graph, and the have stays what
it runs (ADR 0656, which changes ADR 0571 §8).
- §184 **From G's delivery, per watched branch and between artifacts.** The catalog graph is kept once per watched branch,
each revision naming its branch, and its edges run between artifact references, of the six kinds of *Dependencies
between artifacts* (ADR 0666 §6, §14). The assigner computes a node's entries from the catalog graph of the branch its
placement names, and writes a want from a revision of that branch whose `required` steps passed, in place of a trunk
revision's alone (ADR 0666 §9, which changes ADR 0656 §2).
- §185 **Reverted.** Only the new form is written: the indexer, the assigner and the gate keep nothing of ADR 0603 §2's
keys or of `merges` and `turns` beside the new forms, since nothing reads them after milestone G. A revert of
milestone G is a change like any other, checked and delivered, and the delivery system restores what it must
(*A module's kept data on the bus, backed up and restored with its delivery*, under *A backup before the apply*).
Milestone G changes the form of these kept data, and raises their `form` (a kept data declaring none reads as form
0, so G's own move takes their restore points):
| Part | Kept data before G (form 0) | After G | Restored at a revert |
|---|---|---|---|
| the indexer | the state `modules` (ADR 0603 §2's one key per module) and its durable consumer's position on the forge bridge's stream `forge`; the states `holds`, `engine-reads` and `hold-bounds` of its mesh-wide hold | catalog graph revisions as objects, form 1; `modules` no longer declared, nor its tool `indexer_read_modules` or its view; the mesh-wide hold, its three states and its tools `indexer_holds` and `indexer_engine_reads` gone, the wait per node being the assigner's (*Composing waits per node, by the assigner*); every reader of it reading the catalog half (*Every other reader of which modules exist*, under *The revision view*, below) | yes: `modules` and the hold's three states as they stood at G's delivery, and the consumer's position |
| the gate | the states `merges` and `turns` | the delivery record's ledger, form 1; `merges` and `turns` no longer declared | yes: `merges` and `turns` as they stood at G's delivery |
| the assigner | the wants, written from the indexer's `modules` | the same wants, form 0, written from the assignment graph; the assignment graph revisions as objects, form 1 | no |
The wants keep their form: their keys and values are unchanged, only what the assigner computes them from changes,
so G raises no `form` on them and a revert restores none. Restoring the wants would send nodes back to older
fingerprints until the reverted assigner computed them again; leaving them, the nodes keep what runs and the reverted
assigner writes them again from the caught-up indexer. The assigner's placement and every other kept data of the
three keep form 0 and are never restored, so nothing decided since (a placement, a verdict, the source revision) is
set back. The restored `modules` is as it stood at G's delivery, so a module added since is missing from it until
the reverted indexer takes in its next source revision, and a reverted reader that learns which modules exist from
`modules` does not name it until then. What a reader granted since stays: the forge module's write grants on a
repository it learnt of from the catalog half are kept, since it grants writes and takes none away, so a revert
costs no repository its account's writes.
### A change's graph difference is its delivery
- §186 **The difference names everything a plan named.** (§512) For a change (a pull request's heads, a delivery group's
heads, or a merge), the indexer gives its catalog graph and the assigner its assignment graph. Compared with the
current revision, the difference names the new versions, the moved edges, the touched nodes and the order, read off
the edges, and no plan is stored (ADR 0656). The assigner serves it by the seat `mesh-wants`'s verb `difference`,
for two revisions of the assignment graph or for a candidate against trunk. ADR 0603 §6's tiers, as ADR 0633 §5
changes it, are a view worked out from that difference (ADR 0656, which changes ADR 0603 §6), read through
`difference`, with the waits and holds read through their owners' tools and the pending revisions through
`indexer_pending`.
- §187 **The preview is a candidate's difference.** Issue 1874's plan preview of a group before its merge is its
candidate's difference, read by one call of `difference`, and needs no code of its own: a candidate's graphs are
computed for its check anyway, so the preview costs nothing more.
- §188 **A changed fingerprint with no difference is refused by name.** When a module's fingerprint in the change
differs from the running one while the difference is empty, the assigner refuses it, naming the module, and the
check fails naming it.
- §189 **A candidate's graphs are kept apart, and dropped with its heads.** A candidate's two graphs are computed per
check, kept apart from trunk's, and dropped on the gate's event `heads-replaced`, said when the candidate's heads
were replaced or its pull request closed. A candidate costs a computed graph per check.
- §190 **Only a trunk revision writes wants**, whether a merge taken in or a placement or capability change made it; a
candidate's graph writes none (ADR 0656). From G's delivery a want comes from a revision of the watched branch a
placement chose whose `required` steps passed, and a candidate is a run on request's: the indexer builds it on the
runner's `run-started` and drops it on that run's `pipeline-ended`, the gate's `heads-replaced` no longer making
candidates (ADR 0666 §9, §18).
- §191 **Each part acts on its own entries.** The builder, the build modules and the grants part each act when their own
entries in the new revision differ from what they run (ADR 0656), reading them through the seat `mesh-wants`'s verbs
and events, never its bus state (ADR 0596), and naming no other module (ADR 0656). The node-engine acts on its want
alone, as it does today: the want is the one record a node reads, and handing every node the assignment half would
have each node read every other node's placements.
- §192 **A node asleep catches up when it wakes.** A node away holds nothing and catches up by its wants (ADR 0603 §7);
a want's new form reaches only a node-engine that says it reads it (ADR 0614 §4, ADR 0618 §2).
- §193 **No delivery goes ahead of a change.** A change is delivered as its own difference, its acting parts' new versions
first in their own steps (ADR 0656); the one exception is the delivery before research 085's milestone (*The
bootstrap delivery, and the one milestone after it*, under *Milestones*; ADR 0644).

Some files were not shown because too many files have changed in this diff Show More