Compare commits

...
Author SHA1 Message Date
jschoubben f67c788c82 Merge pull request 'Design 47 G: the lead's answers A-353 to A-362 (G's start)' (#608) from design/47-G-lead-answers-30 into main
Reviewed-on: #608
2026-11-13 19:08:20 +00:00
jschoubben 4fd3612c56 Answer A-361 and A-362: call words obey the cycle rule; the start seat list holds mesh-delivery
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 17:44:13 +01:00
jschoubben 8531306165 Answer A-360: the indexer says each change of modules on its views family
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 17:31:03 +01:00
jschoubben b0d52a1119 Answer A-359: the gate's start path passes the four seat verbs alone
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 17:27:52 +01:00
jschoubben 14acd2b593 Answer A-353 to A-358: G's start breaks the runner's own wait, grants keeps its modules read, the SDK declares the verbs it calls
Found after G's merge: a G tool runner waits on itself, grants freezes once
G's indexer runs, the watchman does not build, gitea needs G's build runtime,
and the graph ordered the tool runner first because the SDK's calls were undeclared.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 17:25:39 +01:00
mesh-tools 2b39e39385 Merge pull request 'Playbook 07: hold a delivery-system release's push on a dry run with no item open' (#607) from playbook/07-dry-run-delivery into main 2026-11-13 13:49:14 +00:00
jschoubben 010a93d6bc Hold a delivery-system release's push on a dry run with no item open
mesh/delivery merged: source.main moved to novox/hq@2b39e39385b6
mesh/check checking 010a93d6bc69 on 2b39e39385b6
Milestone G was pushed with its dry run's take-in item still open; once
merged, the running indexer refused G's own indexer with the live seat
list, and its trunk builds waited on renamed packages only the take-in
publishes. Playbook 07 gains step 2b: the live take-in run whole, every
build against the published packages, the gate's merge path read for the
group, and a dry run with an open item is not a pass. The glossary gains
"dry run"; the playbooks page lists the step.

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 14:26:22 +01:00
mesh-tools 0bbcf98baa Merge pull request 'Design 47 G: A-352, a bus module's running build counts while its newer build waits for the operator's yes' (#606) from design/47-G-lead-answers-29 into main 2026-11-13 12:10:51 +00:00
jschoubben 328ca1b320 Answer A-352: a bus module's running build counts while its newer build waits for the operator's yes
mesh/delivery merged: source.main moved to novox/hq@0bbcf98baa96
mesh/check checking 328ca1b32047 on 0bbcf98baa96
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 13:09:39 +01:00
mesh-tools 67960e12ba Merge pull request 'Design 47 G: A-350, the catalog's fill starts above every number the builder gave' (#605) from design/47-G-lead-answers-29 into main 2026-11-13 12:09:15 +00:00
jschoubben 258de07806 Answer A-351: the filled edges count the builder's numbers too; no digest where the builder kept none
mesh/delivery merged: source.main moved to novox/hq@67960e12ba4f
mesh/check being carried to 258de078064c on 67960e12ba4f | from aed903059436
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 13:05:30 +01:00
jschoubben 5dee91703f Answer A-350: the catalog's fill starts above every number the builder gave, published or not
mesh/delivery closed: a newer head of the same pull request
mesh/check checking 5dee91703f2e on aed903059436
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 13:02:51 +01:00
mesh-tools aed9030594 Merge pull request 'Design 47 G: A-349, the pipeline runner's old forms accepted before G' (#604) from design/47-G-lead-answers-28 into main 2026-11-13 11:03:20 +00:00
jschoubben ca86c2d8e7 Answer A-349: the pipeline runner's old forms are accepted before G, as §529 names it
mesh/delivery merged: source.main moved to novox/hq@aed903059436
mesh/check being carried to ca86c2d8e7b7 on aed903059436 | from afdb26b05706
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 11:46:29 +01:00
mesh-tools afdb26b057 Merge pull request 'Design 47 G: A-343 to A-348, the third reader release before G' (#603) from design/47-G-lead-answers-28 into main 2026-11-13 10:45:27 +00:00
jschoubben 96307d5f56 Answer A-343 to A-348: the third reader release before G, its scope and its checks
mesh/delivery merged: source.main moved to novox/hq@afdb26b05706
mesh/check being carried to 96307d5f56fe on afdb26b05706 | from dc76022e04b8
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 11:34:39 +01:00
mesh-tools dc76022e04 Merge pull request 'Design 47 G: the lead's answers A-339 to A-342, met merging the zfs group into G' (#602) from design/47-G-lead-answers-27 into main 2026-11-13 08:54:30 +00:00
jschoubben 14b5b324a2 Answer A-342: the node-engine sets MESH_NODE for every process it runs
mesh/delivery merged: source.main moved to novox/hq@dc76022e04b8
mesh/check pass 14b5b324a258 on a7004d5e2ad8 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 61s on 2…
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 09:17:36 +01:00
jschoubben 253c18c693 Answer A-341: a zfs device's finding in the writer's key form
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 09:16:08 +01:00
jschoubben 20a04f0226 Answer A-340: a process's bus may name what its module consumes; the writer's three events
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 09:10:36 +01:00
jschoubben 325bf80f19 Answer A-339: zfs's findings take G's writer over its keeper
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 09:08:48 +01:00
mesh-tools a7004d5e2a Merge pull request 'Design 47 G: the lead's answers A-323 to A-334, from the dry run of G on itself' (#601) from design/47-G-lead-answers-26 into main 2026-11-13 08:00:18 +00:00
jschoubben ed8b3bbd98 Answer A-338: f1-circuits' cache and Link2Pay's logs are class cache
mesh/delivery merged: source.main moved to novox/hq@a7004d5e2ad8
mesh/check pass ed8b3bbd98e7 on bba961f9fd86 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 68s on 4…
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 08:52:09 +01:00
jschoubben 50c4980a49 A-337: the glossary's word, the manifest validator
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:47:38 +01:00
jschoubben b216d914f7 Answer A-337: the judge counts and finds declarers over the whole trunk case
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:47:09 +01:00
jschoubben e7e3691799 A-336 holds for every start of a pipeline, not only a take-in
mesh/delivery closed: a newer head of the same pull request
mesh/check pass e7e369179944 on bba961f9fd86 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 66s on 4…
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:42:06 +01:00
jschoubben cbd32e3fa4 Answer A-336: a take-in heard before its branch's half starts with no steps
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:35:19 +01:00
jschoubben 712eca24ad Answer A-335: the ten repositories still on version 1 join G's form (the operator's yes)
mesh/delivery closed: a newer head of the same pull request
mesh/check pass 712eca24ad12 on bba961f9fd86 | unit 43 run, 0 kept, 1 skipped | integration 43 run, 0 kept, 1 skipped | composition passed in 69s on 4…
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:27:56 +01:00
mesh-tools bba961f9fd Merge pull request 'Design 42 zfs-owns-zfs: the lead's answers A-30 to A-45 met in develop and review (issue 1979)' (#600) from design/42-zfs-answers-2 into main 2026-11-13 01:27:30 +00:00
jschoubben 4a466efcac A-329, A-333: the glossary's word catalog
mesh/delivery closed: a newer head of the same pull request
mesh/check being carried to 4a466efcac84 on bba961f9fd86 | from a5eae7aa0a68
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:18:35 +01:00
jschoubben 5df4fb0ff6 Answer A-334: in a group take-in the changed runner is wanted on its own pass, first
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:12:58 +01:00
jschoubben e4145affa2 Answer A-333: package-only cycles and delivery edges into packages are not refused
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:06:52 +01:00
jschoubben 7c13ee5682 Answer A-332: a kind-4 edge keeps its first-process-resource source
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 02:02:20 +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 f9f78f8f36 Answer A-331: a cycle across steps is a cycle; amends 272 as A-323 built it
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 01:58:42 +01:00
jschoubben 9fa132e393 Answer A-330: the acting-cycle finding; the node-engine's want is held by no acting edge, other rules stand
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 01:53:08 +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 04e259524f Answer A-329: the seed's left-out module raises seed-left-out until a seat builds it
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 01:49:50 +01:00
jschoubben a60e04ed47 Answer A-326 to A-328: the build-input cycle is allowed, a late registration places its pipelines, the runner's own pipeline is never held
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 01:49:27 +01:00
jschoubben 6f417f30e7 Correct A-324 as A-325: a bypassed step is still placed and said
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 01:49:07 +01:00
jschoubben 75149fb5f3 Answer A-324: an archive or upstream artifact has its steps bypassed; a module no seat builds is left out of the seed by name
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 01:48:25 +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 87fdd04e81 Answer A-323: the assigner and the node-engine are one step, the node-engine's want first
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 01:44:49 +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
27 changed files with 3842 additions and 295 deletions
+2 -2
View File
@@ -20,9 +20,9 @@ questions are.
- 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 the milestone part does not give; and a piece of code placed where
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 the operator's answer is in the design;
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;
+17 -6
View File
@@ -1,20 +1,21 @@
---
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 alone - 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) and the list of design questions the design does not answer. Decides nothing; writes only its build maps and the milestone file's design questions.
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 section `Design questions`, nothing else; you call no verb that writes and make no git
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.** Read the milestone part and the design's main file whole, at the commit the
**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.
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
@@ -28,14 +29,24 @@ callers and kinds, as on main) with the design lines that name that seat, field
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, code or a live state that contradicts a design line: list
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, and the design questions, one line each with your recommendation. Glossary
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.
+63 -10
View File
@@ -89,8 +89,8 @@ is listed under [Homonyms](#homonyms), with the qualified form each domain uses.
**Purpose.** To say what one module is, in the one document every other domain reads.
**Recorded in** the catalog, one manifest per module. **Upstream of** every other domain; its words
are a published vocabulary, fixed by the manifest's schema. **Decided by** ADR 0040, 0126, 0174, 0188,
0207, 0236, 0262, 0634, 0635.
are a published vocabulary, fixed by the manifest's schema and judged by the manifest validator. **Decided by** ADR
0040, 0126, 0174, 0188, 0207, 0236, 0262, 0578, 0595, 0634, 0635.
**Uses:** module, seat, config value (Config), provision (Provisioning), data class (Data), health (Health
and repair).
@@ -100,9 +100,32 @@ and repair).
and never a pipeline step's status, which is the registration's policy (ADR 0666 §7, §14). Say
manifest in prose, not the file's name.
*Not:* ~~module definition~~ (hq)
- **resource** — one thing a manifest declares a node must have: a file, a directory, a service, a
container, a package, a bundle, an archive. A module **declares** resources; the verb *declares* is
- **resource** — one thing a manifest declares a node must have, by its `type`: a `directory`, a `file`, a
`service`, a `package`, a `container`, a `user`, an `archive`, a `network`, an `access`, a `process` or an
`opening`; never an `action` (ADR 0005). Its fields are the node-engine's vocabulary (mesh-sdk's declaration
package), which refuses a field its type does not use. A module **declares** resources; the verb *declares* is
this domain's and means what a manifest states.
- **process resource** — a resource of type `process`: a bundle of the module's own, named by `artifact`, fetched by
digest and run by the node-engine as a unit with the command `run`, in one of three modes: one that stays up, a
**run-once step** or a **scheduled run**. The way a module runs its own code without an image (ADR 0052, ADR 0053).
*Not:* ~~daemon resource~~ (hq)
- **run-once step** — a `process` or `container` resource with `run-once` true, run to completion by the node-engine
before every resource after it, which it gates; with `restart-on` it runs again when what it names changes (ADR
0052, ADR 0099). Not a migration of the module's own state, which is `prepares` (ADR 0135).
*Not:* ~~init step~~ (hq)
- **scheduled run** — a `process` or `container` resource with `schedule`, a five-field cron, run again on each fire
and never left running; a scheduled container may hold its own module's containers still while it runs
(`while-stopped`, ADR 0053, ADR 0189).
*Not:* ~~cron job~~ (hq)
- **runtime of its own** — the runtime that serves the tools bundle of a module saying `runs-as`, as that module's
account and on a bus account of its own, never the machine's runtime the tool runner runs as the operator's
account; its readiness is the manifest's `runtime-health` (ADR 0259 §8, ADR 0366).
*Not:* ~~private runtime~~ (hq)
- **old section** — a manifest's `state`, `logs`, `streams` or `objects`: kept data in the form before templates.
The parts the bus module needs before it can answer keep them for good; any other module's use of one is refused
once no accepted module outside those parts uses it any more (ADR 0595 §11, ADR 0619 §1). New kept data is asked
for in `requires` by template.
*Not:* ~~legacy section~~ (hq)
- **claim** — a module taking a spot on a seat: `claims: [{name, scope}]` in a manifest. A mesh-scoped
exclusive claim is how the mesh says "there is one of me". A foundation seat is named after the role
it guards: the `mesh-controller`, `postgres` and `nats` modules claim the `mesh-controller`,
@@ -112,7 +135,7 @@ and repair).
lines survive every send and are given back when the module goes (ADR 0174). The other way a node
varies a module, besides a config value; a **managed** file's marked block is one (Provisioning).
- **bundle** — the artifact a module's own code is built into — its tools, a seat's implementation, a
daemon — in any language the mesh has a toolchain for; never an image. A **tools bundle** speaks MCP to
daemon — in any language the mesh has a build module for (Go and TypeScript); never an image. A **tools bundle** speaks MCP to
the tool runner. One module may declare several
(ADR 0188).
- **tool** — one capability a module serves through the tool runner, addressed `<module>.<tool>`. A
@@ -535,6 +558,11 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
coding agent on a node (ADR 0183).
Taking a consumer off a licence is **unbinding** it; the licence manager's verb for it is still
`release`, an identifier to rename.
- **room** of a licence — how much of its vendor's usage windows is still unused: 100 less the higher of its
session window's (five hours) and weekly window's percentage in its latest reading, known only for a subscription
read recently and healthy. The licence manager moves every consumer off a licence at its threshold to the one with
the most room (design 39 bus-and-switch §25, §26).
*Not:* ~~headroom~~
- **proof** — what an authorising answer carries: a verified sender, a one-time code, a security key's
touch (ADR 0234). How much proof a kind of ask needs is its **assurance level**.
- **controller's terminal** — a command line the controller runs as typed by its operator, not asked through a
@@ -581,6 +609,11 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
registrations, asks each pipeline step of the seat that serves it, says each step's start and end and each pipeline's
end as its events, keeps each pipeline's state and step log on the bus, and serves the run on request (ADR 0666 §7,
§11). An acting part of the step `pipeline` (design 47). Of this domain: its contract is in `mesh-delivery-contracts`.
- **mesh-delivery-view** — the seat, held once for the mesh, of the status view's role (ADR 0540 §1): it serves no
verb and declares one state, `answers`, the person's answers in the web view to the asks of `mesh-wants` that need
no proof of who answered, offered as a view, so the teller follows it by the seat (design 47 G §717). The four
events of ADR 0540 §1 are its holder's own. Of this domain: its contract is in `mesh-delivery-contracts`.
*Not:* ~~status seat~~ (hq)
- **mesh-builds** — the seat, held once for the mesh, whose holder numbers a package's version when it is published and
says the publication as its event `package-published`, on which every artifact built against the package is rebuilt
and its pipeline started (ADR 0666 §15; design 51). Of this domain: its contract is in `mesh-delivery-contracts`.
@@ -663,6 +696,12 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
It keeps the verdicts of the packages it passed for the check to take (ADR 0637). Asked only when the operator
asks for one (ADR 0637 §6). Goes at milestone G's delivery, the **run on request** in its place (ADR 0666 §18); the
word goes to that entry's *Not:* line with the as-is update after that delivery.
- **dry run** — a session's run, before a release is pushed, of the parts that will take it in, build it and merge it,
as they run live, on the revision as the merge will make it: in process on the session's machine, reading the mesh
and writing nothing there. It holds four items: the live take-in run whole with the seat list it will read, every
build against the packages as published, the gate's merge path read for the group, and no item left open (hq
playbook 07, step 2b). Not a **try** nor a **run on request**, which the mesh runs and which deliver a verdict; a dry
run owes the forge nothing and is not a check.
- **trunk commit** — the commit of the trunk a merge check's verdict was judged against: the trunk's head when
the build module cloned it. The verdict holds for that commit only, goes out of date when the trunk moves, and
is then carried to the new commit (ADR 0323, ADR 0548). Goes at milestone G's delivery with the merge check: a pipeline
@@ -987,10 +1026,20 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564, 0668.
`recreate` of a protected stream, in place (design 47 G §681), and the backup holder only stores copies (design 47 G §554).
**aside** — live kept data a restore set aside, removed only by the hand tool `recovery_discard_aside`, which the operator and sessions call.
*Not:* ~~restorer~~, ~~restore account~~
- **ZFS snapshot** — a read-only point in time of one ZFS dataset on a machine, kept inside that dataset's pool, so
it guards against a mistake and never against the pool's loss; taken by a person with `zfs_snapshot`, or by the zfs
module's scheduled run, which names its own `mesh-<kind>-<time>`, marks them and removes only those past their keep
count (ADR 0679 §8; design 42 zfs-owns-zfs §21, §22). Never bare, beside a ledger's snapshot and the stream snapshot
(see [Homonyms](#homonyms)); not a backup or a restore point, which are kept elsewhere.
*Not:* ~~pool snapshot~~, ~~dataset snapshot~~
- **disk readings** — the size of each data item a node's modules declare, measured by its `measure` and kept
with its largest size in seven days by disk-load on that node, in its own state, one key
`<node>.<module>.<item>` written only when it changes; the holder of `node-backup`, mounts, nfs-server and the
self-check read them there, never the controller (ADR 0571 §5).
- **carry-over** — a module's one copy of the data it kept in its old form into its bus state, at its first start
on the new form and before it does anything else, recorded in a key of its own so it is never copied again
(ADR 0437 §7; design 39 bus-and-switch §11 to §13). Not a take-over, which moves a job between parts.
*Not:* ~~data take-over~~
- **sticky binding** — a consumer's tie to the data a provider keeps for it, which moves only by a
person (ADR 0232).
- **bus state** — what a module keeps on the bus, asked for in `requires` as a provision of the bus module, by a
@@ -1349,11 +1398,11 @@ Not a domain of the mesh but of the way it is built, listed because its words me
should be, and lives only while the milestone is built (playbook 07, step 0).
*Not:* ~~context file~~ (hq)
- **design question** — what a milestone's builder would have to choose because its milestone part does not say it:
a field, a key, a bound, an owner, a caller, an event, a fate for what exists. It is asked of the operator, and its
answer is written into the design before any code builds it (playbook 02); nobody else answers it, save one of
internal form only (changing nothing the operator sees, no permission and no data kept or removed: a wire form, a
function's input, a contract field's shape, a package's place), which the design writer answers as a design line
read by the reviewers by architectural domain (playbook 07, step 0). A milestone's
a field, a key, a bound, an owner, a caller, an event, a fate for what exists, or a place where a design line and
the code disagree. The lead answers it in one line against the principles, naming the principle that settles it;
only one no principle settles goes to the operator (when its answer changes what the operator sees 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. Every answer is in an hq commit before any code builds it (playbook 07, step 0b). A milestone's
design is **complete** when no design question of it is open (playbook 07, *Design complete*).
*Not:* ~~open point~~ (hq)
- **issue** — a question about the mesh that someone must answer, numbered and tracked by the issue tracker, which
@@ -1415,6 +1464,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| | anything a module reads that someone may change | **config value** | Config |
| snapshot | the latest state of one ledger instance, from which a rebuild starts | **snapshot** of a ledger's state | Data |
| | the bus's own backup of each stream | **stream snapshot** | Data |
| | a read-only point in time of one ZFS dataset, inside its pool | **ZFS snapshot** | Data |
| template | a storage type with a configuration, asked for in `requires` | **template** of bus state | Data |
| | a file with placeholders a provisioner fills | **template file** | Config |
| option | a setting of a template a module may change | **option** of a template | Data |
@@ -1437,6 +1487,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| step | an action run on a module's commit before it is delivered | **pipeline step**, always whole | Delivery |
| | the step an acting part acts in (`take-in`, `build`, `compose`, …) | an acting rule's **step** | Delivery |
| | a step a person starts, like the bus's | **planned step** | Core |
| | a resource the node-engine runs to completion before what follows it (`run-once`) | **run-once step** | Module |
| | a step of a rollout | **tier** | Delivery |
| tier | a level of the mesh | **layer** | Core |
| | a step of a rollout | **tier** | Delivery |
@@ -1494,6 +1545,8 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| | the notice that mail matters | **mail signal** | Mail |
| conversation | the domain *Operator and conversation* | the domain's name | Operator and conversation |
| | the mail questions and answers on the mail page | **mail conversation** | Mail |
| part | one independent process of the delivery domain with its own bus credential (ADR 0429 §1) | the part by its name (the **indexer**, the **gate part**, the **grants part**) | Delivery |
| | one of the pieces a module is built of: a bundle, or a resource that places or runs one, in one manifest (ADR 0040; design 20 *What a module is built of*) | **a module's part** (its manager bundle, its tools bundle, its settings file) | Module |
## How this page is kept
+13 -12
View File
@@ -191,14 +191,15 @@ 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 answered by the operator and by nobody else:
not by the lead, the preparer, a developer or a reviewer, however technical it is. The one exception is a question
of internal form only (playbook 07, step 0, *Who answers a design question*; the operator, 2026-11-11): changing
nothing the operator sees, nobody's permissions and no data kept or removed, it is answered by `hq-design-writer` 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 it 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:
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
@@ -213,10 +214,10 @@ was met (file:line) and a recommendation. Then:
(`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 design line
added during a milestone with no operator's answer or record behind it, other than an answer of internal form only
that touches nothing the operator sees, no permission and no data, is a finding of the review by architectural
domain.
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
+41 -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,39 @@ 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
@@ -124,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),
+142 -46
View File
@@ -54,23 +54,35 @@ A small fix (one repository, one issue, about an hour of work) follows
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, the operator answers that (or, for a
question of internal form only, the design writer, step 0), and develop starts only when it is all answered (step 0b, *Design complete*).
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 dry run with an open item, taken as a pass.** On 2026-11-13 milestone G of design 47 was pushed with its dry
run's item "the take-in's seat path is not run" still open; once merged, the running indexer refused the milestone's
own indexer and five more modules with the live seat list, and its trunk builds waited on renamed packages only the
take-in would publish. The dry run (step 2b) now runs the live take-in, the builds against what is published and the
gate's merge path, and holds the push while any item is open.
- **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, with the design completed between the first two (step 0b), 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, for a change of what judges, builds or merges it, a dry run between the review and the check (step 2b), 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 | from the milestone part alone: where each design line lands in code, what exists there today and what relies on it, as one build map per repository; and every design question, the design's silences, listed | 15 minutes, longer when what exists is large: code never starts on an incomplete answer |
| 0b. Design complete | every design question is answered, by the operator or, for one of internal form only, by `hq-design-writer` (step 0); each answer is written into the milestone part (playbook 02) and its build map rows added | until the list is empty: develop never starts before |
| 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 |
| 2b. Dry run | for a release that changes a part judging a change, or a module path, a package or a manifest form the take-in reads: the live take-in run whole with the seat list it will read, every build against the packages as published, the gate's merge path read for the group, and no item left open | until no item is open: nothing is pushed before |
| 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` |
@@ -116,7 +128,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
`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. 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 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
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
@@ -129,32 +141,35 @@ follows the decided design and makes no decisions, least of all architectural on
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. Where each
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) and the list of design questions
(item 10). It writes no findings of its own, no recommendations into the build map and no rulings, and no file of
- **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** (the operator, 2026-11-11). A 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 a question of internal
form only: `hq-design-writer` answers it as a design line from the design and its records, with its reason, the four
reviewers by architectural domain read it in one round, and the lead tells the operator each such answer in one
line. Every other design question is the operator's, however technical: not the lead's, the preparer's, a
developer's or a reviewer's, nor a lead's of another session; one that seems internal but touches who may call,
what the operator sees or data is the operator's. The lead's part is the process: who works on what, on which
model, the waits and the reports, and asking the operator.
- **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); a question answered by anyone but the operator, other than one of
internal form only answered by `hq-design-writer` in a design line the reviewers by architectural domain read, is a
breach the lead names at the next report.
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
@@ -166,11 +181,17 @@ breach the lead names at the next report.
`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. **Read the milestone part and the design's main file whole**, before any code; the preparer reads nothing else
to know what is to be built. 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), never into an answer of the preparer's or the lead's. A
fixing agent that meets a design question stops that part, and its run is spent again: the prepare is where the
questions are found.
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
@@ -192,7 +213,8 @@ breach the lead names at the next report.
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, never the lead's own choice.
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
@@ -288,39 +310,47 @@ breach the lead names at the next report.
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. The list is the prepare's last output and step 0b's input.
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 are all answered, each in the design (the operator,
2026-11-10: a question the design does not answer stops that part, goes to the operator, and its answer goes into
the design before the code).
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 sorts each question and asks the operator's.** A question of internal form only (step 0, *Who answers
a design question*) goes to `hq-design-writer` in its handover; every other one the lead asks the operator, one per
ask, as playbook [02](02-graduation.md), *Answering a design question*, says, and adds each ask to its wait list.
The lead answers none and passes on no answer of its own; a question the writer finds is not of internal form only
it hands back for the operator.
2. **Each answer 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.
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 names its answering commit. Then step 1 starts, from the milestone
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 asks it as above, and the
stopped subtasks resume from the hq commit that answers it; one of internal form only goes to `hq-design-writer` as
item 1 says. Nobody builds around it, guesses it or builds both ways.
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` 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
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)
@@ -481,10 +511,74 @@ an answer no hq commit holds, is a finding of the review (step 2, item 2a).
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 2b — Dry run (a change of the delivery system, or of what the take-in reads)
**When it runs.** After the review approves the heads and before the push of step 3, for every release that changes a
part judging a change (step 3, item 3: the indexer and its manifest validator, the gate, the builder, a build module,
the checker or the pipeline runner, the node-engine, a seat definition), and for every release that adds or renames a
module path or a package other modules take, or changes the form of manifests the take-in reads. The prepare step's
milestone file says per release whether it runs and why (`Dry run: yes` or `no`, with the reason); the review refuses a
group that lists a judging part with no such line. The operator, 2026-11-13: "remember the mistake and make sure our
pipeline catches this".
**What it is.** A dry run runs the parts that will take the change in, build it and merge it, as they run live, on the
revision as the merge will make it: in process on the session's machine, reading the mesh and writing nothing there,
asking no try and no check. An agent of hq's definitions runs it, read-only on the mesh and on every working copy; the
lead writes only the handover. Everything live it uses (the builds the nodes run, the seat list, the published
versions, the trunks) is read in the same hour as the push, and read again, with the dry run run again, when a head,
a main or a live build moves before the push. Four items, each held before any push:
1. **The live take-in, run whole.** The indexer that will take the revision in (the running one, read from the view's
reach of its module; for a revision that names another build of the indexer, the running one, which keeps it
pending, and the named one, from its start) runs on the revision: every member's head merged onto its base's main as
it is now, the other trunks' manifests as the indexer's state holds them, and **the seat list as the take-in will
read it**, which is the seat list the seat `mesh-catalog`'s verb `seat-definitions` answers at that moment, not the
group's own definitions laid over it (step 1's brief lays them over for the developer's case; the take-in does not).
It runs through to the keys the take-in would write (each fingerprint, a pending revision, a group's members
gathered), never the manifest validator alone. A manifest refused, a module set aside, or a revision left pending
with no indexer that can take it in is a fault of the release: it is fixed in the release, or what the running parts
cannot take in is delivered first (step 3, item 3), before the push.
2. **Every build against the packages as published.** Every build the revision starts (each module whose fingerprint it
moves, as the indexer's run of item 1 lists them) is built with `GOWORK=off` and no `replace` toward a local tree,
each mesh package resolved from the registry seat of its language as that seat serves it at that moment (its verb
`versions`), never from the group's heads. A new or renamed module path, or a package that only the revision's own
take-in or build would publish, shows here as a package with no published version: a circle in which the build
waits on a take-in that waits on the build. It is a gap of the build and delivery system, built there before the
push (in the same milestone or before it) and said to the operator, never pushed into.
3. **The gate's merge path, read for the group.** Before the push, and again before the operator's Approve is asked:
each member's base, read with `mesh-delivery.show` (the gate never hands a member whose base is not the branch it
merges into, and every member after it in the gate's order waits behind it); the group itself, read with
`mesh-delivery.show` on the group's id, open and holding every member (a pull request reopened into a group the gate
holds closed joins it as a lone delivery); and every member present before the first ask. The gate merges a group's
members one at a time and undoes none, so one member it cannot merge leaves the group half merged. A member on
another base is moved to the gate's branch or left out of the group, a group the gate holds closed is pushed under a
new branch name, and an Approve ask that names fewer members than the group is ended and said to the operator,
never answered.
4. **No item open.** The dry run's report says of each item what ran, at which heads and on which live builds, and its
counts ("ran: 193 manifests, 0 refused, seat list read at 13:05"). An item said "not run", "not known" or "read, not
run" is closed, by running it or by showing why it does not apply, or the push is held: **a dry run with an open
item is not a pass**, and no rule is weighed against it. Before the push the lead says to the operator, in one line
per item, what each showed.
The faults it answers, 2026-11-13: milestone G of design 47 was pushed after two dry runs, the second of which said
"with the live seat list 26 refused; the take-in's seat path is not run", and the lead pushed with that item open.
Once merged, the running indexer took the revision in with the live seat list and refused the milestone's own indexer
and five more modules. The milestone renamed module paths, and the trunk builds waited for packages that only the
take-in publishes; no dry run had built a trunk module against the published packages alone. The same day the gate
merged a group one member at a time with no undo, stopped at a member whose base was not main, and asked an Approve
while a group was still forming and per member of a closed group.
**How it is checked.** The milestone file holds a section `Dry run` with each item's result, the reviewed heads it ran
on and its time (`grep -n -A6 '^## Dry run' .work/<slug>/milestone.md`), older than the push (the forge branch's
first push of the heads) and with no item open; the pull requests' description names it in one line. A push of a
release whose milestone file lists a judging part with no such section, or with an item open, is this step skipped;
a fault met after the merge that an item would have shown is named at the next report and written here as a lesson.
## Step 3 — Check (no review)
1. **Push the reviewed heads once, then open one pull request per repository, together** (ADR 0548; the operator,
2026-10-26: "every release should only run 1 check or 1 try"). **No pull request, draft or not, exists before the
2026-10-26: "every release should only run 1 check or 1 try"), after the dry run where step 2b asks one, with no
item open. **No pull request, draft or not, exists before the
review approves the heads**, and no fix round is pushed to an open pull request: every push to an open pull request
asks a new check, of every member for a group, and the mesh's build capacity is spent on it ("we don't have more
processing power"). The integration tests run once, in the checker's merge check on the merge with the trunk's head, at the unit
@@ -849,6 +943,8 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
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.
- A release whose milestone file lists a part judging a change holds a `Dry run` section with all four items closed,
older than its push (step 2b); one pushed without it, or with an item open, is that step skipped.
- 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
+7 -6
View File
@@ -45,12 +45,13 @@ processing power"). Everything before the push is local and fast, and no try is
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**: the session asks the operator, one question with its recommendation, and the answer is
written into the design (playbook [02](02-graduation.md), *Answering a design question*) before any code builds
it; neither the session nor the lead answers it. A question of internal form only (playbook
[07](07-feature-branches.md), step 0, *Who answers a design question*) is answered instead by `hq-design-writer`
as a design line, 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
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
+7 -6
View File
@@ -282,7 +282,8 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<h3>Step 0 · what the lead writes before any agent starts</h3>
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
<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>Design complete: the operator answers each question, or the design writer one of internal form only (no change to what the operator sees, no permission, no data kept or removed), read by the reviewers by architectural domain; the answer is written into the milestone part, and only then does develop start. Nobody else answers one.</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>
@@ -467,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"],
@@ -475,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, read the milestone part and the design's main file whole. (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 operator answers every question (the design writer one of internal form only), each written into the milestone part; 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)"],
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)","Dry run, for a change of what judges, builds or merges it: the live take-in run whole with the seat list it will read, every build against the packages as published, the gate's merge path read for the group; an item left open holds the push. (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","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."},
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","A dry run with an open item, taken as a pass"],
check:"The checker's one status, mesh/check; the review record; the dry run's section in the milestone file; 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, the design lines it builds and the code; a design question goes to the operator (one of internal form only to the design writer) and into the design 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)"],
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"],
+182 -134
View File
@@ -3,36 +3,64 @@ layer: to-be
status: proposed
code:
- mesh-catalog modules/showcase
- mesh-controller internal/builder
- mesh-sdk src
- 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-05
updated: 2026-11-12
decisions:
- "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 0150"
- "ADR 0099"
- "ADR 0053"
- "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
@@ -93,136 +121,144 @@ the build modules' regenerate-and-compare step, the catalog check of a client an
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).
## A module with four capabilities, in four languages
## Who reads a manifest, and what each one judges
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.
*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").*
```
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
```
A manifest is read by four parts, and each judges only what is its own:
### The manifest
| 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) | — |
```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 * * *" }
]
}
```
**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.
**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.
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.
### Step 1 — say what it is, in each language's own terms
## What a module is built of
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 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).
```
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"]
```
**The artifact kinds** the manifest validator accepts are five, and a builder builds each with one build module:
**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.
| 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 |
### Step 2 — write each part against its capability
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.
Each uses only the part of the protocol it needs. The event consumer never learns what a grant is.
**The parts, and the manifest form of each:**
```
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
```
| 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 |
### Step 3 — the mesh does the rest
**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).
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.
**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 costs you to use four languages
**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.
**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.
**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 catalog 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
@@ -230,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.
@@ -397,3 +397,10 @@ clause of the record that statement cites, as it was before the move; "§ 12.n"
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).
@@ -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):
@@ -0,0 +1,181 @@
---
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.
@@ -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
@@ -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:
@@ -1658,9 +1658,10 @@ list:
point while the reverted manifest declares no catch-up tool), and only then are the reverted
assigner and gate wanted; a module delivered during milestone G keeps its fingerprint while they wait; no want goes
to a fingerprint older than the trunk's; the operator is asked again only for an ask raised after the restore
point. A revert whose restore point is missing is held, naming it. Proved with the real builds of the indexer, the
assigner and the gate from before milestone G, not fixtures, started by their fingerprints in the artifact store
(whole, in `mesh-host`: checker, gate, assigner, indexer, node-engine, backup holder, bus module; design 47 G §405).
point. A revert whose restore point is missing is held, naming it. Proved in two parts (the operator, 2026-11-12): each module's integration test proves its own wiring
of the undo with every other module a stand-in (the node-engine's: wants held until the restore and the catch-up,
a missing restore point held, naming it), and the whole undo end to end belongs to the test lab, which is paused until its
performance is fixed; no check starts real builds.
37. **Nothing is delivered ahead.** Research 085's milestone is delivered with no part delivered ahead of it, read
from its delivery record (ADR 0656, How it is checked).
38. **A newer indexer is reached.** A merged revision naming a newer indexer gives, from the running indexer, a catalog
@@ -327,7 +327,7 @@ for several build maps is answered once, and the maps' names for it are given in
- §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 **Test 36 reads the builds of before G through the registry seat.** (§405, §555, §610) Test 36's run, in the
- §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.
@@ -103,19 +103,25 @@ names the principle that settles it, and is cited as "design 47 G A-n".
- **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-26, A-16; principle 7) The tool runner's first views follow A-26 too: `firstview` builds a
`views.ProcessReader` over the runner's bus connection and adopts its followers, so the reader raises their findings;
the runner's `bus.Conn` gains one accessor for its JetStream, its one place (A-16).
- **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, the tool runner's ask) writes and clears its `first-view-unanswered` finding itself through
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, §686; principle 3) Every module whose writer publishes declares the events it says
(`finding-written`, `finding-cleared`, `findings-stated`) in its manifest; none publishes an event it does not declare.
- **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.
@@ -131,3 +137,554 @@ names the principle that settles it, and is cited as "design 47 G A-n".
`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,748 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-13
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.
- **A-323** (§49, §268; principle 3) The assigner and the node-engine on one node are an acting cycle of delivery edges
only (compose and place), so they are delivered as one step (§49), never held by each other: the assigner writes that
node's node-engine want first, held by nothing but its build (§268), and holds the node's other wants, its own included,
until the node-engine seat says the new version runs there. The acting-wait check finds a cycle across steps as well as
within one, and a want held on a cycle it cannot break says so as a finding (found in the dry run of G on itself,
2026-11-13).
- **A-324** (§8, §535, §704, design 51; principles 3, 7) An artifact the builder packs or fetches itself, kind `archive` or
`upstream` with no language (design 51), has no lint, unit or integration step to ask: the seed and the runner call no
build seat's `steps` for it, its registration carries those steps as `bypass` (§8's reason), and the runner places no
bypassed step. An artifact that names a language or kind no build seat serves stays a failure as §535 says, naming the
artifact, in the seed as in the runner: the seed leaves that one module out with a finding naming it, never the whole
seed.
- **A-325** (corrects A-324's runner half; §23, §28; principle 3) The runner places a bypassed step as §23 and §28 say:
outcome `bypassed`, asked of no one, said. For an artifact of kind `archive` or `upstream` with no language, its lint,
unit and integration steps are `bypass` whatever its policy says, so no build seat is asked for them; every other step
keeps its policy.
- **A-326** (§47, §49, §51, §270, ADR 0614 §6; principles 3, 8) A cycle through a build module's own inputs is not
§49's: where the build edge (kind 3) goes out of an artifact of a module that holds a build seat into an artifact it
builds, the running version builds the new one (§270), so the cycle is broken at the running version and is allowed.
Every other cycle with a build edge is refused at take-in, naming every artifact on it (§49, §51); the indexer builds
that rule in G, over the catalog graph it keeps. An edge of kind 4 names the artifact that offers the provision (its
`answered-by`), not the module's first process resource.
- **A-327** (§21, §23, §54; principles 1, 3) A pipeline started before its module has a registration is placed when the
registration arrives: the runner, on `registration-changed`, places from the registration the steps of every pipeline of
that module that has none, once each (by compare-and-set), and says it. Until then the pipeline says it waits on the
module's registration.
- **A-328** (§29, §65, §270; principle 5) A take-in that changes the runner holds every other pipeline of the take-in
until the new runner runs (§65), but never the runner's own pipeline: that one runs on the running runner, as a build
module's running version builds its successor (§270), so its pass lets the new runner be wanted (§29) and its running
releases the held ones. The bound stays only as a failure bound, with its finding.
- **A-329** (A-324, §8; principles 3, 7) The module the seed leaves out raises the indexer's finding `seed-left-out`,
scope `module`, id the module's name, naming the artifact and its language or kind, and is named in the seed's ask
beside the unprotected branches (`Left`, §8). It clears only when a later seed finds a build seat for every artifact of
it, or the module leaves the catalog; the seed being applied without it does not clear it.
- **A-330** (sharpens A-323; §268; principles 3, 8) A want the assigner holds on a cycle it cannot break raises its finding
`acting-cycle`, scope `module`, id `<node>.<module>` of the held want, naming the cycle with each edge's step; the next
pass that finds the want held on no cycle clears it. "Held by nothing but its build" means held by no acting edge: the
node-engine's want on a node (the holder of the seat `node-engine` there, read from the acting edges' seat) keeps every
other rule of a want (grant, hold, canary, composition).
- **A-331** (amends §272 by A-323; §49; principle 3) §272's "a cycle across two steps is none" no longer holds: a cycle of
acting waits across steps is a cycle (A-323), delivered as one step where it has delivery edges only (§49), and a want
held on one no rule breaks raises `acting-cycle` (A-330).
- **A-332** (withdraws A-326's last sentence; §47; principle 7) A manifest names no artifact for a provision (`answered-by`
is `helper` or unsaid), and the delivery waits cut edges down to module names, so the artifact a kind-4 edge starts from
changes no order: it stays the module's first process resource, as built, and G changes nothing there.
- **A-333** (sharpens §49 and A-326; §47; principles 3, 7) Two more cycles are not §49's. (1) A cycle of package edges
(kind 1) alone: a real import cycle is refused by the language's own build, and one found only because imports are
given to every artifact of a module is not a cycle of what is built. (2) No delivery edge (kinds 2, 4, 5, 6) ends at a
package artifact: a package is delivered only inside what uses it, which a kind-1 edge carries, so such an edge is not
made and cannot close a cycle. How it is checked: the indexer's cycle rule accepts today's trunk catalog and G's own
(the dry run's entries, kept as test data), and refuses a cycle with a build edge among delivered artifacts, naming
them.
- **A-334** (sharpens A-328; §29, §65, §268; principle 5) In a group take-in that changes the runner, the runner is the
acting part the other members wait on, so it goes first as the node-engine does (§268): its member is wanted on its own
pipeline-ended once that passed, ahead of the group. The other members keep §29: their pipelines run on the new runner
once it runs, and they are wanted together on group-ended. The bound stays a failure bound only.
- **A-335** (§368, §583; principle 5; the operator, 2026-11-13 about 02:30 CET: yes) Every module on the trunk joins G's
form, not only G's own: the ten repositories whose 24 manifests still say `"version": "1"` (mesh-media-catalog,
Link2Pay, de-spiegel, photos, okioki, car-hunter, f1-circuits, snake, txt-game, amqp-email-forwarder) are members of
G's group, each manifest's `version` written as §583 says (the `MAJOR.MINOR` of its newest published version through
`builder_versions`, `0.1` for one with none). §368's rule keeps no old form. How it is checked: G's judge accepts every
trunk manifest at G's heads (the dry run's mesh-wide read, again before the push).
- **A-336** (A-327, §200; principles 3, 7) Any start of a pipeline (a take-in, a package published) heard before its branch's catalog half is held starts its pipelines
with no steps, never with steps placed from empty entries (§200: nothing new of a branch is acted on until its half is
held); the path of A-327 places them once the half is held.
- **A-337** (§712, ADR 0579 §2; principles 3, 7) "Refusing only what the revision causes" takes the trunk case as it
stands: the manifest validator counts old sections and finds the declaring modules over every trunk-case manifest that decodes,
accepted by its own rules or not, so a trunk manifest the new rules refuse still counts and still declares. A trunk-case
manifest that does not decode is named in the verdict's report. How it is checked: G's manifest validator on G's heads against
today's unmigrated trunk refuses none of G's 169 manifests for a count or a declarer (the dry run's re-creation).
- **A-338** (A-335, ADR 0233; principle 7; the operator, 2026-11-13 about 03:50 CET: "it's a poc") The two trunk manifests
the manifest validator refuses once they change, for a written directory with no data declared, declare it in G's
change: f1-circuits' `cache` and Link2Pay's `logs`, both class `cache`.
- **A-339** (G §383, §384, §387; design 42 zfs-owns-zfs A-32, A-37, A-39, A-41; principles 1, 7) The zfs module's
findings take G's form through the bus path it already has: its keeper's closed list gains exactly what G's generated
findings writer needs (the publish of `finding-written` on its stream `zfs-findings`, and a hear of
`mesh-conditions.findings-asked`), and the root follower runs G's writer over the keeper; its manifest gains the
`finding-events` stream and that `consumes`. The follower keeps writing its findings, since it is the part that sees
them; nothing else of the keeper's list widens.
- **A-340** (sharpens A-339; design 42's V5 and G1; principle 7) A process's `bus` may name, under `consumes`, an event its
module's `consumes` names, as V5 lets it name what its module's `requires` asks; the manifest validator judges it as V5
does, and the grants part gives that process's account the hear and the consumer of exactly those events, as G1 gives
the publish. The zfs keeper names `mesh-conditions.findings-asked` there. "Exactly what G's writer needs" is its three
events on the module's `finding-events` stream: `finding-written`, `finding-cleared`, `findings-stated`.
- **A-341** (A-339; design 42 §27; G's findings writer; principle 7) Under G's writer a zfs device's finding is scope
`module`, id `zfs.<pool>.<device>` (the pool's own state: `zfs.<pool>.pool`), kind `not-online`, its subject scope
`module`, id `zfs`, on the node; `<device>` is the device's own name, the last element of its path. §27's one finding
per device not online stands; only its key takes the writer's form.
- **A-342** (A-339, A-341; principles 2, 7) A process learns its node the way the SDK already reads it: the node-engine
sets `MESH_NODE` to its own node's name in the environment of every process it runs, beside the resource's own `env`
(a resource's own `MESH_NODE` is refused by the manifest validator, so the mesh's value is the only one). The zfs
follower's writer and its `node-service-manager` calls then name the node; no new reference form and nothing special to
the keeper.
- **A-343** (§50, §494, §697; principles 5, 7; the operator, 2026-11-13: G's order put right, readers first) A third reader
release goes before G: one delivery group of mesh-sdk and mesh-delivery that carries exactly the readers of the seven
forms today's manifest validator refuses in G's manifests (R1 invokes `seat:<seat>.accept.<queue>` and
`seat:<seat>.<stream>.<kind>`; R2 reads `seat:<seat>.<state>`; R3 `last-per-subject`; R4 `objects-max-mib`; R5
`mesh-host-findings`; R6 the old `objects` section of gitea and distribution; R7 a process's `bus.consumes`), built from
mesh-sdk main after the revert (27da9491). The node-engine is rebuilt with the release's mesh-sdk, so it states
`word:streams.last-per-subject`. Nothing accepted on main today is refused after it: f1-circuits and link2pay stay as
they are, named in the whole-run test. How it is checked: today's validator, rebuilt with the release, accepts all 193 of
G's manifests and every trunk manifest it accepts today; delivered and reached on every node before G is asked again.
- **A-344** (A-343; §601, A-128; principle 3) The release reads each form with G's check of it (a `seat:` name that names
no seat, queue, stream or state of that seat is refused, as G refuses it), never the form alone; no trunk manifest uses
these forms, so nothing on main is refused for them.
- **A-345** (A-343, §584, §627; principle 7) Before G's merge the foundation list stays as it is; the two old forms of
gitea, distribution and the node-engine (bus state in `requires`, the old `objects` section) are accepted for exactly
those modules until G puts them on the list.
- **A-346** (A-343, §600, §601; principle 7) The release changes no grants code: until G is delivered the grants part
keeps its reading; G's own grants part is delivered first within G (§90).
- **A-347** (A-343, §714, §715) G's take-in is one revision of its group, so the acceptance of the release is the
all-of-G run (G's 193 manifests with G's 65 seat definitions), not the live per-member verdicts of before.
- **A-348** (corrects §703's sentence; A-343) The second reader release did not carry `last-per-subject`; the third does.
- **A-349** (sharpens A-345; §529; principle 7) The modules whose old forms the release accepts before G puts them on the
foundation list are those §529 and §584 name: gitea, distribution, the node-engine and the pipeline runner
(mesh-delivery-pipeline). The runner is new in G, so no trunk verdict moves.
- **A-350** (sharpens §372; principles 2 and 3) The fill takes, for each module, the highest version the builder's state
`versions` gave a number, whether its state is `numbered` or `published`, not only `published`: today's builder numbers
fresh bundle versions without publishing them (the late publish finds no digest), so a fill from `published` alone
would number again below what the builder already said (mesh-config/tools: `published` 0.1.0, numbered up to 0.1.14),
and one version would name two contents in the builder's log, its conditions and the catalog.
- **A-351** (sharpens A-350; principles 2 and 7) The filled edges of kinds 1 and 3 take the same rule: the dependency's
version the builder gave a number, numbered or published, since that is the version the module was built against; a
filled version or edge whose number the builder kept no digest for carries none (the fill needs only the number to
count above it, and a filled edge is stale until the module's next build).
- **A-352** (ADR 0614 §5 and §2; principle 3) The assigner's bus-state rule counts a build that offers `bus-state` and
runs healthy from the holder's have, also while a newer build of the bus module waits for the operator's yes: the
running build serves the bus state, so a consumer that needs none of the new build's forms is not held; a consumer
whose new form only the new build reads still waits for it by its edge of kind 4 (§50).
- **A-353** (G's start, found 2026-11-13 after its merge; principles 3 and 7) While its catalog half is unread, a tool
runner's approval gate lets through the first-view calls the catalog half is read by (the seat mesh-catalog's
`registrations`, `revision` and `entry`, the seat mesh-delivery's `source`, and their owners' first-view tools) and
holds every other call as today, so the runner that serves the indexer's tools can answer the reads it waits on; the
rule lives once, in mesh-sdk's approval gate.
- **A-354** (G's start; deb88b17 "grants keeps its read of modules until W8"; principles 3 and 7) The indexer keeps
serving the first view `modules` in its current form until grants reads the catalog half, so grants issues new
rights once G's indexer runs; the two move together in the same release as W8.
- **A-355** (G's start; principle 5) Every G part builds from main before any is placed: the watchman's go.mod
requires and replaces the seats it imports (nodebuildimage, nodebuildnode), as every module's go.mod does.
- **A-356** (G's start, the forge; principle 7) G's gitea is built by G's mesh-build-node, which is therefore delivered
before gitea; no lock file is kept for a mesh package that the build runtime exempts.
- **A-357** (§47 kind 6; principles 3 and 7) A package's `calls` names every seat verb its code calls on a module's
behalf, in the `invokes` form (`seat:<seat>.<verb>`); mesh-sdk's Go package adds `seat:mesh-catalog.registrations`,
`seat:mesh-catalog.revision`, `seat:mesh-catalog.entry` and `seat:mesh-delivery.source`, and the indexer derives an
edge of kind 6 from such a call word against the verb's server exactly as from `invokes`, so every module built
against the SDK is delivered after the running versions that serve its start.
- **A-358** (§47; principle 7) The tool runner keeps `invokes: "*"`, its role of forwarding any call; what its own start
needs comes to it through the SDK's `calls` (A-357), as for every module.
- **A-359** (narrows A-353; principle 7) The catalog half is read by seat verbs alone, so the gate lets through, while
it is unread, exactly the calls whose seat and verb are `mesh-catalog` `registrations`, `revision` and `entry` and
`mesh-delivery` `source`, told from the call's seat and verb as today; no tool is marked as a first-view tool and no
owner is named.
- **A-360** (completes A-354; principles 1 and 3) "Its current form" is the first view and its changes: the indexer
also says each change of `modules` on its views family (`mesh-delivery-indexer.views.modules`, in its `emits`) when a
new trunk revision changes it, so grants hears a newly merged module without asking again.
- **A-361** (clarifies A-357, ADR 0614 §3; principle 7) "Exactly as from `invokes`" includes the cycle rule: a package's
seat call words count among the invokes of every module built against it when the graph leaves out the edges that lie
on a cycle, so a call word's edge on a cycle is left out like an invoked verb's.
- **A-362** (completes A-353; principles 3 and 7) The seat list a runner starts with (mesh-sdk's bootstrapped list)
holds `mesh-delivery` beside `mesh-catalog`, so `mesh-delivery.source` is admitted before the seat list is read.
+10 -2
View File
@@ -3,7 +3,7 @@ layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
updated: 2026-11-12
decisions:
- "ADR 0500"
- "ADR 0673"
@@ -45,10 +45,12 @@ decisions:
- "ADR 0573"
- "ADR 0571"
- "ADR 0567"
- "ADR 0565"
- "ADR 0564"
- "ADR 0563"
- "ADR 0551"
- "ADR 0548"
- "ADR 0540"
- "ADR 0539"
- "ADR 0534"
- "ADR 0525"
@@ -72,6 +74,10 @@ decisions:
- "ADR 0259"
- "ADR 0235"
- "ADR 0233"
- "ADR 0330"
- "ADR 0126"
- "ADR 0112"
- "ADR 0084"
---
# 47, milestone G — every line milestone G builds
@@ -82,7 +88,7 @@ file on 2026-11-10, and is cited as "design 47 G §n". A sentence that holds a d
stayed in the main file, and G builds what it says from G's delivery. A section named in italics is in this part, or,
when it is not here, in the main file.
The part is eighteen files, each read whole, its design lines numbered across them:
The part is twenty-two files, each read whole, its design lines numbered across them:
- [G-1-pipelines-registration-and-steps](G-1-pipelines-registration-and-steps.md): pipelines per module: registration, watched branches, steps and policy, what holds a delivery, approval, groups, dependencies, a publish.
- [G-2-pipelines-sizes-checks-and-tools](G-2-pipelines-sizes-checks-and-tools.md): 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.
@@ -104,5 +110,7 @@ The part is eighteen files, each read whole, its design lines numbered across th
- [G-18-fix-round-six-seven](G-18-fix-round-six-seven.md): the fix round of §587 to §676: the approval mark once no verb names its callers, the keeper's snapshots, the recovery module's verbs, the node-engine's kept entries, the gate before a marked verb's running, develop's D-16 to D-49, and a delivery's wait on another machine's network (§677 to §705).
- [G-19-outside-wait-and-refusals](G-19-outside-wait-and-refusals.md): a delivery's wait on an outside cause, written whole once (§706, in place of §705), and the finding writer's own refusals in every bundle (§707, amending §580).
- [G-20-lead-answers](G-20-lead-answers.md): the lead's one-line answers to develop's questions, each tested against the principles (A-1 on).
- [G-21-provider-and-revert-ask](G-21-provider-and-revert-ask.md): the provider a requirement points to, the named seat and the contribution edge (§708, §709, completing §357), and the assigner's ask of a held revert (§710, completing §300), the edge an acting rule gives (§711, completing §47's kind 5), the validation before take-in (§712, §713, completing §16), the take-in and groups of every watched branch (§714, §715), where the gate keeps a group's members (§716) and the seats of the status view's and the assigner's views, followed before their holder is known (§717).
- [G-22-lead-answers-2](G-22-lead-answers-2.md): the lead's answers continued, from A-182.
Design 34's milestone [every-event](../34-milestones/every-event.md) and design 46's milestone [7-approval-mark](../46-milestones/7-approval-mark.md) ship in milestone G's release, built in its repositories beside G's lines (lead-47, 2026-11-11 15:30 CET); where one of their lines and a line of G differ, G's later line holds and the other is corrected in place (§624 to §626).
@@ -0,0 +1,34 @@
---
layer: to-be
status: draft
updated: 2026-11-12
decisions: ["ADR 0593", "ADR 0606", "ADR 0429"]
code: [mesh-host, mesh-catalog]
---
# A placed module whose build failed: the lead's answers for a small fix on main
The small fix of a provisioner held unhealthy since 2026-11-09: a placed module whose newest build the builder holds
failed (a Go package, which the builder refuses today) is never published by the node-engine, so the provisioner waits
for its composition at every pass, says itself unhealthy, and the assigner holds every new provisioner build. The fix
goes alone on main and milestone G joins it, since G rewrites the same files.
- **U1** (who answers): the node-engine, which publishes for every placed module (ADR 0593 §6), writes a refused key at
the newest fingerprint for a placed module whose newest build is failed, with the build's words; a build queued or
running stays unpublished (P2, P3).
- **U2** (the Go package build itself, against ADR 0606 §1 and §7): not in this fix; milestone G builds it (P5).
- **U3** (whether mesh-wireguard is a module the mesh provides): not in this fix; the node-engine's special case stays
until a design says otherwise (P7).
- **U4** (a lasting unpublished hold in the provisioner's own health): not in this fix; noted on the issue the fix runs
under (P7).
- **U5** (held fingerprints of superseded provisioner builds): after the fix is delivered, the eight older holds are
released through the assigner's hold ask, told to the operator in one line; their cleanup by the assigner is noted
(P3, P7).
- **U6** (the provisioner's comment at holder.go:113-114 says an unpublished module stops retirement on the whole node;
the code keeps only that module's keys): the comment is corrected to the code, in the same group (P3, P8).
- **U7** (the provisioner's retiring asks whose id is over 64 characters are refused at every pass, and the key's why says
it waits for an open ask): the ask id is bounded, made from the key, and a refused ask is said in the key's why and
in provisioner_health, in the same group (P3).
- **U8** (develop, U1's trigger): the node-engine writes U1's refused key on the event that the placed module's newest
build ended failed, not at the next pass some other input starts; a refusal written late is a wait nobody can read
(P1, P3).
@@ -0,0 +1,59 @@
---
layer: to-be
design: 50
milestone: resolver-wait
updated: 2026-11-12
code: [mesh-catalog modules/systemd-resolved, mesh-host internal/compose/resolution]
decisions:
- "ADR 0678"
- "ADR 0247"
- "ADR 0223"
---
# Design 50, milestone resolver-wait — the own resolver's file waits longer than its pass
**What it builds.** On a machine where a module holds the node seat `node-resolver`, the file `/etc/resolv.conf`
gives its programs a wait longer than the own resolver's pass over one silent mesh resolver to the next, so a lookup
behind a silent first mesh resolver answers late instead of failing (ADR 0678 §1). The value is the main file's,
[§2](../50-split-dns-on-a-machine-with-a-vpn-client.md#2-the-file-and-the-resolver):
`options timeout:3 attempts:2 edns0`. This part holds every line the milestone builds, cited as "design 50 resolver-wait §n". It is built under
issue 1748.
**Where.** Two repositories: mesh-catalog (the module `systemd-resolved`, holder of `node-resolver`: §1) and `mesh-host`
(the node-engine's names check and its test: §3). One milestone, its two pull requests one delivery group with one
check. No ordering edge joins them: once §3's test reads the options from the catalog's template, each repository's
check passes against the other's main, whichever merges first.
## Lines
1. **The own resolver's template carries the longer options.** The module `systemd-resolved` renders the file twice
from one template, its facts `resolvers` (the live file) and `kept` (the kept copy beside resolved's
configuration, which the guard puts back); both carry the options line of design 50 §2, `options timeout:3
attempts:2 edns0`, in place of `options timeout:1 attempts:2 edns0`, which goes from the template. The one
`nameserver` line, the machine's private address, is unchanged. The node-engine writes both when it renders the
module, the kept copy first (design 50, *As built*), so the guard never compares a new live file with an old copy.
Read by `<node>/node-reads.read` of the file and by the uplink seat's `resolvers` verb, as today. Checked by the
module's manifest test, which asserts the one server and this options line in both facts; the guard's own test,
which holds a literal of the file, carries the new line.
2. **Nothing else of the resolver changes** (ADR 0678 §2). resolved's drop-in stays as it is: no cache, the mesh's
resolvers as its servers in the order of the module's roster (ADR 0223 §9), `~.`, no fallback, its extra listener
on the private address. The uplink modules' files (`networkmanager`, `systemd-networkd`) keep both mesh resolvers
with `timeout:1 attempts:2`, held by the existing test over every uplink's resolver file. The captured copies of
the catalog in other repositories' test data are left as they are: no check reads them.
3. **The names check judges the file with the wait its programs give it.** The node-engine's names check already
bounds each ask by the `timeout:` of the file it judges, never under 1 s; that stays, so on a machine holding
`node-resolver` each ask's bound becomes 3 s. Its test that composes the catalog's own resolver on a machine
reads the expected options line from the catalog's `systemd-resolved` template beside it, in place of the literal
`timeout:1 attempts:2 edns0` it asserts today (ADR 0678, *How it is checked*). The reason: a test literal of
another repository's template fails one repository's check on whichever merges first, the kind issue 1748's note
of 2026-11-10 met.
4. **The value stays 3 s per try until the pass can be read.** design 50 §2's `timeout:3 attempts:2` stays ADR
0678's starting value until a read of resolved's own server switches exists. A restart of resolved and lookups
at the own resolver with its first mesh resolver silent answered in 44 to 46 ms (the lead's reading of
2026-11-12 18:05 CET, issue 1748), so a restart and a timed ask do not catch the pass. After the delivery, on the
workstation first and then the laptop, `<node>/node-reads.read` of `/etc/resolv.conf` shows the one server and
§1's line; the proof that the wait outlasts the pass is §5's.
5. **The live proof.** At the next silence of the first mesh resolver, `mesh-build-go.build_go_steps` lists no Go
build step on a machine holding `node-resolver` failed with `i/o timeout` at that machine's own address. Issue
1748 is resolved once §4's read of the file holds, naming this line as where the proof will show, and reopened
if it fails.
@@ -6,11 +6,13 @@ code:
- mesh-controller internal/catalogue/seats.go, declaration.go, resolve.go
- mesh-catalog modules/systemd-resolved (the resolver, its guard and its verbs)
- mesh-catalog modules/forticlient (the VPN client's adapter)
updated: 2026-10-07
- mesh-host internal/compose/resolution, internal/network (the names check of the file, milestone resolver-wait)
updated: 2026-11-12
decisions:
- "ADR 0247"
- "ADR 0223"
- "ADR 0241"
- "ADR 0678"
---
# 50 — Split DNS on a machine with a VPN client
@@ -66,8 +68,14 @@ not share a machine.
## 2. The file and the resolver
- **The file** names the machine's private address alone, with ADR 0223's options, and begins as every
mesh resolver file does, so the uplink's `resolvers` verb and the node-engine read it as the mesh's.
- **The file** names the machine's private address alone and begins as every mesh resolver file does, so
the uplink's `resolvers` verb and the node-engine read it as the mesh's.
- **The file's options wait longer than resolved's pass** over one silent mesh resolver to the next
(ADR 0678 §1): `options timeout:3 attempts:2 edns0`, a total wait of 6 s, the timeout times the attempts, where
ADR 0223's `timeout:1 attempts:2` gave about 2 s. Its program asks only the own resolver, so it waits on that
pass; a file that lists the mesh's resolvers keeps `timeout:1 attempts:2`, because its programs try the second
after one second (ADR 0678 §2, design 08 §2). The value stays ADR 0678's 3 s per try until a read of resolved's
own server switches exists, since no read today catches the pass (milestone resolver-wait §4).
- **The uplink's holder steps back** from that path on that machine, by the controller's rule. It does not
write the file and lose a race; it is never given the file to write.
- **resolved** is given every mesh resolver as its global servers, with `~.` so they answer every name no
@@ -122,12 +130,24 @@ resolver's hold, and is therefore raised. A VPN that tells resolved its link's D
## 6. What it costs and what it does not do
- The client's search domains route full names. They do not complete short ones.
- A name no mesh resolver answers takes the file's total wait to fail on that machine: 6 s, about 4 s longer
than before (ADR 0678).
- resolved down is no names on that machine. Its `unit` health, and the node-engine's names check against
the address the file lists, say so.
- resolved should take no servers from the resolver file, because its own are configured. The live test
confirms this while a write stands.
- A container started before the resolver keeps the mesh's two resolvers until it restarts.
## How it is checked
| Check | Defends |
|---|---|
| the own resolver module's manifest test: its file names one server, the machine's private address, with §2's options | ADR 0247 §6, ADR 0678 §1 |
| the node-engine's test that composes the catalog's own resolver on a machine reads the options from the catalog's template, not a literal, and its names check bounds each ask by the file's `timeout:` | ADR 0678 §1 |
| the test over every uplink's resolver file: both mesh resolvers, `timeout:1 attempts:2` | ADR 0678 §2, design 08 §2 |
| live, on a machine holding `node-resolver`: at the next silence of its first mesh resolver no Go build step there fails on `i/o timeout` (milestone resolver-wait §5); a timed lookup waits for a read of resolved's own server switches (§4) | ADR 0678 §1 |
| each phase's *Done when* below | ADR 0247 |
## Phases
| Phase | Repository | Delivers | Done when |
@@ -135,6 +155,7 @@ resolver's hold, and is therefore raised. A VPN that tells resolved its link's D
| A — the seat | mesh-controller | `node-resolver` and its verbs; the uplink stepping back | its tests pass; the controller is running on the mesh |
| B — the resolver | mesh-catalog `systemd-resolved` | the module, its guard and verbs | its tests pass; assigned to every machine that runs the VPN client's module, each one's file names its own address, `routes` answers |
| C — the adapter | mesh-catalog `forticlient` | `split-dns` required; the adapter | its tests pass; on the laptop, connected, a company name and a mesh name both resolve and no rewrite stays raised |
| resolver-wait | mesh-catalog `systemd-resolved`, `mesh-host` | design 50 §2's options (ADR 0678), under issue 1748; its part is [`50-milestones/resolver-wait.md`](50-milestones/resolver-wait.md) | its tests pass; *How it is checked*'s live row holds |
## As built
+2 -2
View File
@@ -29,7 +29,7 @@ document is written and this one's status becomes `implemented`.
| [`17-raising-a-mesh.md`](17-raising-a-mesh.md) | How a mesh comes into existence, and how a machine joins one that exists | ADR 0067, ADR 0006, ADR 0005 |
| [`18-building-a-module.md`](18-building-a-module.md) | How a build is modelled, and why a recipe that is always a Dockerfile does not fit what a module is | ADR 0040, ADR 0039, ADR 0009 |
| [`19-the-module-protocol.md`](19-the-module-protocol.md) | What a module's code and the mesh say to each other; an SDK is an implementation of it | ADR 0074, ADR 0042, ADR 0043 |
| [`20-writing-a-module.md`](20-writing-a-module.md) | A worked guide: one module, four capabilities, four languages, and the packages it publishes | ADR 0074, ADR 0040, ADR 0039 |
| [`20-writing-a-module.md`](20-writing-a-module.md) | A worked guide: what a module is built of, the manifest form the manifest validator accepts for each part, and the packages it publishes | ADR 0578, ADR 0595, ADR 0074, ADR 0040 |
| [`21-the-installation-in-full.md`](21-the-installation-in-full.md) | Every step from a bare machine to a mesh that maintains itself, and what is not yet true | ADR 0067, ADR 0073, ADR 0014 |
| [`22-the-work-ahead.md`](22-the-work-ahead.md) | Everything decided and not yet built, in dependency order, each phase ending at a run | ADR 0074, ADR 0075, ADR 0014 |
| [`23-choosing-a-provider.md`](23-choosing-a-provider.md) | Which of several providers of a kind serves a consumer, and when a module carries its own instead | ADR 0084, ADR 0027 |
@@ -49,7 +49,7 @@ document is written and this one's status becomes `implemented`.
| [`46-the-conversation-with-the-operator.md`](46-the-conversation-with-the-operator.md) | **Designed.** The mesh tells and asks its operator over channels that are holders of two kinded benches, `channel` and `intake`, declaring capabilities from a fixed vocabulary; the router orders them by work context and never lowers the bar; an answer that performs an action is checked and performed by the controller, on a TOTP code or a verified Telegram sender, never on a desk click alone; operator messages addressed or answered by the mesh's responder, untrusted input kept as data, references for what words may not carry, and a recoverable factor; Telegram first, in six phases | ADR 0234, ADR 0227 |
| [`47-delivery-from-commit-to-delivered.md`](47-delivery-from-commit-to-delivered.md) | **Designed.** A delivery is one commit in one repository, from its pull request's head to every machine; a delivery group is deliveries sharing a branch name, ordered and checked as one future state; both owned by the `mesh-delivery` module with one state table, its state on the bus, every transition said, noted on the commit and shown on the pull request; the controller keeps the planner, the gate, sending and the rollout, and the core's own updates never wait for the module | ADR 0239, ADR 0238, ADR 0236 |
| [`49-the-mesh-in-domains.md`](49-the-mesh-in-domains.md) | **In progress.** Every concept belongs to one of eleven domains, which owns its one word; the glossary is organised by them and is the authority, a retired word is named on its replacement's line with its scope, and two checks hold this repository's documents and the catalog's tool descriptions to it | ADR 0244, ADR 0006, ADR 0008, ADR 0302 |
| [`50-split-dns-on-a-machine-with-a-vpn-client.md`](50-split-dns-on-a-machine-with-a-vpn-client.md) | **In progress.** A machine whose VPN client pushes servers of its own runs a resolver of its own, on a node seat, that routes the VPN's domains over its link and every other name to the mesh's resolvers; the VPN client's module carries the adapter | ADR 0247, ADR 0223 |
| [`50-split-dns-on-a-machine-with-a-vpn-client.md`](50-split-dns-on-a-machine-with-a-vpn-client.md) | **In progress.** A machine whose VPN client pushes servers of its own runs a resolver of its own, on a node seat, that routes the VPN's domains over its link and every other name to the mesh's resolvers; the VPN client's module carries the adapter | ADR 0247, ADR 0223, ADR 0678 |
| [`51-the-registries-keep-what-is-named.md`](51-the-registries-keep-what-is-named.md) | **In progress.** The artifact store, the package registry and each machine's container runtime get tools that say what they hold, what names each thing and what could go; every removal is a dry run unless a person asks and says why, and what decides is always a record | ADR 0251, ADR 0189 |
| [`52-shares-and-mounts.md`](52-shares-and-mounts.md) | **In progress.** A node shares one folder at a time through `nfs-server`, which provides it as a provision to the private network only, mapping every client to the folder's owner; a node that asks mounts it through `mounts`, a mount unit and an automount per share, so nothing mounts or fails at boot; a device that comes and goes is an occasional source whose absence is a state | ADR 0263, ADR 0051, ADR 0092 |
| [`53-the-issue-tracker.md`](53-the-issue-tracker.md) | **Designed.** The mesh keeps its issues in one module, `issues`, on the control-node: every number from one counter advanced by compare-and-set, the cycle's issue rules refused at the write, an event per change; an issue lives whole in the tracker, hq keeping no issue content (ADR 0291) | ADR 0275, ADR 0291 |
+5 -2
View File
@@ -25,8 +25,11 @@ own, ready for the review by architectural domain. An eleventh, `hq-record-write
research effort to the knowledge base from a draft (playbooks 01 and 02) and follows its review to approve. An agent that researches, decides, designs or reviews a design starts
from the knowledge base and maps each interaction onto a mesh path. An agent that prepares, builds or reviews code
starts from the milestone's part of its design alone, builds what its design lines say, and decides nothing: what
the design does not answer is a design question for the operator, answered in the design before any code
(playbook 07). The lead writes only the handover, never an agent's way of working and never an answer to a design
the design does not answer is a design question, answered in an hq commit before any code (playbook 07). The prepare
also reads the code each design line lands in and lists every place where a line and the code disagree. The lead
answers each such question in one line against [`00-META/principles.md`](00-META/principles.md); 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 to `hq-design-writer` as a design line (playbook 07, step 0b). The lead writes only the handover, never an agent's way of working and never an answer to a design
question. A subagent runs in the session that started it, from hq: its way of working comes from its definition and hq's playbooks, its knowledge from the knowledge base or, for building, the design; 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.
## The development cycle