Merge pull request 'Design 47 milestone G: develop answers batch 4 (G-12, G-14), the recovery module and the operator's answers of 2026-11-11' (#548) from design/47-G-develop-answers-4 into main

This commit was merged in pull request #548.
This commit is contained in:
2026-11-11 12:14:42 +00:00
15 changed files with 853 additions and 60 deletions
+6
View File
@@ -974,6 +974,12 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564, 0668.
at one moment; **restore** — bringing it back beside the live data, never over it.
- **stream snapshot** — the bus's own backup of each stream, taken by the module holding `mesh-broker`
(ADR 0235).
- **recovery module** — the holder of the seat `mesh-recovery`, held once for the mesh: under an account of its own it
takes a restore point of a module's kept data on the live bus before the module moves across a `form` change, sets
the live data aside and restores the point at a revert, and lists and removes restore points; the bus module and
the snapshot account restore nothing into the live bus, and the backup holder only stores copies (design 47 G §554).
**aside** — live kept data a restore set aside, removed only by the operator's `discard-aside`.
*Not:* ~~restorer~~, ~~restore account~~
- **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
@@ -200,7 +200,7 @@ check and deliver milestone G itself (ADR 0667 §2); each of them says so in a l
`operator-channel`'s accepted kind `ask`, on its work queue by the runner's name (design 46 §10, *Asks, warrants and answers on the bus*;
the seat has no verb `ask`), and heard on its event `decided` (ADR 0666 §7, §10). A step with nothing to do ends at once as
`nothing to run`, never as passed.
- §23 **The policy.** Each step's status, per module and per artifact, is `required` (runs; holds the delivery until it
- §23 **The policy.** (§553) Each step's status, per module and per artifact, is `required` (runs; holds the delivery until it
passes), `optional` (runs; a failure is shown and may be accepted) or `bypass` (nothing runs; said as bypassed),
kept in the registration and never in a manifest: the manifest validator refuses a manifest that names a status
(ADR 0666 §7; research 086, R3). A `required` step that ends `nothing to run` holds the delivery and names the step.
@@ -267,7 +267,7 @@ check and deliver milestone G itself (ADR 0667 §2); each of them says so in a l
prerequisite step `bypass`) ends the pipeline refused within five minutes, naming the step and what it waits for,
never as a silent wait (research 086, R5); a step whose seat has no holder ends its pipeline failed, naming the seat
(ADR 0666 §17).
- §33 **A failed head is said to its author, then to the operator.** A `required` step that failed, or ended `could not
- §33 **A failed head is said to its author, then to the operator.** (§540) A `required` step that failed, or ended `could not
run`, on a watched branch's head is the runner's finding `pipeline-failed`, naming the module, the branch, the commit
and the step; the condition keeper raises it as the commit's author's for two hours and then the operator's, as ADR
0505 §5 and §6 have a delivery whose cure is its author's new head, and clears it when a newer revision of that branch
@@ -42,7 +42,7 @@ each the design's choice with its reason, §468 to §479 those of the runner's,
operator, 2026-11-11 at the terminal (develop question D-12): a refusal would be "hard-coded logic for no good
reason". How it is checked: unit (definition reader), a row where a verb G adds with no `class` takes `read`'s
callers; unit (seat generator), a definition with a verb naming no `class` is generated.
- §457 **The put session's wire form.** (§489, §490) §345's put session runs on the subject `<bundle call subject>.put.<call id>`.
- §457 **The put session's wire form.** (§489, §490, §518) §345's put session runs on the subject `<bundle call subject>.put.<call id>`.
Each chunk is one message whose body is the value's raw bytes, at most half the bus's maximum payload as the
connection reads it from the server, with the header `Mesh-Put-Seq` (a decimal from 1, one more each chunk) and, on
the last chunk alone, `Mesh-Put-Last: true`. The tool runner answers each chunk with `{seq}`, the sequence it took,
@@ -62,7 +62,7 @@ each the design's choice with its reason, §468 to §479 those of the runner's,
its own version". Why: the operator, 2026-11-11 at the terminal (develop question D-19); a build without a version
is §371's and §373's. How it is checked: whole (Go, TypeScript and image build modules), a built image's tag and
label, a Go program's `main.meshVersion` and a Node program's own version are the reserved version.
- §459 **`SeatsToPassOn`'s input.** mesh-sdk's `catalogue.SeatsToPassOn` takes the holdings on record, the
- §459 **`SeatsToPassOn`'s input.** (§536) mesh-sdk's `catalogue.SeatsToPassOn` takes the holdings on record, the
assignments (each a node, a module and its placement's watched branch), what the caller heard of each node and
module from the node-engine seat's `running` (a fingerprint, `stopped`, or nothing heard since its consumer began),
and a reader of entries the caller passes over its own view of the catalog half, which answers an entry by its
@@ -77,7 +77,7 @@ each the design's choice with its reason, §468 to §479 those of the runner's,
(ADR 0596 §2), and the function stays a pure rule (develop question D-14). How it is checked: unit
(`SeatsToPassOn`), a table with a row per case: a heard fingerprint, nothing heard (the branch's entry taken), a
`stopped` holder passing no seat, and an entry not answered keeping the holder on record and saying it.
- §460 **An asker says whether its key is still open.** mesh-sdk's kept-call form takes, beside the ask's key, a
- §460 **An asker says whether its key is still open.** (§531) mesh-sdk's kept-call form takes, beside the ask's key, a
function `open` the asker passes, which reads the asker's own state and answers whether that key is still open, and,
where one exists, the event that replaces the ask; it asks again only while `open` answers yes and no replacing
event was heard. An error of `open` is written in the asker's journal naming the key, and the form asks again: a
@@ -265,7 +265,7 @@ each the design's choice with its reason, §468 to §479 those of the runner's,
its own fact (ADR 0596 §2) (develop questions GIT12 and REG1). How it is checked: unit (`mesh-builds` contract),
`reserved` with `name` read; whole (builder, registry holder), a Go publish named by its module path is checked
against the module's reservation, and an unknown name refused.
- §479 **A branch's catalog view is named with its branch.** (§495) In mesh-sdk's `go/views` reader and process reader, a
- §479 **A branch's catalog view is named with its branch.** (§495, §521, §532) In mesh-sdk's `go/views` reader and process reader, a
first view of a per-branch state is named `<owner>.<state>.<branch>`, the branch escaped as a key's name is (§73),
so `mesh-catalog.catalog-revisions.<branch>` names one view per watched branch, a part reads several in one reader,
and `<module>_first_views` lists one row per branch (§358, §365). A view of a state that is not per branch keeps
@@ -62,7 +62,7 @@ reason. What a question of another kind leaves open waits for the operator and i
## The review of §456 to §479, round 1
- §483 **Main's step keys stay readable while main's askers run.** (§600, §601) A build seat's holder of G keys a step whose ask
- §483 **Main's step keys stay readable while main's askers run.** (§600, §601, §544) A build seat's holder of G keys a step whose ask
came on main's subject `mesh.seat.<seat>.accept.<kind>`, with no asker's name, as main does, `<node>.<id>`, and a
step whose ask names its asker as §481 says; so a builder or checker of before G, which asks on main's subject,
finds its answer under `<node>.<id>` through the rollout one node first and after a revert. Every asker of G asks
@@ -106,7 +106,7 @@ reason. What a question of another kind leaves open waits for the operator and i
(review round 1, running and testing 5, health and events 3). How it is checked: whole (runner), with a fixture's
small cap a step ends and is said, `full` is said and a start refused, and on `room` the entry is appended and the
start taken.
- §487 **The forge module learns a commit's fingerprint from every step end.** §475's `seen.<commit>.<module>` is
- §487 **The forge module learns a commit's fingerprint from every step end.** (§548) §475's `seen.<commit>.<module>` is
written from every `step-ended`, whatever its commit, a run on request's included, so a commit that failed in a run
on request before its pull request was opened is known at its merge. A `seen` key is deleted by §135's rule once
its commit has been a watched branch's head or the head an open pull request names and is neither, and otherwise
@@ -128,13 +128,13 @@ reason. What a question of another kind leaves open waits for the operator and i
not answer the new calls (ADR 0595 §9; review round 1, security and upgrade 2). How it is checked: unit (tool
runner), each word stated with its family; whole (mesh-sdk), against a tool runner that does not state
`put-sessions` a large put fails naming it; the delivery plan of G orders the modules after the tool runner.
- §489 **Who publishes on a put or get session.** On `<bundle call subject>.put.<call id>` only the module's own
- §489 **Who publishes on a put or get session.** (§518) On `<bundle call subject>.put.<call id>` only the module's own
account publishes the chunks and only the tool runner of its node answers them; on `<bundle call subject>.get.<call
id>` only the tool runner publishes the chunks and only the module's account answers. The grants part grants them
so, as it grants a module's call subject today (ADR 0363 §2). Why: §457's subjects lie under a subject the module
already owns, so the grant follows the record (review round 1, security and upgrade 3). How it is checked: whole
(grants part, tool runner), a chunk published by another module's account is refused by the bus.
- §490 **The get side of a session.** A get of a value over the limit (§457) runs so: the bundle subscribes to
- §490 **The get side of a session.** (§518) A get of a value over the limit (§457) runs so: the bundle subscribes to
`<bundle call subject>.get.<call id>` before it makes the call; the tool runner's answer to the call is
`{chunked: <count>, size, digest}`, the value's `sha256:` digest; the tool runner then publishes each chunk as a
request on that subject, with `Mesh-Put-Seq` and, on the last, `Mesh-Put-Last: true`, and the bundle answers each
@@ -170,7 +170,7 @@ reason. What a question of another kind leaves open waits for the operator and i
release's publish (kind 1), so the runner's manifest is read by every part that takes it in. Why: readers first
(ADR 0667 §4; review round 1, security and upgrade's note). How it is checked: G's check reads the runner's and the
node-engine's manifests; whole (grants part), the runner's `pipelines` bucket has its cap.
- §495 **Small corrections from the review.** §472's "kind 3 is stale (§441)" reads §375. §472's `toolchain-agreed` carries its digests as they stood when the version was agreed; a digest that changes later at that version is read from `toolchain-reported` (§492) and starts nothing. A sent store already over its cap when §477's
- §495 **Small corrections from the review.** (§530) §472's "kind 3 is stale (§441)" reads §375. §472's `toolchain-agreed` carries its digests as they stood when the version was agreed; a digest that changes later at that version is read from `toolchain-reported` (§492) and starts nothing. A sent store already over its cap when §477's
cap is planned keeps no cap until its seven-day age brings it under, the bus module's plan naming it with its fill
meanwhile; a sent store is planned only for a module §344 names, and the plan refuses caps whose sum passes the
bus's storage limit, naming the sum. §468's "behind" is the age of the oldest message the consumer has not
@@ -197,7 +197,7 @@ reason. What a question of another kind leaves open waits for the operator and i
`branch-unprotected` stands (§17) raises none either. Why: the watchman already times each take-in wait (develop
question on §12). How it is checked: whole (watchman), a head behind a pending revision raises nothing for 60
minutes, then `take-in-waits`.
- §498 **A wait names its clock and its kind.** (§608) `meshwants.Wait`, the runner's wait body and the node-engine seat's
- §498 **A wait names its clock and its kind.** (§534, §608, §542) `meshwants.Wait`, the runner's wait body and the node-engine seat's
gain `clock-from` (the time the bound counts from, §266, §269) beside `bound`, and `step`, one of the acting steps
of §203 or `put`, `delivery`, `kept-build`, `backup`; a put's wait (main file, *A put without hold that stands*)
is `step: put` with the node, the module and `fingerprint`; a dependent's delivery wait (§376) is `step: delivery`
@@ -205,7 +205,7 @@ reason. What a question of another kind leaves open waits for the operator and i
fingerprint, `held`, since and bound, from which the watchman seeds. Why: one wait form, told apart by `step`
(develop questions WM6 and WM10). How it is checked: unit (contracts), each field read; whole (watchman), a put's
wait raises `put-stands` and a pipeline's wait raises nothing for it.
- §499 **The node-engine says a kept build's wait, and its version is read from the catalog.** (§609) A node-engine of G
- §499 **The node-engine says a kept build's wait, and its version is read from the catalog.** (§541, §609) A node-engine of G
waiting for a kept build says `wait-held` on its seat with `step: kept-build`, `node` the build node, and why
(`node away` or `build no longer kept`), and `wait-ended` when it applies; its have's value carries the same as
`waits`. The watchman takes the keeping node from the builder's `build.ended` (§379), never its done key. Each
@@ -252,7 +252,7 @@ reason. What a question of another kind leaves open waits for the operator and i
never computed again from the manifest whole, whose encoding may differ; the module's value and §445's
`manifests` key are that digest. Why: the bytes, not the map, are what the node-engines before G read (develop
question IX16). How it is checked: unit (indexer), the entry's digest is the digest of the bytes read.
- §507 **The watched branches before the first registrations.** Every reader of a trunk's source revision reads, per
- §507 **The watched branches before the first registrations.** (§532) Every reader of a trunk's source revision reads, per
watched branch the registrations name, `source.<branch>` through the gate's verb `source`; before the first
registrations (§95, §353), the watched branches are the branches the gate's `source` answers, which G's switch
carries in from `source.main` as the branch `main`, read from the key and never fixed in code (§7). The assigner's
@@ -260,7 +260,7 @@ reason. What a question of another kind leaves open waits for the operator and i
and one source of the branches for every reader (develop questions Q18 and VW0, and the bridge's FB9). How it is
checked: whole (assigner, bridge), before the first registrations a want is written from `source.main`, and no
part's code holds `main`.
- §508 **The node-engine claims its seat as one module.** (§609) The seat `node-engine`'s definition in mesh-contracts no
- §508 **The node-engine claims its seat as one module.** (§527, §609) The seat `node-engine`'s definition in mesh-contracts no
longer marks it held by the engine; the manifest validator accepts one module of a revision claiming it and
refuses a second, naming both, in place of refusing every claim, and §494's release carries the rule. The
node-engine's guard reads the holder from the seat list (§364). Why: §364 has the node-engine's manifest claim the
@@ -274,7 +274,7 @@ reason. What a question of another kind leaves open waits for the operator and i
Why: one message per move, the pull request where there is one (develop questions FB1, FB2 and FB7). How it is
checked: whole (bridge), an admitted push writes `pushed`, a merge `merged`, and a move of an unprotected branch
writes nothing.
- §510 **A merge the switch carries is performed.** (§608) A `merges` key the old bridge had not performed, an Approve the
- §510 **A merge the switch carries is performed.** (§608, §542) A `merges` key the old bridge had not performed, an Approve the
operator gave before G, becomes an act (§169) that the bridge performs through `merge-pull`, judged as every merge;
while `merge-pull` refuses it for missing registrations (§95) the act stays unanswered and is performed again on
`registration-changed`. No other merge act is made after G (§167). Why: §169 drops nothing the operator approved
@@ -301,7 +301,7 @@ reason. What a question of another kind leaves open waits for the operator and i
`membership.BusBinding`, built in mesh-sdk. Why: the objects template has no default (develop questions NATS1 and
on §399). How it is checked: unit (bus module's plan), each planned objects store has its cap; unit (membership),
both fields read.
- §514 **The teller tells through `notify`, on every channel.** For `widens` (§71, §335), `recovery-merged` (§81)
- §514 **The teller tells through `notify`, on every channel.** (§522) For `widens` (§71, §335), `recovery-merged` (§81)
and a `forge-made` record (§170) the teller calls the seat `operator-channel`'s verb `notify`, named in its
manifest's `invokes`, with `every-channel: true`, which the router sends on every channel's `send`, as design 46
announces a break-glass act. The words are the operator's to approve and wait for them (develop questions TL1 to
@@ -0,0 +1,573 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0672"
- "ADR 0667"
- "ADR 0666"
- "ADR 0660"
- "ADR 0656"
- "ADR 0636"
- "ADR 0626"
- "ADR 0619"
- "ADR 0618"
- "ADR 0614"
- "ADR 0596"
- "ADR 0595"
- "ADR 0564"
- "ADR 0505"
- "ADR 0404"
- "ADR 0363"
- "ADR 0240"
- "ADR 0235"
---
# 47, milestone G: the operator's words of 2026-11-11 and develop's fourth batch of questions
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§522, §542 and §551 to §553 are the operator's answers of 2026-11-11 at 12:11, 12:33 and 12:41 CET at lead-47's terminal. §543 to §550
answer what the four reviewers found in round 3 of [G-13](G-13-review-round-3.md) (§600 to §609), which has no round
of its own after it. Every other line answers a question
develop met after [G-11](G-11-develop-answers-review.md), of internal form only (playbook 07, step 0b), each the
design's choice with its reason. What a question of another kind leaves open waits for the operator and is said in the
line. The operator's answer of 12:55 CET (the recovery module, §554 and §555) and the
review of §516 to §553, round 1 (§556 to §558), are in [G-14](G-14-recovery-module-and-review.md); the findings of
that round are written into the lines they name, here. Questions that an earlier line already answers have no line here: develop reads that line.*
## Kept data on the bus: restore points and what is set aside
- §516 **A restore point's bytes, and the forms of `snapshot-kept` and `restore-kept`.** (§551, §554) The seat
`mesh-recovery`'s two verbs (§554) take and answer the forms mesh-contracts' definition gave the seat `mesh-broker`'s,
which move to `mesh-recovery` and which this line makes the design's: `snapshot-kept` takes `module`, `kept` (the
kept data's local name), `form`, `fingerprint`, `why` and `catch-up` as `{tool, arguments}` (§451); its point
(the module, the kept data, its form, the fingerprint, `snapshot`, `consumers` with each durable consumer's
stream, name and acknowledged sequence, `size` in bytes, `at` and `catch-up`) comes on its event `ended` (§554); `restore-kept` takes
`module`, `kept`, `form`, `fingerprint`, `why`, `from-backup` (yes or no) and `snapshot` (the restore point's name),
and refuses `aside-only` and `discard-aside`, answering `error` naming them: setting aside without restoring and
discarding an aside are the operator's hand tools alone (§554); its result (`restored`, `aside`, `missing`, true when the
point is not in the recovery module's directory, and `error`) comes on its event `restored`. Both verbs answer only
`accepted`, `busy` or `error` (§554). No argument `local-name` or `from` is taken, and a
catch-up is never text.
- **Where a point lives.** The recovery module takes a point through the bus's stream snapshot under its own account
(§554), one bus object of the kept data at a time (its stream, or the stream under its bucket or object store),
with ADR 0235 §10's pacing and §11's bounds, and writes it with the point's record under a temporary name, renamed to its own name only once the point is
whole (as ADR 0235 §2 writes the dump), in its own data directory, `points/<module>/<kept>/<form>-<fingerprint>`;
at its start it removes every point left under a temporary name, so a point cut off by a restart, the bus away or
its bound is never listed nor restored; each token escaped as a key's name is (§73) and a token that is
empty, `.` or `..` refused; that name is the point's `snapshot`.
- **Its copy.** That directory is an item of the recovery module's manifest `data`, backed up by the holder of
`node-backup` on its node as every item is (ADR 0404), which stores the copy and nothing else. When
`restore-kept` answers `missing`, the node agent calls it again with `from-backup: yes`: the recovery module asks
the seat `node-backup`'s `restore` of its item for that directory, which brings it back beside its path as that
verb always does, takes the point from there and restores it; when no repository snapshot holds it, `restored`
says `failed` with `restore point missing`, and §300 holds the revert.
- **The aside, and a restore that fails.** `restore-kept` sets the live kept data aside by taking a stream snapshot
of each of its bus objects into `aside/<module>/<kept>/<at>` in the same directory, reads the aside back to check it (`at` the UTC time written
`20261111T121200Z`, with `-2`, `-3` and on added when that name exists), only then removes the live objects, which needs no person's warrant, the data staying in the aside (the operator,
13:06 CET, §554),
restores the point under their names and sets each consumer to its recorded sequence. Each step is written first
in the aside's record (`set aside`, `removed`, `restored`): when a step after the removal fails, `restore-kept`
restores the aside under the live names and sets the consumers back before it answers `failed`, and at its start
the recovery module does the same for every aside whose record ends at `removed`, so a failed or stopped restore
never leaves the kept data gone. The aside's `id` is that directory's name, its `name` the bus name it was live
under, its `size` its bytes on disk; nothing of an aside stays on the bus. `recovery_discard_aside` (§554) removes that directory
and nothing else, an `id` holding `.`, `..` or `/` refused. A restore point is removed only as §551 says.
This completes §405 and §406. Why: the operator's answer of 12:55 CET (§554) puts the taking and restoring of a
module's kept data on the live bus on a module of its own; the server restores a stream only where no stream of that
name exists, so the live data is set aside as a snapshot before the point takes its name (develop questions BKP1,
BKP3 and NATS4's argument names; review round 1, running and testing 1, seams and tools' notes). How it is checked:
unit (mesh-recovery's contract), each verb's input and answer read, an input naming `from` refused; a node-engine's
`restore-kept` with `aside-only` or `discard-aside` refused, naming them; whole (recovery
module, backup holder, bus), a point is taken, its directory removed, brought back with `from-backup` and restored,
the live data's aside listed with its size and the consumer at its recorded sequence; a point missing from both says
`restore point missing`; a restore made to fail after the removal, and one stopped there and started again, leave
the live data as before; a point cut off half way is removed at start and never listed.
- §517 **The recovery module lists what it holds.** The seat `mesh-recovery`'s read verb `kept-points` (class `read`,
§391's default callers) lists every restore point and aside in its directory with its module, kept data, form,
fingerprint, size and `at`, and the act that runs, with since when and what it waits on (§554); its tools are `recovery_kept_points`, which reads the same, and
`recovery_expire_points`, which runs §551's removal now (class `act`, §391's default callers, written in the
hand-act log). The directory is the one place they are kept, so no copy can list less: the backup holder keeps no
state of them (its `backed-up` lists the recovery module's item as any item, §302), and no `bus-points` state is
made. Why: a module's tools read everything it holds (*Tools are how a module is spoken to*), and the list is the
holder's own fact (ADR 0596 §2; develop question BKP2; review round 1, seams and tools 4). How it is checked: whole
(recovery module), `kept-points` lists a point and an aside with their sizes, an aside is gone after
`recovery_discard_aside`, and a restart lists the same.
- §523 **A seat event whose subject has parts.** A seat definition's `subject` with `<part>` tokens (§397), such as
`mesh-recovery`'s `ended.<module>.<kept>` and `node-backup`'s `item-started.<node>.<module>` (§554), is said on
`mesh.seat.<seat>.event.` followed by the subject, each part filled in the definition's order: the seat
generator's subject function for the event takes each part as a string in that order and fills each as one token,
escaped as a key's name is (§73), refusing an empty part, `.` and `..`. A reader hears one module's events by the subject with
that module's tokens and `>` after them, and every module's by `*` for each part. Such an event has no key of its
own and is kept as its definition's `keep` says, or on EVENTS when it names no `keep` (§396). The holder's account
publishes it as a seat's holder publishes its seat's events today; no grant changes. Why: §397 fixes the subject's
words, not how a part is filled (develop question NATS4's wire). How it is checked: unit (seat generator), a subject
of three parts made in order and an empty part refused; whole (recovery module), `ended` heard by a reader of one
module's subject.
## The tool runner and its bundles
- §518 **A bundle's put and get sessions run on its channel to the tool runner.** A tools bundle reaches the tool runner
only over the channel the tool runner starts it with (its standard input and output) and holds no bus account, so
§345's put session and §490's get session run on that channel, as every `mesh/` call of a bundle does (ADR 0595
§9); `<bundle call subject>` in §457, §489 and §490 names that call, and no bus subject. A value at or under the
limit is put and got by one call, as today. Over it:
- **Whose object.** A session's `name` names an object of the bundle's own module alone: the tool runner resolves
it in that module's binding (its state's `<state>-objects` or its sent store), and refuses a name of another
module's, naming it. A put's whole size is bounded by that store's cap, and a put whose chunks pass it is refused
at the chunk that does, naming the store.
- **Put.** The bundle sends each chunk as the call `mesh/objects.put-chunk` with `name` (the object), `call` (the
bundle's id for this put), `seq` (from 1, one more each chunk), `last` (true on the last chunk alone) and `data`
(the chunk's bytes in base64, at most half the bus's maximum payload before encoding, §457). The tool runner
answers each with `{seq}` or `{error}`, the bundle sends the next only after a `{seq}`, and the answer to the last
is the put's answer; a `seq` that is not one more than the last taken is answered `{error}` naming both, and a put
not sealed within the call's bound is dropped (§345). The tool runner writes each chunk into the store as it takes it, through the
object store's own chunking, holding no more than one chunk in memory, and seals the object on the last. A put dropped at its bound has its written chunks purged by the tool runner,
which deletes the unsealed object; until then they count against the store's cap.
- **Get.** The tool runner answers the get call `{chunked: <count>, size, digest, call}` (§490), and the bundle asks
each chunk in order by the call `mesh/objects.get-chunk` with `call` and `seq`, answered `{seq, last, data}` or
`{error}`; the bundle checks the whole against `digest`, and a get not completed within the call's bound fails,
naming the object. The tool runner drops a get session it holds when the call's bound passes, or the bundle's
channel closes, whichever comes first.
- **The bound.** The call's bound counts once for the whole session, from its first chunk, never per chunk.
The headers `Mesh-Put-Seq` and `Mesh-Put-Last` of §457 are these fields `seq` and `last`. Nothing of a session is
on the bus, so §489's grants are not made and the grants part grants nothing for it. A module's process, which
holds a bus account of its own, writes and reads its objects on the bus through the object store package directly,
with the store's own chunking, and runs no session. The capability word `put-sessions` (§488) names these two
calls. This amends §345, §457, §489 and §490; in the get the bundle asks each chunk, since on that channel every
`mesh/` call is the bundle's. Why: the operator's answer to develop question D-13, chunks in order, each
acknowledged before the next, is kept whole on the one channel a bundle has (develop questions T-5 and T-7). How it
is checked: whole (mesh-sdk, tool runner), a value of a hundred times the limit, into a fixture state whose
`objects-max-mib` holds it, is put and got back whole by a bundle, its digest checked, a put naming another
module's object is refused, and a chunk out of order fails the put naming both sequences; the grants part's table
test grants no session subject.
- §519 **A bundle's fingerprint is in the environment the node-engine composes.** The node-engine writes
`MESH_FINGERPRINT`, the fingerprint of the want it applies for the bundle's module, into each tools bundle's
composed environment, as it hands it to each process (§205); the tool runner starts each bundle with its composed
environment whole, and reads no catalog entry and no want for it. A bundle started with no value says `own build
unknown` (§424). This completes §424. Why: the want the node-engine applies is the one fact of which build a node
runs (§205; develop question T-12). How it is checked: whole (node-engine, tool runner), a bundle reads the
fingerprint of its module's want.
## The node-engine and the want
- §520 **`running` says a build's health, and the assigner reads health from it alone.** The node-engine seat's
`running.<node>.<module>` gains `healthy` (true or false), the health of the build that runs as the node-engine's
have holds it from the module's declared check (ADR 0240), and the node-engine says `running` again on the same
subject each time `healthy` changes, and for every module its have holds at its own start, so the stream, kept last per subject, holds each build's newest health.
`stopped` (§365, §404) is said only when the have no longer holds the module, never for an unhealthy build. A `running` with no
`healthy`, said by a node-engine of before G, reads as healthy while it stands, as `running` meant on main. The
assigner reads a provider's health for rule 5, the canary's for rules 3 and 4, and a put's end from these events
alone, read as §556 has it, and reads no have: a
provider of which nothing was heard counts as not healthy, so its consumers' new wants
are held as rule 5 holds any, and nothing falls back to the have. `SeatsToPassOn` reads no `healthy` (§459): an
unhealthy holder still holds its seat. The assigner's `reads` of the node-engine's have go. This amends §309 and
§404. Why: the assigner reads no other module's state (ADR 0596 §2), and a stopped build and an unhealthy one are
told apart (develop questions AS15 and AS16). How it is checked: unit (the node-engine seat's fixtures), `running`
with `healthy` read; whole (assigner, node-engine), a provider turning unhealthy holds a consumer's new want and its
recovery lets the want go, with the assigner's account holding no read of the have; whole (assigner, a
node-engine of before G), its provider's `running` with no `healthy` holds no consumer's want; a restarted assigner
holds nothing it did not hold before.
- §524 **The node-engine checks a want's parts by their digests.** A composition's archive, process and container
resources name a source and a digest, not an artifact, so the node-engine matches no resource to a `parts` key. A want
with no `parts` (from an assigner of before G, or for a node whose record does not state `want-parts`, §525) is
applied as today, its resources fetched by their sources and checked by their own digests. A want with `parts` is
applied only when the digest of every resource it fetched is among the values of the want's `parts`, and
every value of `parts` is the digest of a resource it fetched; otherwise it applies nothing of that module and its
have says the phase `parts-differ`, naming the digest. `parts` stays keyed by `<module>:<artifact>` for people and
the delivery record. This completes §379. Why: the digest is what is fetched and checked (develop question H-8). How
it is checked: unit (node-engine), a want whose parts and fetched digests agree applies, and one with a fetched
digest outside `parts` or a part not fetched applies nothing, naming it; whole (node-engine of G), a want written by
an assigner of before G is applied.
- §525 **A node-engine says it reads the want's new fields.** A node-engine of G states the capability `want-parts` in
its node record's capabilities (ADR 0614 §4, ADR 0618 §2), and the assigner writes `version`, `parts` and `kept-on`
(§526) into a node's want only while that node's record states it; that is §379's "once that node runs G's
node-engine". Why: ADR 0614 §4 has each node-engine state what it reads (develop question H-7). How it is checked:
unit (node-engine), the word stated; whole (assigner), a node whose record lacks it gets a want without the fields.
- §526 **A want names the node that keeps a kept build.** For a kept build (§271) the assigner writes `kept-on`, the
build node the builder's `build.ended` names as keeping it (§379), into the want beside `parts`; the node-engine
fetches that build through the seat verb `kept-artifact` of that node, and fetches a want with no `kept-on` from the
store. Nothing else names the keeping node, the builder's `builds` going (§421). This amends §271's "the builder's
done key names it so with its digest". Why: ADR 0596 §2 (develop question H-9). How it is checked: whole (assigner,
node-engine), a kept build's want names its node and is fetched from it.
- §527 **The node-engine's own module is read from the catalog half.** The seat list answers seat definitions alone
(ADR 0636), never who holds a seat. The node-engine's apply guard and its compose read their own module's name from
the catalog half of their view: the module whose entry claims the seat `node-engine`, which §508 makes one per
revision; the manifest validator, beside §508's refusal of a second claim, refuses a revision in which no module
claims it, naming the seat. §364's, §365's and §508's "from the seat list" read so, the condition keeper's too. With
no entry claiming the seat, which only a revision from before G shows, as at a revert of G, the node-engine's own
module is the module of the catalog graph entry its own build's fingerprint names (§443, §205), so a revert of the
node-engine applies. Why: §364 and §508 have the node-engine claim its seat
as one module, so the claim names it (develop question H-15). How it is checked: unit (manifest validator), a
revision with no claim of `node-engine` refused; whole (node-engine), its guard protects the module its catalog
half names, and with a revision of before G the module its own fingerprint's entry names, and the code review finds no fixed name (§428).
## Findings and the condition keeper
- §529 **A part's finding state and stream are declared in its kept data's form.** A part's state `findings` and its
finding stream (§383) are declared as ADR 0666 §4 has a part declare its kept data:
- **A foundation part** (the manifest validator's list: ADR 0619 §1's parts as ADR 0666 §4 changes them, the runner
from G's delivery and the checker until its removal) declares `findings` in its old `state` section and the
stream `<module>-findings` in its old `streams` section, with the events `finding-written.*`, `finding-cleared.*`
and `findings-stated.*`, 7 days and 16 MiB, its oldest dropped (§383), published on
`mesh.stream.<module>.event.<event>.<node>` (ADR 0363 §2).
- **Every other part** asks both in its `requires`: the state `findings` by the template `state`, keeping its local
name and carrying its settings as options (ADR 0595 §11), and the stream by the template `events` under the local
name `finding-events`, with `max-age-days` 7 and `max-mib` 16, which the template drops oldest when full (ADR 0595
§3), its entry listing the three finding events in its field `events`, which the catalog graph entry carries
(§365), published on its binding's subject, `mesh.bus.<module>.finding-events.<event>.<node>`.
- **`mesh-healers`**, whose old `streams` declares `mesh-healers-findings` on main, moves it so in G's change, as
ADR 0595 §11 moves a module G touches. Its old stream stays on the bus as an orphan that `orphans` lists, removed
only by the operator's `drop` (ADR 0595 §4, §5); the grants part deletes the keeper's consumer on it once the
healers' entry no longer declares it (§386), and the healers say `findings-stated` at their start on the new
stream (§384), so the keeper loses no finding.
The manifest validator applies ADR 0595 §11's count of old sections to no part of its foundation list (ADR 0619
§2), so the tool runner's and the checker's manifests are not refused for it, and accepts in a foundation part's
old `streams` the finding stream in the one form above, as it accepts a stream on its list of streams kept until
moved; every other old `streams` entry is judged as today. A part whose list entry is missing is a fault of the
list, fixed in the validator (ADR 0619 §3). The validator refuses a manifest whose state `findings` has no finding
stream in its part's form, or no `consumes` of `mesh-conditions.findings-asked` (§387). The writer (§461) is handed
the subjects from the part's binding or its old stream, as it is handed every subject. The catalog graph entry
carries the finding stream in either form (§365); the grants part makes the keeper's consumer of §386 on an
old stream at the revision that declares it, and on a template stream when it hears the bus module's `created` for
`finding-events` and grants each part publish on its own finding stream's subjects of its own node only, in either form; the
keeper takes the observer and the node from either subject. This amends §383, §386 and §387, whose "into the
manifest's `streams`" holds for a foundation part alone. Why: ADR 0666 §4 and ADR 0595 §11 put a part's kept data
in the templates unless it is a foundation part, and ADR 0619 §2 keeps the foundation's old sections whole; the
local name `finding-events` is the design's choice, since `findings` names the state and ADR 0595 §8 takes one
name per module (develop questions on §387, both validator refusals). How it is checked: unit (manifest validator),
a foundation part's old `findings` and finding stream accepted, a non-foundation part's old sections refused and its
template form accepted; the catalog's validation at G's heads refuses none of §387's manifests; whole (keeper, a
template observer and the tool runner), both forms' events raise a condition; whole (keeper, healers), a healers'
finding raised before their move is still raised after it.
- §540 **The keeper hands a failed head to the operator after two hours.** The condition keeper raises
`pipeline-failed` (§33) with `owner` the commit's author, as the runner's finding names it, and two hours after the
finding's `since` sets the condition's `owner` to `operator-channel`, the seat the operator is asked through (a
seat's name, §451), and says that change as it says any change of a condition; the runner's finding keeps the
author. §451's `owner` and `waiting-on` gain a third form, `forge:<account>`, the commit's author as the forge
names it. A `pipeline-failed` for a newer failing head is a finding of its own, its id naming that commit, so it is
its new author's with two hours of its own. The keeper's tool `conditions_owner` sets a condition's `owner` now, of
class `act` with §391's default callers, written in the hand-act log. The keeper times the two hours on its own conditions from `since`, so after its restart it times them from
`since` again. Why: §33 has the keeper raise it as the author's, then the operator's (ADR 0505 §5, §6; develop
question COND7). How it is checked: whole (keeper), a `pipeline-failed` is the operator's two hours after its
`since`, and so after a restart of the keeper; unit (mesh-conditions' contract), `forge:<account>` read.
- §541 **The keeper hears what a node runs and the healers' attempts as events.** The keeper's `reads` of
`node-engine.have` and `mesh-healers.attempts` go (§434).
- **What a node runs.** The keeper consumes the node-engine seat's `composed`, `running` (with `healthy`, §520, and
`stopped`) and `wait-held` and `wait-ended` (§499), read as §556 has it, and compares them with the wants it hears
from the assigner, to hold a module's findings while it is delivered (ADR 0564 §13), as it did from the haves.
The node-engine says `wait-held` on `mesh.seat.node-engine.event.wait-held.<node>.<module>`, with `step: compose`
and the reason, for each hold its have writes (§300's `restore point missing`, §421's `entry-missing`, §524's
`parts-differ`, §499's kept build), and `wait-ended` on `.wait-ended.<node>.<module>` when the hold ends. The
keeper raises no condition of its own from them: the watchman writes `delivery-waits` for a `wait-held` of the
node-engine standing past composing's 30 minutes (§266), naming the node, the module and the reason, and it clears
on `wait-ended`; a `composed` whose fingerprint is not the want's is covered by that same wait or by the put's
wait (§498).
- **The healers' attempts.** The seat `mesh-healers` says `attempt-changed` on
`mesh.seat.mesh-healers.event.attempt-changed.<condition>`, the condition's key escaped as a key's name is (§73),
carrying the attempt's value whole as its key in `attempts` holds it and `until`, the end of that key's
lifetime, or `removed: true` when the healer ends the attempt; the seat's definition, in its contracts module,
keeps it last per subject, 7 days and 16 MiB (§396). The healers say it again for every open attempt at their start. The
keeper reads it as §556 has it, and drops an attempt at its `until` on its own clock.
Why: the keeper reads no other module's state (ADR 0596 §2; develop question COND8). How it is checked: whole
(keeper, healers, node-engine), an attempt said is shown on its condition and dropped at its `until` or its
removal, a node-engine's hold is heard from `wait-held` and past 30 minutes raises `delivery-waits`, a restarted keeper
holds the attempts it held, and the keeper's manifest declares no `reads`.
## Views of the catalog half, per watched branch
- §521 **An owner's view event names its state, and the key names the branch.** An event an owner says for a per-branch
state (`catalog-revision` for `catalog-revisions`) names the state as the owner's manifest names it,
`catalog-revisions`, and the key, `<branch>.<revision>` (§343); mesh-sdk's `go/views` reader and process reader
route it to the view `<owner>.<state>.<branch>` (§479) by the key's first token, the branch escaped as a key's name
is (§73), which the subject's last token repeats. No event names a state `<state>.<branch>`; mesh-sdk's code at
eed96854, which names it so, follows this line. Why: a state is named once, as its owner declares it, and the key
already carries the branch (develop question on §479 and §495). How it is checked: unit (`go/views`), one
subscription of a per-branch event routes two branches' events to their two views.
- §530 **`revision` says `none yet` in a field.** The seat `mesh-catalog`'s `revision` for a watched branch with no
revision yet answers `{none-yet: true}`, with no revision and no `error`; mesh-sdk's client reads `none-yet` as a
ready, empty view (§495) and never reads an error's text for it; `views.NoneYet` is the client's name for that
answer. Why: an answer is told by its fields, never its words (develop question on §495). How it is checked: unit
(mesh-catalog's contract), the answer read; unit (client), `none-yet` gives a ready, empty view.
- §532 **Every reader of the catalog half follows every watched branch, and adopts a branch registered while it runs.**
Every part that reads the catalog half per watched branch (among them the bus module, the status view, the
condition keeper, the tool runner, the assigner, the grants part and the runner) follows one view per watched
branch: the branches the registrations name, read by `registrations` at its start and followed by
`registration-changed`, and before the first registrations the branches the gate's `source` answers (§507); no part
names a branch in its code, the view's `main` included.
- **Adopted after the start.** mesh-sdk's views reader adopts a view after its start and drops one: on
`registration-changed` adding a watched branch the part adopts `<owner>.<state>.<branch>`, which takes its first
view at once and then follows; on one removing a branch the part drops the view and its row of
`<module>_first_views`.
- **One subscription per event.** The reader subscribes once per event, `<event>.>`
(`mesh.seat.mesh-catalog.event.catalog-revision.>`), and routes each event to its branch's view by §521, so two
branches never overwrite each other's handler; `meshcatalogclient` wires its views on that one reader, and the
runner's own wiring of `catalog-revision.>` goes for it.
- **The bus module across branches.** The bus module plans a module's kept data from the newest revision of each
watched branch that lists the module: each local name once, from the first of the module's watched branches, in
its registration's order, whose revision lists it. A registration's watched branches are a list in that order: the
branch `register` names first (the repository's default branch, a placement's default, ADR 0666 §6), then each
branch in the order `watch` added it; `unwatch` removes one and keeps the others' order; another branch asking that local name with other settings is
said as the bus module's `drifted`, naming the branch (ADR 0595 §6), and plans nothing.
This amends §358, §365, §421, §433 and §507. Why: no part fixes a branch (§7, ADR 0666 §5), and a registration may
add a watched branch while a part runs (develop questions NATS3, VW1 and COND6, and the client's lost per-branch
events). How it is checked: unit (`go/views`), a view adopted after the start takes its first view and follows,
and two branches' events reach their own views; whole (keeper, bus module), a branch registered while they run is
followed with no restart; whole (bus module), a feature branch asking a local name with other settings than the
default branch's is said `drifted` while the default branch's are planned; the code review finds no branch's name
in any part.
## The bus module's plans
- §533 **A module of the new form.** "A module of the new form" (§59, §344) is a module that is no foundation part.
The bus module tells it by its catalog graph entry declaring no old `objects` section `sent-objects`: a foundation
part whose entry claims a seat whose definition has a verb or an event, or declares an event of its own, declares
it (§59), and the manifest validator refuses such a foundation part's manifest without it, naming it. Why: the bus
module reads no list of foundation parts, which is the validator's alone (ADR 0619 §3; develop question on §59 and
§344). How it is checked: unit (manifest validator), a foundation part saying an event without `sent-objects`
refused; whole (bus module), a module of the new form gets a sent store and a foundation part none.
- §537 **A definition's included parts are resolved before they are counted.** The bus module counts a seat
definition's verbs and events after resolving the parts it includes: it reads each definition that includes parts
by the seat list's answer for that one seat, which resolves them (ADR 0636), and plans a sent store (§344) for the
seat's holders when the resolved definition has a verb or an event. Including parts alone counts for nothing.
Why: an included part may hold only shared bodies (develop question on §344). How it is checked: whole (bus module),
a seat whose verbs come only from an included part gives its holder a sent store, and one including only bodies
gives none.
- §538 **The bus module plans again when the bus-state entries change.** The bus module compares, for each module,
the bus-state entries the catalog graph entry carries with those it last planned for that module, kept in its old
`state` section `plans` (the bus module is a foundation part), one key `<module>` holding the entries as planned
and their digest, written after each plan and deleted when the module is in no watched branch's newest revision, its
entries then orphans that `orphans` lists; its tool `nats_plans` reads it. It plans again only when they differ; it never compares the manifest's digest (§506), which changes with
any field. Why: what it plans is those entries alone (develop question on §433 with §506). How it is checked: whole
(bus module), a manifest changed outside `requires` plans nothing, and a changed option plans the difference.
## The runner, the checker and mesh-sdk's helpers
- §528 **The checker built from G's heads asks no group build.** From G's delivery the pipeline is the check and the
run on request the try (§66), so the checker built from G's heads makes no group check: its group build through
tagged versions (its check's group path, `judge.go`'s, and `tagwanted.go`) goes with `tag-wanted` (§373), nothing replaces it in
the checker, and a group's builds against its package members' digests are the runner's (§373, §465). Until G's
delivery the running checker makes G's own check with main's code (ADR 0667 §2). Why: §66 and §373 (develop
question CK10). How it is checked: the code review finds no group build in the checker of G's heads.
- §534 **The wait's `step` in the runner's body.** The runner's wait body keeps `step` as the pipeline step a wait inside
a pipeline is in (`lint`, `unit`, `integration`, `composition`, `review`, `approval`), and gains the value
`pipeline`, the acting step of §203, for a wait of the entries the runner holds for its new version (§266,
`runner_waits`); `clock-from` stands beside it (§498). The watchman tells them apart by the value: `pipeline` and
every value §498 lists are acting or delivery waits, timed by their bound (§269); a pipeline step's value is a
step's wait on a person, a build node or the revision view (§73), timed as §75 has it, a wait on a person owing no
progress (ADR 0666 §24). This completes §498 for the runner. Why: one field, told apart by its value (develop
question on §498). How it is checked: unit (runner's contract), both kinds read; whole (watchman), a runner's
`pipeline` wait raises `acting-part-late` past its 30 minutes and an `approval` wait raises no `pipeline-stands`.
- §535 **A missing build seat and a seat with no holder, each read where it shows.** The runner reads that no build
seat serves an artifact's language from the catalog half of its revision view: no entry whose `acts`, the acting
rules of the seats it claims, hold a rule `build` with `on` naming that language (§323); the pipeline then ends
failed at once, naming the language and the missing build seat. It reads that a seat has no holder from the step
itself: an ask on that seat's work queue whose `step-started` is not heard within five minutes ends the pipeline
failed, naming the seat (§32). It reads neither from the seat list, which answers definitions alone (§527), nor
from any holding. Why: §32 already ends a pipeline whose step nothing starts, and the catalog half is the runner's
one view (develop question Q-RN7). How it is checked: whole (runner), a fixture language with no build seat ends at
once, naming it; a seat whose holder is stopped ends within five minutes, naming the seat.
- §539 **The runner's tools are named `runner_`.** The runner's module, `mesh-delivery-pipeline`, names its tools with
the prefix `runner_`, so the design's `runner_catch_up`, `runner_waits`, `runner_release` and `runner_health`
stand, and its first-view tool is `runner_first_views`; `<module>_first_views` (§205) carries the prefix the
module's tools carry. Why: the design names the runner's tools so throughout (develop question RN1). How it is
checked: the runner's manifest, and `mesh_search` with "runner first views" finds `runner_first_views`.
- §531 **The kept-call form in Go.** §460's form is mesh-sdk's `busretry.DoKept`, which takes the ask's key, `open` (of
the type `busretry.Open`), an optional `replaced` and the function that writes the journal, and a process's call
through it is `process.Conn.CallKeptWhileOpen`, as mesh-sdk 19fb1dce has them. Why: a helper's API is the design's
(develop question R-5). How it is checked: §460's.
- §536 **`SeatsToPassOn`'s types.** §459's function in mesh-sdk's `catalogue` takes `holdings`, `assignments` (a list
of `Assignment` with `Node`, `Module` and `Branch`), `heard` (a function of a node and a module answering a `Heard`
with `Fingerprint` and `Stopped`, and whether anything was heard) and `entries` (an `EntryReader` with
`ByFingerprint` and `OnBranch`), as mesh-sdk e0f9356b has them. Why: a helper's API is the design's (develop
question R-4). How it is checked: §459's.
## The operator's words
- §522 **The words the teller says.** The teller says, through `notify` on every channel (§514), in the operator's
words:
- `widens`, one line per entry of the pipeline's end (§335): "Widening: `<module>` `<commit7>` on `<branch>` widens
`<kind>` `<name>`.", `<kind>` and `<name>` the entry's.
- `recovery-merged` (§81): "Recovery merged: `<module>` `<commit7>` on `<branch>`, named by `<caller>`: `<why>`."
- a `forge-made` record (§170): "Merged at the forge, not through the mesh: `<repository>` pull request #`<n>` at
`<commit7>` on `<branch>`; delivered like any merge."
`<commit7>` is the commit's first seven hexadecimal characters. This completes §514. Why: the operator, 2026-11-11
12:11 CET at lead-47's terminal, approved the words as the design writer proposed them. How it is checked: unit
(teller), each of the three said in these words with its fields filled; whole (teller, router), as §514.
- §542 **An Approve given before G is that pull request's approval step.** The operator, 2026-11-11 12:33 CET at
lead-47's terminal: approval is a step whose status each module's registration sets, never a gate of its own, so
an Approve given before G counts, once G is delivered, as that pull request's `approval` step and nothing else is
special. At G's switch the gate says, for each `merges` key the old bridge had not performed, `approval-carried` on
`mesh.seat.mesh-delivery.event.approval-carried.<repository>.<pull>` (`repository`, `pull`, `commit` the head the
Approve named, `approver` the operator, `at`), declared in the seat's definition with `keep` 90 days (§396). The gate
keeps its `merges` keys, read no more, until §97's removal, so a revert's gate of before G finds them. The runner
hears the event through a durable consumer made to start from the stream's first message, so a runner started
after the switch hears every one, and keeps each in its old `state` section `pipelines` as
`carried-approval.<repository>.<pull>`, holding the commit. It takes it as the result `passed`, caller the
operator, only of the `approval` step of the delivering pipeline whose source revision takes in that pull
request's merge, as `take-in.taken`'s entries name it (each entry carrying the repository and the pull request of a
`merged` message, §493, §509), and only when the pull request's head merged is that commit; never in a run on
request and never in another pipeline at the same commit; then it deletes the key, and deletes it unused when the
seat `git` says the pull request `pull-closed`. Where the module's policy makes `approval` `required`, a pull request whose head
moved after the Approve asks a new approval, as any step whose input changed does (an `approval` step is never
carried, ADR 0666 §8); where it is `optional` or `bypass`, the carried Approve holds nothing. The merge itself is
any merge (`merge-pull`, ADR 0666 §11): the bridge performs no carried act, the gate says no `wait-held` with
`step: merge`, and the watchman writes no `merge-waits`. Every `wait-held` the runner says for an `approval` step
has its `wait-ended`, said when the step ends in any of §24's outcomes (`passed`, `declined`, `superseded`,
`cancelled`, `not asked`) or its pipeline is superseded or cancelled, so no approval wait stands without an end.
This replaces §510 and §608, and `merge` leaves §498's list. Why: the operator's answer ("you're mixing in step and
approval gates, this should be configurable"); the event, the key and the deletion are the design's (round 3,
health and events' R7). How it is checked: whole (gate, runner), a carried Approve of a head passes the `approval` step
of the pipeline taking in that pull request's merge, with the operator as caller, and no run on request at that
commit; a runner started after the switch takes it all the same; with `approval` `required`, a head moved after the Approve asks a new
approval; a pull request closed deletes its carried key; every `approval` wait-held in the whole test has its
`wait-ended`; the code review finds no `merge-waits`.
## Round 3 of the review of §480 to §515
- §543 **A build seat's holder refuses a kind its asker may not ask.** Callers stay per accepted kind (§338), and the
builder joins them as the one caller, beside the checker until §483's removal, of the kind `build`, as §601 has it
ask under its name. The grants part grants each asker publish on `mesh.seat.<seat>.accept.<queue>.<asker>` (§600)
for every queue of the seat on which a kind it is a caller of is taken, and the holder, which takes the asker from
the subject (§481), refuses an ask whose body names a kind that asker is not a caller of, answering `error` naming
the kind and the asker, and ends it with `step-ended` `failed`. On main's subject `accept.<queue>`, which names no asker and is granted to
the checker and the builder alone (§600), the holder takes only the kinds `build` and `run` and refuses any other,
`step` among them, as above; the builder's `run` there is taken until the checker's removal (§97), after which
main's subject goes (§483). This amends §338 and §600. Why: §600 puts the queue
in the subject and the kind in the body, so a subject's grant alone cannot hold a caller to its kinds (round 3,
security and upgrade, seams and tools: R1). How it is checked: unit (Go build module), a `step` published from the
indexer's account is answered `error` naming `step` and `indexer`; unit (grants part), each asker's grants are the
queues of its kinds; unit (Go, Node and image build modules), a `step` on main's subject is answered `error`.
- §544 **Named asks have a worker of their own.** For each queue the grants part makes, beside main's worker
`SEAT_<SEAT>_<QUEUE>_worker` filtered to main's `mesh.seat.<seat>.accept.<queue>` alone, a second worker
`SEAT_<SEAT>_<QUEUE>_named_worker` filtered to `mesh.seat.<seat>.accept.<queue>.*`; the two filters do not overlap,
so the work-queue stream takes both. Only a holder of G pulls the named worker, and a holder of before G knows only
main's, so no filter is ever switched and a holder of before G that comes back from away (a laptop) after the grants
part runs takes no named ask. This replaces §601's "until every node's holder of a build seat is of G the grants
part's worker keeps main's one filter", and with it the edge from the seats' holders to the grants part, which no
manifest fact gives: the order is the asker's `invokes` of `<seat>.accept.<queue>` (kind 6, from the seat's holders
to the asker, §601), and the grants part comes before every asker as the acting part of the step `grant` on
`every` (§203): each entry of a revision waits for the grants part's new version to act for it (§266), so no asker
of G runs before a grants part of G has made its named worker and its grant. A holder of G pulls main's worker
alone until its membership names the named worker, which the grants part writes as it binds every consumer
(§386), and says meanwhile, in its journal and its read tool, `named worker not bound`. A named ask waits on its worker while no holder of G
runs, said by `build-capacity-waits` after 60 minutes, which corrects §483's "waits on the work queue, whose
consumer of before G takes only main's subject". At a revert, the grants part of G takes its view of the revert's catalog revision before
it is itself reverted, since it acts for every entry of it (§203); at the first revision in which no entry `invokes`
any `<seat>.accept.<queue>`, it purges the subjects `accept.<queue>.*` of the stream and deletes the named workers,
within its granting bound of 10 minutes (§266), saying each in its journal. Where that did not run (the grants part
stopped first), its tool `grants_named_purge`, of class `act` with §391's default callers and written in the
hand-act log, does it by hand; an ask is transport,
not kept data (as §59 has a sent object). Why: one consumer per filter, and a filter that never moves cannot be
pulled by a holder that does not read it (round 3, R2, the security reviewer's form). How it is checked: whole
(grants part, Go build module), a holder of before G started after the grants part of G takes main's asks and no
named one; a revert leaves no named worker and no named ask; a holder of G before its binding pulls main's worker
and says `named worker not bound`.
- §545 **The tool runner's artifact declares the words it states.** The artifact entry of the tool runner's manifest
takes `states`, the capability words (§488) it states; the manifest validator refuses a package artifact's `calls`
word (§605) that no artifact of the revision `states`, naming the word, and the delivery plan's edge of §605 runs to
the module whose artifact `states` that word, so no manifest names the tool runner's module. A bundle is started by
the tool runner on its channel and ends with it, so a restart of the tool runner restarts its bundles, and each
reads the stated words at its own start; no event is heard and nothing polls. This amends §605's "each time the
tool runner of its node starts". Why: the words are stated only at run time, and the plan needs them in a manifest
(round 3, seams and tools: R3). How it is checked: unit (manifest validator), a `calls` word nothing `states`
refused; unit (tool runner), the words it states are exactly its manifest's `states`; unit (delivery plan), the edge to the module stating the word.
- §546 **When the builder forgets a node's toolchain.** The builder deletes its `toolchains-heard` key
`<node>.<language>` (§604) when the node-engine seat's `running.<node>.<module>` says `stopped` (§365) for the
module whose catalog graph entry claims the build seat of that language (its `acts` rule `build` with `on` naming
it, §535), heard through its consumer of that seat's `running`, read as §556 has it; never on a placement, which
is the seat `mesh-module-assigner`'s state. The state is an old `state` section of the builder, a foundation part,
one key per node and language, about 12 keys of about 0.3 KiB each, capped at 1 MiB, so it adds no objects store
to §607's count (§58 plans one only for a state of the template); `builder_toolchains` reads it and
`builder_toolchains_drop`, of class `act` with §391's default callers (`operator`, `sessions`), deletes one key,
with why, written in the hand-act log of the seat `mesh-hand-acts`; a
holder says the key again at its start and on each change (§492). This amends §604's "when the node leaves the
catalog's placements". Why: the builder reads no other module's state (ADR 0596 §2; round 3, seams and tools: R4).
How it is checked: whole (builder), a build module stopped on a node deletes that node's key, and the drop tool
deletes one key and logs it; a drop by another caller is refused by the bus.
- §547 **The sum of the bus's caps is read against its limit.** Measured on 2026-11-11 against an embedded bus
(nats-server 2.11.17): an account's storage limit reserves each stream's maximum size when the stream is made (a
second stream of 64 MiB under an account limit of 100 MiB is refused, "insufficient storage resources"), and a
server's own limit does not. So each `<state>-objects` is planned with 16 MiB (about 2.4 GiB for 150 states), a
state needing more naming `objects-max-mib`, and no later measure is waited for. The bus module reads the account's
and the server's storage limits at its start and at each plan, sums the cap of every entry it plans (states, objects
stores, sent stores, streams, logs) and of every old section the catalog graph's entries declare, and shows the sum and
both limits in its `list` and its read tool. A plan whose sum would pass the account's limit is refused, naming the
sum and the limit, said as its `refused` (ADR 0595 §6): the plan refused is the one that adds or raises a cap, and
the caps already made stay as they are. With no account limit (unlimited) nothing is refused and the sum is shown
against the server's limit. This amends §607's 64 MiB and its measure in G's check. Why: the limit is a
fact of the server, read rather than written in the design, and a sum over it fails loudly (round 3, running and
testing: R5). How it is checked: whole (bus module), a fixture bus with a small account limit refuses the plan
that would pass it, naming the sum, and keeps the caps made; with no account limit nothing is refused; the sum and
the limits are in `list`.
- §548 **The forge module writes `seen` only from a pipeline's steps.** §487's `seen.<commit>.<module>` is written only
from a `step-ended` that carries a pipeline's `commit` and `fingerprint`, which a pipeline's step carries; the
`step-ended` of any other ask (§602) carries `asker`, `ask`, `node`, `outcome` and `at` and neither, so the forge
module writes nothing from it and names no asker. This amends §487.
Why: since §602 every ask says `step-ended`, and a builder's build is no run of a commit (round 3, health and
events: R6). How it is checked: unit (forge module), a builder's failed build writes no `seen`; whole (watchman), a
`stuck` raised for an ask clears on its late `step-ended`.
- §549 **Smaller corrections from round 3.** At its start the runner appends an `unlogged` entry (§606) only after
reading back its `step-log`'s last entry of that pipeline and step, and skips it when that entry is already
logged, so a stop between the append and the clear appends nothing twice. `step-progress` and `ask-taken` (§603)
name no `keep` and go on EVENTS, kept 7 days (§396). `runner_unlogged_append` is of class `act` and takes §391's
default callers. §58 is planned with the cap §607 and §547 give. How it is checked: whole (runner), a runner
stopped between the append and the clear logs the entry once; the rest as the lines they correct.
- §550 **A publication's pipeline keeps the version.** The runner's pipeline value gains `version`, the version
`package-published` names, for a pipeline the runner started from a publication or from `outdated` (§334), and
none for any other; `show` answers it. This completes §334's "the version in the pipeline's state". Why: the
runner's contract had no field for it (develop's note on §334). How it is checked: unit (runner's contract), the
field read; whole (runner), a publish's pipeline shows its version.
## The operator's answers of 12:41 CET
- §551 **A restore point is removed after 90 days, never the newest.** The operator, 2026-11-11 12:41 CET at lead-47's
terminal. The recovery module removes a restore point's directory (§516) 90 days after its `at`, as long as delivery
records are kept, unless it is the newest point of its module and kept data, which it never removes; it checks at
its start, once a day and at each `snapshot-kept`, on its own clock and its own directory, as one of its acts, never
beside a `restore-kept` (§554). Each removal is written in the hand-act log of the seat `mesh-hand-acts` and said as
`mesh-recovery`'s event `point-removed` on `mesh.seat.mesh-recovery.event.point-removed.<module>.<kept>` (`module`,
`kept`, `form`, `fingerprint`, `snapshot`, `at`), kept 90 days (§396). The backup holder's copies go by its
repository's own rotation (ADR 0235 §4). Data set aside is removed only by `recovery_discard_aside` (§554). This completes
§516 and answers issue 1921's note on the retention of restore points. Why: the operator's answer; the event, the
checks and their order are the design's. How it is checked: whole (recovery module), a point older than 90 days is
removed and said, the newest point of a kept data older than 90 days stays, and a removal due during a restore runs
after it.
- §552 **The checker may drop its own asks until its removal.** The operator, 2026-11-11 12:41 CET at lead-47's
terminal: the build seats' verb `drop` (§338) gains the checker as a caller until its entry is removed with its
buckets and bus account (ADR 0667 §12), as `run` has it, so the validator accepts the checker's manifest's
`invokes` of `seat:node-build-go.drop` and `seat:node-build-node.drop`. This amends §338. How it is checked: unit
(manifest validator), the checker's manifest accepted; whole (grants part), the checker's drop granted and another
module's refused.
- §553 **A step status left empty is `optional`.** The operator, 2026-11-11 12:41 CET at lead-47's terminal: a step
whose status a registration's policy (module-level `policy.steps`, or an artifact's) leaves empty is read as
`optional`: it runs, and a failure is shown and holds nothing (§23); `register` and `policy` accept such a policy
and refuse nothing for it, and the runner, `registrations` and `show` say the step `optional`. This completes §23.
How it is checked: unit (runner), a step with no status runs and its failure holds no want; unit (mesh-catalog's
contract), a registration with an empty status accepted.
## Questions an earlier line answers
These write nothing new; develop reads the line named.
- The tool runner's fallback of local commit 1015b38 for a foundation part's bundle goes: §407 binds that bundle to
its old `objects` section, and a binding that is missing fails loudly (ADR 0595 §1) (Q-T5, T-11).
- The re-read tool's callers of `seatlist.Part` are §204's for every part, the assigner's own list going (AS19).
- The bus module's cap of a state's objects store is §513's as §607 and §547 amend it, and the binding's `objects`
and `sent-objects` are §513's (NATS1, §399).
- The contracts' `Hold` is §194's view, the newest assignment half and the catalog half it names; the candidate's
whole-half reader is §195's and §441's, read by a run on request's steps, `grants-for` and the indexer's candidate
pass (contracts).
- The view's module value carries the entry's `manifest-digest` (§506) and computes none (VW1).
- `store-filling` is raised past 80 % and cleared below 70 % (§181).
@@ -28,7 +28,7 @@ takes §600 on, since §516 on are those of batch 4's file G-12.
*These lines answer what the four reviewers by architectural domain found in round 2 of
[G-11](G-11-develop-answers-review.md) (§480 to §515), the last round. Each is the design's choice with its reason.*
- §600 **A build seat's asks: subjects, workers and grants.** A build seat (`node-build-go`, `node-build-node`,
- §600 **A build seat's asks: subjects, workers and grants.** (§543, §544) A build seat (`node-build-go`, `node-build-node`,
`node-build-image`) keeps its three queues of main, `delivery`, `check` and `recheck` (ADR 0525 §4, ADR 0536 §4), on
its one work-queue stream. An ask is published on `mesh.seat.<seat>.accept.<queue>.<asker>`, `<queue>` one of the
three, `<asker>` the asker's name; the ask's kind (`step`, `run`, a build) stays in its body, as on main. The
@@ -43,7 +43,7 @@ takes §600 on, since §516 on are those of batch 4's file G-12.
checked: unit (grants part), each queue's worker filters both subjects and each asker's grant is its own name;
whole (grants part, Go build module), an ask under `runner` is taken and answered, and the runner's account
publishing on main's `accept.check` is refused.
- §601 **The order of G's rollout for the build seats.** The holders of the three build seats are delivered first,
- §601 **The order of G's rollout for the build seats.** (§544) The holders of the three build seats are delivered first,
then the grants part, whose workers then filter both forms (§600), then every asker of G (the runner, the indexer,
the assigner, the builder) and the watchman. Until every node's holder of a build seat is of G the grants part's
worker keeps main's one filter, so a holder of before G never takes a named ask, whose key it would write in main's
@@ -68,20 +68,20 @@ takes §600 on, since §516 on are those of batch 4's file G-12.
`stuck`, naming the seat, the asker, the ask and the node, as main's mirror does. This completes §496. Why: only the
runner says `step-started` (review round 2, health and events 1). How it is checked: whole (watchman, Go build
module), a builder's build whose holder stops raises `stuck` within five minutes and one that ends raises nothing.
- §603 **Where `step-progress` and `ask-taken` go.** Both are seat events and go on the stream that carries the seat's
- §603 **Where `step-progress` and `ask-taken` go.** (§549) Both are seat events and go on the stream that carries the seat's
events, at that stream's own age; no stream is added and no event's age changes. `step-progress` and `ask-taken`
are declared in the three build seats' definitions in `mesh-delivery-contracts`, and `step-progress` in
`mesh-wants`' definition for a composition step. This amends §496's "kept on the seat's stream for a day". Why:
a build seat's own stream is its asks' work queue (review round 2, running and testing 4, seams and tools' note).
How it is checked: unit (definitions), each event declared.
- §604 **The builder keeps the toolchains it heard.** The builder writes each `toolchain-reported` (§492) in a state of
- §604 **The builder keeps the toolchains it heard.** (§546) The builder writes each `toolchain-reported` (§492) in a state of
its own, `toolchains-heard`, one key `<node>.<language>` holding the version, the recipe, the digest and when it
was said, and agrees the version from those keys, read back at its start, so a restart needs no node to report
again. Its manifest `consumes` the three build seats' `toolchain-reported`; its tool `builder_toolchains` lists the
keys; a key is deleted when the node leaves the catalog's placements (§365's reason). Why: a durable consumer does
not deliver an acknowledged event again (review round 2, seams and tools 1). How it is checked: whole (builder), a
restarted builder agrees the same version with no new report.
- §605 **The order after G's tool runner is derived from the package a build resolved.** A package's artifact in a
- §605 **The order after G's tool runner is derived from the package a build resolved.** (§545) A package's artifact in a
manifest takes `calls`, the capability words (§488) its code calls of the tool runner; mesh-sdk's Go and npm
packages name `ledger-upkeep-calls`, `sent-objects-calls` and `put-sessions` from G. For each module whose build
resolved a version of such a package (§466's `resolved`), the delivery plan derives a served-word edge (kind 6) to
@@ -90,7 +90,7 @@ takes §600 on, since §516 on are those of batch 4's file G-12.
This amends §488's `needs`. Why: the bundle makes a session whenever a value is large, so only the package knows
(review round 2, seams and tools 3, running and testing's note). How it is checked: unit (delivery plan), a module
built on G's mesh-sdk is ordered after the tool runner on each node; unit (manifest validator), `calls` read.
- §606 **A step's end and its unlogged entry are one write.** The runner writes a step's end in `pipelines` with the
- §606 **A step's end and its unlogged entry are one write.** (§549) The runner writes a step's end in `pipelines` with the
entry as `unlogged` in the same compare-and-set, appends it to `step-log`, then clears it; at its start it appends
every `unlogged` entry of its pipelines, in order. It reads the log's fill again at each step's end, at its start,
and once a minute while the log is `full`, so the entries the bus ages out say `room` with no step ending. Its tool
@@ -99,7 +99,7 @@ takes §600 on, since §516 on are those of batch 4's file G-12.
full log (review round 2, running and testing 2, health and events 3, seams and tools' note). How it is checked:
whole (runner), a runner stopped after a step's end appends its entry at start; a full log whose oldest entries age
out says `room` with no step ending.
- §607 **A state's objects store has the state's size.** Each `<state>-objects` (§58) is planned with the state
- §607 **A state's objects store has the state's size.** (§547) Each `<state>-objects` (§58) is planned with the state
template's size, 64 MiB, unless the state's `requires` names `objects-max-mib`; the sent stores keep §477's 256
MiB. An estimated 150 states across about 120 modules give about 9.4 GiB of caps. Whether the bus counts a cap
against its storage limit before anything is written is measured in G's check (the bus module's whole test reads
@@ -108,7 +108,7 @@ takes §600 on, since §516 on are those of batch 4's file G-12.
its owner. This amends §513 and leaves §58's "reserves nothing" to that measurement. Why: §58 names the template's
size (review round 2, running and testing 3). How it is checked: unit (bus module's plan), each objects store's cap;
whole (bus module), the reserved storage read after the plan.
- §608 **A carried merge that waits is a timed wait.** A merge act carried by the switch (§510) waiting for the first
- §608 **A carried merge that waits is a timed wait.** (§542) A merge act carried by the switch (§510) waiting for the first
registrations is said by the gate as `wait-held` with `step: merge` (added to §498's list), naming the pull request
and the head the Approve named, bounded at 60 minutes; past it the watchman writes `merge-waits`, which the
condition keeper raises and `wait-ended` clears. It merges only the head the Approve named: a pull request moved
@@ -0,0 +1,209 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0672"
- "ADR 0436"
- "ADR 0667"
- "ADR 0666"
- "ADR 0660"
- "ADR 0656"
- "ADR 0636"
- "ADR 0626"
- "ADR 0619"
- "ADR 0618"
- "ADR 0614"
- "ADR 0596"
- "ADR 0595"
- "ADR 0564"
- "ADR 0505"
- "ADR 0404"
- "ADR 0363"
- "ADR 0240"
- "ADR 0235"
---
# 47, milestone G: the recovery module, and the review of §516 to §553
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
*§554 and §555 are the operator's answer of 2026-11-11 at 12:55 CET at lead-47's terminal. §556 to §558 answer the
four reviewers' round 1 of [G-12](G-12-develop-answers-fourth.md) (§516 to §553); the rest of that round's findings
are written into the lines of G-12 they name.*
## The recovery module
- §554 **The recovery module takes, sets aside and restores kept data on the live bus.** The operator,
2026-11-11 12:55 and 13:02 CET at lead-47's terminal: a module of its own, on a seat of its own, with an account of
its own, owns snapshotting, setting aside and restoring a module's kept data on the live bus, each act in the
hand-act log; the bus module keeps no restore permission (ADR 0626 §1 stays as it is), the snapshot account stays
limited to the nightly dump (ADR 0626 §2), and the backup holder only stores copies. ADR 0672 decides the account,
its grants and its callers; this line is written to it.
- **What it alone does.** It alone takes a restore point before a module moves across a `form` change, and alone
restores into the live bus. The bus module's snapshot before its own `recreate` or `drop` (ADR 0595 §5, ADR 0626
§4) and the nightly dump (ADR 0626 §2) stay theirs. The restore ADR 0667 §12 tests of the snapshot `drop` takes is
the recovery module's `recovery_restore` naming that snapshot, which it reads through the bus module's tool
`nats_snapshot_get` (class `read`, callers `holder:mesh-recovery@same-node` alone).
- **Its seat, module and node.** The seat `mesh-recovery`, held once for the mesh, is defined in mesh-contracts
beside `mesh-broker`, and the module `mesh-recovery`, in mesh-catalog, claims it; its glossary word is the
*recovery module*. It runs on the node of the holder of `mesh-broker`, read from the catalog half and the seat
`mesh-module-assigner`'s placements, never a fixed name: G's seed writes its placement there, and the
placement verb refuses to move it while `kept-points` lists a point younger than 90 days, naming them, so its
points and their copies in that node's repository stay together. Its data directory, its manifest's `data` item
`points`, is capped at 10 GiB and measured by the disk readings (ADR 0571 §5): past 80 % it writes the finding
`points-filling`, and a `snapshot-kept` that would pass the cap ends `failed`, naming it, which holds the move as a
failed backup does (§296). Its `findings` state and finding stream are declared as §529's other parts declare
them, so it waits on the bus module for them and is no foundation part (ADR 0619 §1).
- **Its verbs answer at once; the result is the event.** `snapshot-kept` and `restore-kept` answer `accepted` with
the act's `act` id, `busy` with the act that runs (it runs one act at a time and keeps no queue), or `error`, and
nothing more; the point comes on `ended`, the restore on `restored`. A caller told `busy` asks again when it hears
that act's event. The module writes each accepted act in its directory before it answers; at its start an act
accepted and not ended is ended `failed` with `stopped`, so its caller asks again.
- **Its events.** On `mesh.seat.mesh-recovery.event.` and kept last per subject, 90 days (§396): `ended.<module>.<kept>`
and `restored.<module>.<kept>`, each carrying `act`, `module`, `kept`, `form`, `fingerprint`, `snapshot` and
`outcome` (`done` or `failed`, with `error`), `ended` also the point whole; `restore-started.<module>.<kept>` when a
restore begins; `set-aside.<module>.<kept>` and `aside-discarded.<module>.<kept>` with the aside's `id` and
`size`; and `point-removed.<module>.<kept>` (§551). The node agent wakes only on the event whose `form` and
`fingerprint` are its ask's. Its wait for `ended` or `restored` is bounded at 90 minutes (§405), past which its have
says the wait and the watchman's `delivery-waits` raises it.
- **Who calls it.** `snapshot-kept` and `restore-kept` are called by a node-engine holder (`seat:node-engine`): the
recovery module takes a call only from an account the bus proves to be a node-engine's, and the identity of who
asked is the check (the operator, 13:06 CET), every act logged; no want is read for it, since the operator,
2026-11-11 13:10 CET: "if something asks for a restore, you should do it; he'll have his reasons".
`kept-points` takes the default of a read (§391). The operator, 2026-11-11 13:06 CET at lead-47's terminal: setting
aside is a move, not a removal, so deleting the live objects once their aside is whole and checked (§516) needs
no person's warrant, the data staying in the aside until the operator discards it; setting aside without restoring is
the operator's hand tool `recovery_aside`, and discarding an aside the hand tool `recovery_discard_aside`, both of
class `act`, callers `operator` and `sessions`, written in the hand-act log first; `restore-kept` refuses
`aside-only` and `discard-aside`, naming them (§516). The hand tools are checked by the identity of who asked: they are taken only from the operator's account as the bus proves it, and from sessions
acting on it as for every `act` tool (§391), with no warrant asked per act. Each act is written in the hand-act log of the seat `mesh-hand-acts` first, with the caller the
bus proved and `why`; an act whose log write fails does nothing and answers `error`, naming the log (as §480).
- **The operator's hand tools.** `recovery_take` (take a point of one module and kept data now), `recovery_restore`
(restore one point) and `recovery_retry` (ask again the last failed act of a module and kept data), each of class
`act`, callers `operator` and `sessions` (§391), written in the hand-act log; they are checked by identity, as above.
- **Its account.** Composed by the grants part from the seat's definition and the manifest: the bus's stream
snapshot and restore subjects with their chunk subjects, a stream's deletion and a durable consumer's creation,
only for the bus names of a module's kept data; the subjects its seat says and its hand-act log write; its
consumers of `node-backup`'s `item-started` and `item-ended`, each with the ordered
consumer §556 needs; its calls of `node-backup`'s `restore` as the caller `holder:mesh-recovery@same-node`, which
`node-backup`'s definition names for `restore` and which the backup holder takes only for the recovery module's
own item `points` (the operator, 13:02 CET); and nothing else. The bus names granted are those of every kept data
of every catalog graph entry the catalog half keeps (§443) and of every entry a restore point names (an old
section's name before a form change, a template's binding after), read by the grants part through
`kept-points`; no stream of the mesh's own (`CONTROL`, `NODES`, `EVENTS`, `DEAD_LETTER_NOTICES`, `DEAD_LETTERS`,
`PROV_APPLIED`, `CHECKS_READY`, `CHECKS_OTHER`, every `SEAT_` stream and every `KV_` bucket of a seat) is ever
granted. Restricted streams (ADR 0436) are among the names, as ADR 0672 records.
- **Bus names.** The recovery module turns a module and a kept data into bus names from the catalog graph entry of
the fingerprint it acts at, read by `mesh-catalog`'s `entry` (§443) in its `invokes` (an edge of kind 6): a
template entry's name is `<module>_<local>` (ADR 0595 §1), an old section's its declared name; a point records the
names it took, and a restore uses those.
- **Never beside the nightly dump.** A `snapshot-kept` may run while the dump runs, both paced as ADR 0235 §10 has
it, so the server has at most two snapshots at once. No `restore-kept` starts while the dump runs, and no dump of
the bus starts while a restore runs: the holder of `node-backup` of G says `item-started.<node>.<module>.<item>`
and `item-ended.<node>.<module>.<item>` for every item, kept last per subject, 7 days, and puts off the bus's item
while a `restore-started` of the recovery module has no `restored`; the recovery module learns the bus module and
its node from the seat list's definition of `mesh-broker` and the catalog half's claim of it, never a fixed name.
Both read these streams as §556 has it, so a restart mid-dump or mid-restore misses neither. A blocked act waits,
neither refused nor failing a want: past 60 minutes the recovery module writes `recovery-waits` and the backup
holder `backup-waits`, each naming the item or the act it waits on, and `kept-points` lists the act that runs
with since when and what it waits on. The recovery module consumes the item events that G's backup holder adds,
an edge of kind 6 from that holder to it, so a backup holder of before G, which says none, is never relied on.
- **Its health.** Its declared health check calls `kept-points`, and fails while any aside's record ends at
`removed` (§516), when it also writes the finding `aside-unrestored` naming the module, the kept data and the
aside's `id`.
- **Its order.** The node-engine's `invokes` of its verbs gives the edge of kind 6 from the recovery module to the
node-engine, so §298's first edge reads "the new version of the recovery module" (§555). At a revert of G the
edge runs backward (ADR 0656 §4): the node-engine goes back only after every module it restores there said
`restored` and runs (§298), and the recovery module, new in G, goes only after the node-engine, so it stays while
any restore runs; its data item stays on its node, with its copies.
- **Its tools** are named `recovery_`.
Why: the operator's answers; the node, cap, events, answer on acceptance, dump order, health and edges are the
design's (review rounds 1 and 2; ADR 0672). How it is checked: test 36 with the real backup holder and the recovery
module; unit (grants part), the recovery module's account holds exactly the grants above, a call outside its names
refused, both a point's old-section names and a template's binding granted, and the bus module's and the snapshot
account's hold no restore; whole (recovery module), a call from an account that is no node-engine's refused, a
`busy` answer then the act on the other's `ended`, a second form change not woken by the first's `ended`, a dump
asked during a restore starting after `restored`, a restart during the dump starting no restore, an act whose
hand-act log write fails doing nothing, an unrestored aside failing the health check, a node-engine's `restore-kept` with `aside-only` or `discard-aside`
refused, and the operator's `recovery_discard_aside` removing the aside and writing the log.
- §555 **What moves to the recovery module.** §296 asks `mesh-recovery`'s `snapshot-kept` and waits for its `ended`,
never the seat `node-backup`; §297 asks its `restore-kept` and waits for its `restored`; §302's `backed-up` lists
the recovery module's data item as any item, and the restore points and asides are listed by `kept-points` (§517),
an aside discarded by the hand tool `recovery_discard_aside` (§554); §303 and test 36 name the recovery module where they named the backup
holder and the bus module; §337's and §405's callers `holder:node-backup@same-node` go with the two verbs of
`mesh-broker`, which gains none; the seat `node-backup` gains nothing for kept data on the bus: its `aside` and
`discard-aside`, the arguments `kept`, `form` and `fingerprint` of its `now` and `restore`, and its events
`ended.<node>.<module>.<kept>` and `restored.<node>.<module>.<kept>` go from its definition in mesh-contracts, and
it gains only `item-started` and `item-ended` with their `<item>` token, and names `holder:mesh-recovery@same-node`
among `restore`'s callers for the recovery module's own item (§554); §450's `aside` answer is `recovery_aside`'s;
§406's catch-up is recorded in the point as before. Why: one owner of the acts (§554). How it is checked: unit
(definitions), `node-backup` and `mesh-broker` hold none of the moved verbs; test 36.
## The review of §516 to §553, round 1
- §556 **A part that follows a stream kept last per subject reads it whole at each start.** A durable consumer goes on
from its acknowledged position after a restart, so a part that acts on what it heard from a stream kept last per
subject, and keeps no state of it, reads at each of its starts every subject's last message through an ordered
consumer it makes for that one read, then follows its durable consumer from its acknowledged position, an event
older by `at` than what that read gave changing nothing. Until the read ends the part acts on none of those events
and its `<module>_first_views` names the stream `waiting`. So read: the node-engine seat's `running`, `composed`,
`wait-held` and `wait-ended` (the last two kept on the stream `running` is kept on, last per subject, a node's
module's newer one by `at` standing), by the assigner (§520), the condition keeper (§541), the builder (§546), the
node-engine and the grants part (§199), and the watchman for `delivery-waits` (§541); `mesh-healers'`
`attempt-changed` by the keeper (§541); `node-backup`'s `item-started` and `item-ended` by the recovery module, and the recovery module's `restore-started` and `restored` by the backup holder (§554). The
grants part grants each such reader, beside its durable consumer, the creation of an ordered consumer on that
stream with no durable name (the bus's consumer-create subject for that stream), from the reader's `consumes`. This amends
§199's "each part's durable consumer starts from the last message per subject". Why: what a part heard must not
depend on when it last restarted (review round 1, health and events 2, with running and testing 3). How it is
checked: whole (assigner, keeper), a restarted assigner holds no want it did not hold before, and a restarted
keeper holds the healers' attempts it held.
- §557 **A whole test's bounds are settings.** Every bound a whole test waits on (§535's five minutes, §534's 30
minutes, §544's 60 minutes, §540's two hours, §551's 90 days, §554's 90 minutes) is a setting of the part that
times it, read at its start, its default the design's value, so a whole test sets it in seconds and never waits on
real time. The bus module's and the recovery module's whole cases in these lines are cases of each module's one
whole test against the real bus, within the test budget. Why: a whole test that waits hours runs never (review
round 1, running and testing's notes). How it is checked: the code review; each whole test of these lines runs in
under a minute.
- §558 **A field of a served verb's answer is an edge of kind 6.** Kind 6 (§374) orders a module after a version that
serves a verb or a word it uses, and a verb's presence alone says nothing of a field its answer gained, so a
consumer relying on a new field of a served answer could run before the provider's new version and fall back in
silence (the grants part's first view relies on `binding` in `mesh-broker`'s `list`). An `invokes` entry therefore
takes a long form, `{verb, fields}`: `verb` as today (`seat:<seat>.<verb>`), `fields` the names of its answer's
fields the consumer relies on. The manifest validator refuses a field the seat's definition does not give that
verb's answer, naming it. The delivery plan derives the edge of kind 6 from the holders of that seat to the
consumer, held until the holder runs a build whose catalog graph entry's seat definition (§194) gives the verb's
answer every named field; §374's entry of kind 6 records the verb and its fields, and `outdated` (§375) says it
behind when the delivered version no longer gives them. Both forms stay: the short string where no field is relied on, the long form
where one is. On main mesh-sdk's manifest reader reads `invokes` as a list of strings, so the long form is read
first by §494's second reader release, merged and delivered before G's merge: its manifest reader takes either
form, and the running checker, the indexer and the grants part, which read manifests through it, are rebuilt with
it by the release's publish (kind 1), as for `max-mib`. That publish rebuilds every module importing mesh-sdk's
`catalogue`, the set the release rebuilds already, so the form adds no rebuild. The consumers of G that name fields:
the grants part, `mesh-broker`'s `list` with `binding`; every reader of `mesh-catalog`'s `revision` through
mesh-sdk's client (the bus module, the status view, the condition keeper, the tool runner, the assigner, the grants
part and the runner), with `none-yet` (§530); the status view, `mesh-catalog`'s `entry` with `manifest-digest`
(§506). The recovery module reads the manifest whole the entry carries (§443), a field of before G. Any other part of G relying on a field G adds to a served answer names it so in G's change, which the
code review checks. Why: the module graph orders a consumer after what it relies on, and a field is part of the contract
(lead-47's question from the operator's "the module graph is for this", 2026-11-11). How it is checked: unit
(manifest validator), a field the definition lacks refused; unit (delivery plan), the grants part ordered after
a bus module whose definition gives `binding`, and not before; unit (the release's reader), both forms read.
- §559 **A bundle's finding writer publishes and hears through two runtime calls.** A bundle, in Go or TypeScript,
holds no bus connection, so mesh-sdk's finding writer (§461, §503) in a bundle uses the runtime channel's existing
calls, each gaining one argument: `mesh/publish` takes `ack: true`, and the tool runner then publishes the event on
the subject the module's membership names for it (its old stream's `mesh.stream.<module>.event.<event>.<node>` or
its template binding's, §529; ADR 0363 §8) and answers only once the bus acknowledged it, with its sequence, or
`{error}`; `mesh/subscribe` takes `deliver: new`, and the tool runner makes an ordered consumer on the stream that
carries the pattern, starting at the next message, and delivers each message to the bundle as a subscription
delivers today. The writer says each finding event with `ack: true` (§383's one minute of resending counting from
an unanswered or `{error}` publish) and makes its consumer of `findings-asked` with `deliver: new` before its start
statement (§485). The tool runner states the capability word `finding-calls` (§488, §545) once it answers both,
and the bundle's writer fails at once, naming the word and the node, where it is not stated. Why: one writer in both
languages over the one channel a bundle has, as §518 (develop question of 13:08 CET on §461). How it is checked:
unit (tool runner), a publish with `ack` answered with its sequence and a refused one with `{error}`, a `deliver:
new` subscription given no message older than it; whole (mesh-sdk, TypeScript and Go bundles), a finding written
and an ask answered through a tool runner of G.
@@ -41,13 +41,13 @@ One file of milestone G's part of [design 47](../47-delivery-from-commit-to-deli
compare-and-set; the object it replaced stays, as one whose key was deleted does, listed as `key gone` since no key
names it, and only the drop tool removes either, on a person's word: the mesh removes kept data only through `drop`
and `recreate`, each on a person's word (ADR 0595 §4; ADR 0666 §16).
- §58 **One object store per state, granted to its owner.** (§513) Each state asked by the template `state` gets its object store
- §58 **One object store per state, granted to its owner.** (§513, §549) Each state asked by the template `state` gets its object store
beside it, `<state>-objects`, a template form of ADR 0595 that the bus module provides and the grants part grants to
the state's owner alone (ADR 0666 §16); a foundation part's large values go in an old `objects` section of its own.
Its size is the template's (ADR 0595 §3). An object store holds no space until a value is written to it, so the many
it adds reserve nothing; the few values over the limit (a delivery record, a catalog graph revision list, a test's
output) are estimated, as a guess, at under 1 GiB across the mesh.
- §59 **An object sent is readable by the modules it is for.** A reply's or an event's object is written to the sender's
- §59 **An object sent is readable by the modules it is for.** (§533) A reply's or an event's object is written to the sender's
**sent store**, `<module>_sent-objects`: the bus module plans it, with no manifest field, for every module of the new
form that serves a verb or emits an event, and a foundation part declares it as an old `objects` section
`sent-objects` (ADR 0619 §2). Its cap is the objects template's (ADR 0595 §3), 256 MiB (§477), and it keeps an object 7 days, after
@@ -105,7 +105,7 @@ One file of milestone G's part of [design 47](../47-delivery-from-commit-to-deli
asked it (1921's note of 2026-11-07: "the indexer reads its seat definitions at start"). How it is checked: in the
indexer's whole-module test, started with a definition on a contracts trunk that no event announced, the first
manifest claiming that seat is judged with it.
- §199 **Who holds a seat is applied by one rule, from what each node runs.** The node-engine and the grants part decide a
- §199 **Who holds a seat is applied by one rule, from what each node runs.** (§556) The node-engine and the grants part decide a
seat's pass by mesh-sdk's `SeatsToPassOn` (ADR 0660 §3), reading the catalog graph's entry of the build each assignment
runs, never a manifest (ADR 0660 §2; §442). What each node runs is heard, never asked: each part keeps it from the node-engine seat's events
`running.<node>.<module>` (*What each step says*), through a durable consumer of its own declared in its `consumes`,
@@ -97,7 +97,7 @@ names go, since every new acting part needed code changes elsewhere (research 08
what its new version is built from or needs (a base, a package, a provider) is acted on by its running version,
never waited for: the running build module builds the shared contracts and the registry holder, then the new build
module builds the rest, so ADR 0614 §6's cycle through them stays allowed, now for that reason.
- §271 **The artifact store's holder takes its own new build even when its seat is the one broken.** (§499) A build module
- §271 **The artifact store's holder takes its own new build even when its seat is the one broken.** (§499, §526) A build module
stores the artifacts it builds through the seat `oci-artifact-registry`, so the
holder of that seat is in its own build's path: its new build needs the store it replaces. As every holder's own
inputs are (above), that store is acted on by the running holder: the running holder stores its new build, and only
@@ -329,7 +329,7 @@ later change of a kept data's form, not as a case of this milestone.
changes how it writes that data, or stops declaring it; a declaration naming none reads as form 0. The declaration
alone cannot tell a changed layout of the same bucket, so the holder says it. Only kept data whose `form` changes is
backed up and restored, so a part's other kept data, and every decision it holds, is never set back.
- §296 **It is backed up before the module moves.** When a want moves a module whose new manifest declares a kept data at
- §296 **It is backed up before the module moves.** (§555) When a want moves a module whose new manifest declares a kept data at
another `form` than the running one, or no longer declares it, the node agent applying it first asks the seat
`node-backup` of the node holding `mesh-broker` for a restore point of that kept data, and waits for its event
`ended`, as for any backup before the apply. The bus module's account takes it, as it takes the bus's snapshots
@@ -337,14 +337,14 @@ later change of a kept data's form, not as a case of this milestone.
before, and it holds the kept data whole, the position of each durable consumer the module reads with, and the
catch-up tool the newer manifest declares beside that kept data. A failed
backup holds that module as any failed backup does.
- §297 **A revert restores it before the reverted build starts.** When a want takes a module back across a change of a
- §297 **A revert restores it before the reverted build starts.** (§555) When a want takes a module back across a change of a
kept data's `form`, the node agent applying it stops the newer build, then asks that seat's verb `restore` (§405) for the restore point of that module,
kept data and form, waits for the seat's event `restored`, and only then starts the reverted build. The node agent
that restores is the one running when the revert starts. As every restore is, it is brought back beside the live data
and never over it: the live kept data of the newer form is set aside under a name of its own, and only the operator
removes it, so nothing written since is destroyed. The consumer's position is set back with it, so the reverted
build hears everything since as after any restart.
- §298 **The order of a delivery that crosses a form change** is the module graph's (*Order without a person*), with three
- §298 **The order of a delivery that crosses a form change** (§554) is the module graph's (*Order without a person*), with three
edges the assigner, which writes every want, keeps:
- the new versions of the `node-backup` holder and of the bus module run before any module whose kept data's form
changes is moved, since the restore point needs both;
@@ -381,7 +381,7 @@ later change of a kept data's form, not as a case of this milestone.
ask the gate of milestone G raised after the restore point is not among the pending ones, so its answer is not read
and the operator is asked again. Whether the first look clears a merge kept in the restored `merges` that the forge
merged since was not read in the code; the catch-up test below proves it with the real builds.
- §300 **A missing restore point holds the revert, naming it.** When the restore point a revert needs does not exist, or
- §300 **A missing restore point holds the revert, naming it.** (§541) When the restore point a revert needs does not exist, or
`restored` does not come, the node agent applies nothing of that module, keeps what runs, and writes the have phase
`held` with the reason `restore point missing` and the module, kept data and form it names; the assigner asks the
operator (hold, or start the reverted build on the live data as it is), raises the condition naming them, and on
@@ -391,12 +391,12 @@ later change of a kept data's form, not as a case of this milestone.
(for milestone G, its delivery records and its graph revisions) is set aside and not read again; an Approve asked
and answered since is asked again, and one the gate asked before still reads its answer. The revert's Approve ask
says this in those words, naming each kept data restored.
- §302 **Its tools.** The seat `node-backup`'s `backed-up` lists each module's restore points of its bus data, with the
- §302 **Its tools.** (§517, §555) The seat `node-backup`'s `backed-up` lists each module's restore points of its bus data, with the
kept data, the form and the fingerprint each was taken before, and the data set aside at each restore, with its
size; `restore` restores one; `discard-aside` removes data set aside by a restore, only on the operator's call, and
writes the act, who and why in the hand-act log of the seat `mesh-hand-acts`. `backed-up` and `restore` exist on
the seat today and gain the module's kept data on the bus as an item; `discard-aside` is new.
- §303 **How it is checked.** Whole (node-engine, backup holder, bus module): a want moving a module across a `form`
- §303 **How it is checked.** (§555) Whole (node-engine, backup holder, bus module): a want moving a module across a `form`
change, and one moving a module whose kept data declares no `form` to one that does, each take a restore point
first, keyed by module, kept data, form and fingerprint; a want taking it back restores it beside the live data and
the consumer's position before the reverted build starts, and the data set aside is listed with its size and
@@ -75,7 +75,7 @@ the seat `git` the verb `tag` (§349, §372, §375, §376, §370).
of the revision view, ADR 0656), and `entry` and `difference` on `mesh-wants` (ADR 0656), with the events of *What
each step says*.
§309 **The seat `node-engine`**'s event `running.<node>.<module>` carries, from research 085's milestone, the module, its
§309 **The seat `node-engine`**'s (§520) event `running.<node>.<module>` carries, from research 085's milestone, the module, its
fingerprint and the digest of its build's manifest, so the node-engine of another node and the grants part decide a
seat's pass from the catalog graph's entry of that build (ADR 0660 §2; *Who holds a seat is applied by one rule*). No verb is added.
+11 -11
View File
@@ -43,7 +43,7 @@ record it follows from, and replaces the builder's own choice where one was made
### The runner and its steps
- §323 **A step's build seat is read from the artifact's language.** The catalog graph entry names each artifact's
- §323 **A step's build seat is read from the artifact's language.** (§535) The catalog graph entry names each artifact's
`language`, as its manifest's artifact entry says it and as the indexer's language map matches today; the runner
reads it from the catalog half of its revision view and asks lint and the test steps of the build seat whose
definition's acting rule `build` has `on` naming that language. With no such seat, or no holder of it, the pipeline
@@ -124,7 +124,7 @@ record it follows from, and replaces the builder's own choice where one was made
`take-in.taken` (ADR 0666 §13), which ADR 0596 §1 lets any module subscribe to; the indexer's name stands in that
manifest only, never in the runner's code. Why: ADR 0666 §13, in force, names that event. How it is checked: the code review
reads that no module name stands in the runner's code (§7).
- §334 **A publication's pipelines and their key.** `package-published` carries the package's artifact reference
- §334 **A publication's pipelines and their key.** (§550) `package-published` carries the package's artifact reference
(`<module>:<artifact>`, from the catalog entry), its version and digest, the watched branch and the source revision
its build was made from. The runner asks `outdated` for that package on that branch and starts a pipeline for each
dependent `outdated` names behind (§53), keyed by that branch, that source revision and the dependent module, the version in the pipeline's state; a `take-in.taken` of the same revision and module is the
@@ -161,11 +161,11 @@ record it follows from, and replaces the builder's own choice where one was made
definitions in `mesh-connectivity-contracts` names `invokers`, `persons` and `sessions` in this milestone, since
sessions and persons call them today through the mesh MCP server. How it is checked: unit (definition reader), each
form accepted and any other entry refused; the grants part's table test grants each form.
- §337 **The bus module's restore points.** The verbs the holder of `node-backup` on the bus module's node calls to take
- §337 **The bus module's restore points.** (§555) The verbs the holder of `node-backup` on the bus module's node calls to take
and restore a module's kept data on the bus name `holder:node-backup@same-node`. Why: only that holder takes them
(*A backup before the apply: kept data on the bus*). How it is checked: a call from another node's `node-backup`
holder is refused by the bus.
- §338 **Callers on accepted kinds.** (§480, §481) An accepted kind of a work queue carries `callers` as a verb does, and the grants part
- §338 **Callers on accepted kinds.** (§480, §481, §543, §552) An accepted kind of a work queue carries `callers` as a verb does, and the grants part
grants publish on its subject to them alone: the build seats' `step` the runner; `run` the runner, the indexer and
the assigner, and the checker until its entry is removed with its buckets and bus account (ADR 0667 §12); `mesh-wants`'s
`step` the runner. Verbs: `steps` the runner and the indexer (its seed); `drop` the runner and the builder; both
@@ -198,14 +198,14 @@ record it follows from, and replaces the builder's own choice where one was made
- §343 **Branch names the indexer's keys use.** `register` and `watch` refuse a watched branch named `candidate` or
`entry`, naming why, since the catalog keeps its revisions under `<branch>.<revision>` beside those two key forms.
How it is checked: whole (indexer), each is refused.
- §344 **The bus module plans a sent store from what the catalog already says.** It plans `<module>_sent-objects` for a
- §344 **The bus module plans a sent store from what the catalog already says.** (§533, §537) It plans `<module>_sent-objects` for a
module of the new form whose catalog graph entry claims a seat whose definition has a verb or an event, or declares
an event of its own, read from the catalog half of its revision view and the seat list; the indexer's event carries
no field `sends`. When a seat's definition gains its first verb or event later, the bus module plans the store for
its holders on the seat list's change. Why: no manifest field (*Any size*), and the catalog half is every part's view
(ADR 0656 §6). How it is checked: whole (bus module), a module claiming a seat with a verb gets a sent store, one with
neither gets none, and a definition gaining a verb gives its holder one.
- §345 **A large value reaches the tool runner in chunks.** A bundle's put of a value over the bus's per-message limit is
- §345 **A large value reaches the tool runner in chunks.** (§518) A bundle's put of a value over the bus's per-message limit is
sent to the tool runner as a put session on the bundle's call subject, named by the call's id, in chunks under that
limit, in order, each answered by the tool runner before the next is sent, and written as one object through the
object store's own chunking, its wire form §457's; a put not sealed within the call's bound is dropped by the tool runner and its call
@@ -279,7 +279,7 @@ record it follows from, and replaces the builder's own choice where one was made
candidates, and nothing is guessed. A contribution edge runs from a consumer's entry to the provider its requirement
resolves to, wherever that requirement carries values the consumer contributes. How it is checked: unit
(assignment graph), each step of the order and a hold.
- §358 **The catalog's shared view takes a branch.** The client's view of the catalog half is per watched branch, named by
- §358 **The catalog's shared view takes a branch.** (§532) The client's view of the catalog half is per watched branch, named by
its caller (§479). How it is checked: unit (client), two branches give two views.
- §359 **`difference` names its revisions.** `difference` with none of `from`, `to` and `group` is refused, naming the
missing argument. How it is checked: a unit row.
@@ -299,10 +299,10 @@ record it follows from, and replaces the builder's own choice where one was made
### The condition keeper and the node-engine
- §364 **The node-engine claims its seat.** (§508) The node-engine's manifest claims the node seat `node-engine` (ADR 0563), and
- §364 **The node-engine claims its seat.** (§508, §527) The node-engine's manifest claims the node seat `node-engine` (ADR 0563), and
the indexer and the node-engine's own guard read its holder from the seat list, keeping no module name. How it is
checked: the code review reads that neither fixes a module name (§7).
- §365 **The condition keeper finds its sources in the catalog.** The catalog graph entry carries the events each
- §365 **The condition keeper finds its sources in the catalog.** (§527, §532) The catalog graph entry carries the events each
manifest declares in its `streams`; the keeper takes a first view of the catalog half, and its sources are the
modules whose entry declares the finding events of §383, the node-engine among them, its holder read from the seat
list. It joins the readers of *Every other reader of which modules exist*, reading every watched branch's newest
@@ -406,7 +406,7 @@ record it follows from, and replaces the builder's own choice where one was made
at G's delivery (design 51, *Tagged versions*). Why: the operator, 2026-11-10 about 21:40 CET, no custom versions for
now, a tagged version being one; a run delivers nothing (ADR 0666 §18), so it needs no version; the digest's source
and the wait are the design's choice, round 2 of the review by architectural domain, 2026-11-10. How it is checked: test 103.
- §374 **A release records, per edge, the part it was built against or delivered after.** One version per module
- §374 **A release records, per edge, the part it was built against or delivered after.** (§558) One version per module
stays (§52), and an edge is judged by the digest of the part it points to. The catalog records on each released
version (§349), in its key of `versions`, one entry per catalog-graph edge of the module's artifacts (the six kinds
of *Dependencies between artifacts*), a lockfile per released version. An entry names the kind, whether the edge
@@ -486,7 +486,7 @@ record it follows from, and replaces the builder's own choice where one was made
dependency builds it (its build toolchain part). Every other kind (2, 4, 5 and 6) only orders. A change to a build
module's own code, which is no toolchain part, rebuilds nothing. Why: the operator, 2026-11-10 about 22:30 to 23:00 CET. How it is checked: unit (indexer's
graph), each kind's stale-or-order, and a build module's code change rebuilds no module.
- §379 **A want names the released version and each part's digest.** From G's delivery a want `want.<node>.<module>`
- §379 **A want names the released version and each part's digest.** (§524, §525, §526) From G's delivery a want `want.<node>.<module>`
names, beside its fingerprint and source revision, the module's released version, for people (none for a build of
another watched branch), and the digest of each of its parts; the node-engine fetches and checks each part by its
digest, never by the version. The assigner takes the digests from `version-released` for a released version, and from
@@ -32,7 +32,7 @@ each module's findings as events; a module states its findings from its own trut
tool-runner account stays as it is in G; no record is written for these answers, so each line that departs from a
record says so in its Why. Round 3 of the review by architectural domain (2026-11-11) completed these lines.*
- §383 **An observer says its findings as events on a stream of its own; the keeper reads no observer's state.** Every
- §383 **An observer says its findings as events on a stream of its own; the keeper reads no observer's state.** (§529) Every
module that writes findings is an observer (§387 names them) and says each change of a finding on its own stream
`<module>-findings` (ADR 0363 §1: under its name in capitals on the bus, its `events` `finding-written.*`,
`finding-cleared.*` and `findings-stated.*`, kept 7 days and 16 MiB, its oldest message dropped when full, since the
@@ -97,7 +97,7 @@ record says so in its Why. Round 3 of the review by architectural domain (2026-1
account publishes for; G changes neither that account nor its grants, a known limit. Why: the operator, 2026-11-11;
modules running as the machine's account are issue 1966's (note 193), whose fix carries its check. How it is checked:
none in G; the limit is named on issue 1966.
- §386 **A `*` emitter in `consumes` hears one event from every module that declares it.** A `consumes` entry
- §386 **A `*` emitter in `consumes` hears one event from every module that declares it.** (§529) A `consumes` entry
`*.<event>` takes the event `<event>` from every module whose manifest declares it, and is the one generic form for
hearing an event that many modules say; the keeper's `*.finding-written`, `*.finding-cleared` and
`*.findings-stated` are the first. The manifest validator takes `*` only as the whole emitter, and only before an
@@ -114,7 +114,7 @@ record says so in its Why. Round 3 of the review by architectural domain (2026-1
`*.finding-written` consumer one durable consumer on each declaring module's stream and none on a restricted
stream; whole (keeper, grants part), a fixture module that declares the events after the keeper started is heard,
and a keeper stopped while findings change catches up from its durable consumers at its start.
- §387 **G's change writes the finding stream into every observer's manifest.** In the same change G writes the stream
- §387 **G's change writes the finding stream into every observer's manifest.** (§529) In the same change G writes the stream
`<module>-findings` of §383 into the manifest's `streams`, and `mesh-conditions.findings-asked` into its `consumes`,
for every module that declares a state `findings` on main: `artifact-store-tools`, `claude-code`, `distribution`, `gitea`,
`mesh-build-go`, `mesh-config`, `mesh-healers`, `mesh-knowledge`, `mesh-provisioner`, `mesh-self-check`, `mesh-vault`, `mesh-watcher`,
@@ -108,7 +108,7 @@ line says so.*
(Q13). How it is checked: whole (indexer), `unwatch` of a placed branch refused.
- §403 **A held-back ask.** A held-back ask (§83, design 46 §6) is answered in `asks` with the state `held-back` and the
field `held-since`, and no field `behind`. Why: the design's choice (Q14). How it is checked: the seat's fixtures.
- §404 **The node-engine's events' bodies.** `capabilities-changed` carries the node record's stated capabilities whole
- §404 **The node-engine's events' bodies.** (§520) `capabilities-changed` carries the node record's stated capabilities whole
and `at`; `composed` the module, the fingerprint and the composition's digest; `running` with `stopped` carries
`stopped: true` and no fingerprint (§365), and mesh-sdk's `SeatsToPassOn` counts such a module as not running on that
node, its input being §459's. Why: the design's choice (Q17). How it is checked: the seat's fixtures; unit (`SeatsToPassOn`), a row where a
@@ -116,7 +116,7 @@ line says so.*
### Kept data on the bus, restore points and the reader release
- §405 **The bus module's restore points and node-backup's verbs.** The seat `mesh-broker` gains `snapshot-kept` and
- §405 **The bus module's restore points and node-backup's verbs.** (§516, §517, §554, §555) The seat `mesh-broker` gains `snapshot-kept` and
`restore-kept`, of class `act`, their callers `holder:node-backup@same-node` alone (§337): the bus module's account is
the one that moves bus data, so `restore-kept` sets the live kept data aside under a name of its own and then
restores the restore point beside it, with the argument `aside-only` it sets the live data aside and restores
@@ -132,7 +132,7 @@ line says so.*
choice, one answer to three preparers (Q8, S9, Q-H6, C19; lead-47's ruling, 2026-11-11). How it is checked: test 36;
a call of `snapshot-kept` from another node's `node-backup` holder, and a `discard-aside` from any caller but the
operator, are refused by the bus; a failed restore says `restored` with `failed` and its error.
- §406 **A catch-up names its tool and arguments.** A restore point records the catch-up tool and its arguments whole
- §406 **A catch-up names its tool and arguments.** (§516, §555) A restore point records the catch-up tool and its arguments whole
(§299), and the fingerprint of the newer build it was taken before; the grants part derives the grant of that tool to
`holder:node-engine@same-node` from the catalog graph's entry of that recorded fingerprint (§443). Why: the design's
choice (Q-H7). How it is checked: whole (node-engine), a restore runs its recorded catch-up, granted from the
@@ -208,7 +208,7 @@ line says so.*
### Readers, branches and the node-engine
- §421 **The node-engine reads the entry its want names.** The node-engine joins §196's readers: it reads, from the
- §421 **The node-engine reads the entry its want names.** (§526, §532) The node-engine joins §196's readers: it reads, from the
catalog half, the catalog graph's entry of the fingerprint its want names (§443), never a manifest and never by
branch, so a want needs no field `branch`, and the parts by digest from its want (§379); its reads of the indexer's
and builder's states go. When the catalog half no longer holds that entry, the node-engine holds the want and its
@@ -223,7 +223,7 @@ line says so.*
fixtures; whole (node-engine), a call from another caller is refused by the bus.
- §423 **A kept-build wait says the existing words.** A have waiting for a kept build (§271) says the existing phase and
words, with no new field. Why: the design's choice (Q-H10). How it is checked: whole (node-engine).
- §424 **Each process's own build, and each bundle's.** A process says `own build unknown` in its
- §424 **Each process's own build, and each bundle's.** (§519) A process says `own build unknown` in its
`<module>_first_views` (§205); each tools bundle's composed environment carries its module's fingerprint. Why: the
design's choice (S12, Q-T7). How it is checked: whole (tool runner), a bundle reads its fingerprint.
- §425 **A kept call asks again what its asker names.** An asker passes the kept-call form the ask's key and the event
@@ -268,9 +268,9 @@ line says so.*
named; whole (knowledge base), the overview shows a group's end from `group-ended`.
- §432 **The edges a `needs` names.** An artifact's `needs` names artifact references `<module>:<artifact>` of the
catalog graph's edges (§95, §121). Why: the design's choice (C21). How it is checked: unit (manifest validator).
- §433 **The bus module reads the catalog half.** The bus module's `consumes` and its call of `indexer_bus_state` are
- §433 **The bus module reads the catalog half.** (§532, §538) The bus module's `consumes` and its call of `indexer_bus_state` are
replaced by its view of the catalog half (§344). Why: ADR 0596 §2 (C22). How it is checked: its manifest.
- §434 **Findings name their owner and whom they wait on, and the keeper reads by events.** A finding of §33, §8 and
- §434 **Findings name their owner and whom they wait on, and the keeper reads by events.** (§541) A finding of §33, §8 and
§75 carries `owner` and `waiting-on`; the keeper's other reads are by events or verbs, and no homes rows are added.
Hand-written finding code in Go moves to the generated writer (§439). Why: the design's choice (C16, C17, C15's Go
half). How it is checked: the code review; the keeper's manifest reads nothing.
@@ -380,10 +380,10 @@ line says so.*
- §449 **The grants part's fill events say what `mesh-wants`' say.** The seat `mesh-grants`' events `filling` and `room`
carry the same body as `mesh-wants`' fill events: `store`, `bytes`, `cap` and `at` (§181, §331). Why: develop
question D-4, 2026-11-11. How it is checked: the seat's fixtures, against `mesh-wants`'.
- §450 **`ask-again` and `aside` answer.** `operator-channel`'s `ask-again` (§394) takes `key`, the ask's, and answers
- §450 **`ask-again` and `aside` answer.** (§555) `operator-channel`'s `ask-again` (§394) takes `key`, the ask's, and answers
`state`, the ask's state after it, or `error`; `node-backup`'s `aside` (§405) answers `aside`, the name the data was
set aside under, or `error`. Why: develop question D-5, 2026-11-11. How it is checked: the seats' fixtures.
- §451 **The forms of `owner`, `waiting-on` and a catch-up.** A finding's `owner` and `waiting-on` (§434, §439) are each
- §451 **The forms of `owner`, `waiting-on` and a catch-up.** (§540) A finding's `owner` and `waiting-on` (§434, §439) are each
a string: a person or a session as who@node, or a seat's name. A restore point's catch-up (§406) is `{tool,
arguments}`: `tool` an address as the mesh MCP server names one, `arguments` an object. Why: develop question D-6,
2026-11-11. How it is checked: unit (mesh-conditions' contract, node-backup's contract), each form read and another
+6 -1
View File
@@ -5,6 +5,8 @@ design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0672"
- "ADR 0436"
- "ADR 0670"
- "ADR 0668"
- "ADR 0667"
@@ -23,6 +25,7 @@ decisions:
- "ADR 0641"
- "ADR 0639"
- "ADR 0636"
- "ADR 0626"
- "ADR 0635"
- "ADR 0633"
- "ADR 0625"
@@ -76,7 +79,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 ten files, each read whole, its design lines numbered across them:
The part is fourteen 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.
@@ -89,4 +92,6 @@ The part is ten files, each read whole, its design lines numbered across them:
- [G-9-prepare-answers](G-9-prepare-answers.md): the answers to the questions of G's final prepare and develop's first questions (§391 to §455).
- [G-10-develop-answers](G-10-develop-answers.md): the answers to develop's second batch of questions, the operator's and those of internal form only (§456 to §479).
- [G-11-develop-answers-review](G-11-develop-answers-review.md): the operator's answers of 2026-11-11 noon, the review of §456 to §479, and develop's next questions (§480 to §515).
- [G-12-develop-answers-fourth](G-12-develop-answers-fourth.md): the operator's answers of 2026-11-11 noon, round 3's leftovers and develop's fourth batch of questions (§516 to §553).
- [G-13-review-round-3](G-13-review-round-3.md): the review of §480 to §515, rounds 2 and 3 (§600 on).
- [G-14-recovery-module-and-review](G-14-recovery-module-and-review.md): the recovery module, the operator's answer of 2026-11-11 12:55 CET, and the review of §516 to §553 (§554 to §559).