Answer the step-0b review of G-9: the catalog entry carries its manifest, grants follow the placed build
mesh/delivery merged: source.main moved to novox/hq@fbd7981b7be2
mesh/check checking b4a07c1de6f6 on fbd7981b7be2

The review found that reading the catalog graph instead of manifests needed the entry to carry the manifest and
stay while a build names it, the grants to follow the wanted build, the pre-G node-engines their manifests by
digest, and the restore, caller, stream, finding and test-budget lines a name for every field they left open.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 03:15:45 +01:00
parent 30b29a58a4
commit b4a07c1de6
8 changed files with 174 additions and 100 deletions
@@ -1655,8 +1655,8 @@ list:
assigner and gate wanted; a module delivered during milestone G keeps its fingerprint while they wait; no want goes
to a fingerprint older than the trunk's; the operator is asked again only for an ask raised after the restore
point. A revert whose restore point is missing is held, naming it. Proved with the real builds of the indexer, the
assigner and the gate from before milestone G, not fixtures (whole, checker, gate, assigner, indexer, node-engine,
backup holder, bus module).
assigner and the gate from before milestone G, not fixtures, started by their fingerprints in the artifact store
(whole, in `mesh-host`: checker, gate, assigner, indexer, node-engine, backup holder, bus module; design 47 G §405).
37. **Nothing is delivered ahead.** Research 085's milestone is delivered with no part delivered ahead of it, read
from its delivery record (ADR 0656, How it is checked).
38. **A newer indexer is reached.** A merged revision naming a newer indexer gives, from the running indexer, a catalog
@@ -235,7 +235,7 @@ check and deliver milestone G itself (ADR 0667 §2); each of them says so in a l
and an `approval` step is not; a module with no integration tests shows `nothing to run`, and a `required` unit-test
step on a module with no tests holds its delivery; a bypassed step is said; a runner restarted mid-step hears the
step's end and finishes the pipeline; two runners writing one pipeline key, one loses by compare-and-set; a test step's
counts are in `step-ended` and a repository over budget is named in `pipeline-ended`. Whole (Go build module): `steps`
counts are in `step-ended` and a catalog module over budget is named in `pipeline-ended` (§419). Whole (Go build module): `steps`
answers the steps of a fixture artifact, a `step` ask ends with `step-ended` on its own node's subject, one on another
node's subject is refused by the bus, and a build module restarted mid-step finishes it. Unit (manifest validator): a manifest naming a status is refused. Tests 65, 66, 73
and 88.
@@ -235,7 +235,7 @@ and an estimated 2 mesh-sdk publishes a day, each rebuilding about 120 artifacts
| the watchman, the keeper | `machine-away`, `machine-back` (§389); `findings-asked` (§390) | the seats' definitions | 90 days; 7 days | 16 MiB each |
| the runner | `aged`: three step-log sequences (§330) | old `state` section | never deleted | bytes |
| the teller | `widens-said` (§335) | the template `state` | until the branch is unwatched | a few KiB; the template's |
| each build module | `steps` (§326) | the template `state` | one key per artifact, dropped by `steps` with `drop` | under 1 MiB; the template's |
| each build module | `detected-steps` (§326, §414) | the template `state` | one key per artifact, dropped by `steps` with `drop` | under 1 MiB; the template's |
| the indexer | `registrations` and its stream `registration-history` | old `state` and `streams` sections | the life of the mesh, never derived again | 64 MiB (*A module's registration*) |
| the indexer | catalog graph revisions, per watched branch | objects in the old form (ADR 0619 §2) | while a record pins it or it is its branch's current | 512 MiB, as *Two graphs, one source of truth*; a second watched branch on a few modules adds a few MiB |
| the gate | `source`: one key per watched branch | its old `state` section, as today | 64 revisions of each key; a branch's key deleted when `registration-changed` says the last registration watching it unwatched it (`unwatch`) | a few KiB |
@@ -344,7 +344,7 @@ step keys and the builder's build log, against the checker's last week (test 99)
| §124 Each state's object store `<state>-objects` through mesh-sdk's small package, sent objects readable by a standing grant derived from the catalog graph, and the package's two tools | one object store per state, granted to its owner; the sender's declared callers and consumers read what it sends (ADR 0666 §16); only its users import it, so nothing else is rebuilt |
| §125 The `run` ask published by the runner's account and the indexer's; the verb `run` called by the sessions and the operator only | the steps and the validation run code from the change, in the sandbox only, and each asker reads only its own step keys (ADR 0363 §2); a run publishes tagged packages, so nothing else may ask one |
| §126 The checker's step `check` becomes `pipeline`; `kept` starts empty | the runner is an acting part in the checker's place (ADR 0666 §19); a step's result is taken only from its seat holder (ADR 0666 §21), so no verdict of the checker's is taken in, at the cost of one round of every module's tests |
| §127 The field `callers` in a seat definition replaces the holders' trusted `callers`, a missing field admitting the holder roles and the operator only | the record makes the definition the grants part's one source (ADR 0666 §20); a field left out must open nothing |
| §127 The field `callers` in a seat definition replaces the holders' trusted `callers`, a missing field taking the default of the verb's `class` (§391) | the record makes the definition the grants part's one source (ADR 0666 §20); a field left out must open nothing |
| §128 `widens` in `pipeline-ended`, computed by the running runner over every part serving a step, passed on by the teller | a widening is said and nothing refuses it (ADR 0666 §23, §26); a change must not compute its own |
| §129 `merge-pull` judged from the forge module's own `failed-steps`, heard from `step-ended`, any run at the head's fingerprint counting unless a later rerun of that step passed, the protection read again on every `branch-moved` | the record has it judge from the events it consumes, never by a call, so a head merges while the runner is down (ADR 0666 §11, ADR 0596) |
| §130 The new assigner before the new `merge-pull` and the runner, by `needs`, both switching on the first `registration-changed` | a merge with no Approve must never reach an assigner that writes wants without a pipeline (ADR 0667 §10) |
@@ -3,7 +3,7 @@ layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
updated: 2026-11-11
decisions:
- "ADR 0666"
- "ADR 0656"
@@ -284,16 +284,17 @@ Findings 11). How each is kept, read and retained is this design's choice under
must read every revision it moved between, and a revision neither pinned nor current is read by nobody.
- §179 **An unchanged entry is stored once.** A revision is kept as its list of entries' digests, and each entry as an
object of its own named by its digest, so an entry that two revisions share is stored once; an entry object is
removed when no kept revision lists it. Consecutive revisions differ by the few entries a delivery moves, so this
removed when no kept revision lists it and no want or running build names its fingerprint (§443). Consecutive revisions differ by the few entries a delivery moves, so this
keeps the growth to what changes. It is chosen over a larger cap because whole revisions would grow with every
revision whatever changed, and a cap would only delay the same fill.
- §180 **The caps and the growth** (estimates, to be read from the fill tools once the milestone runs). A whole assignment
graph revision of about 120 modules on four nodes is under the bus's 256 KiB value limit (its unit test), and a
catalog graph revision of about 120 entries about 120 KiB. A record pins the revisions before and after its
catalog graph revision's list of about 120 entries about 4 KiB, its entries, each carrying its manifest whole
(§443), about 15 KiB each. A record pins the revisions before and after its
delivery, and consecutive records share one, so at an estimated 20 deliveries a day about 20 revisions of each graph
a day stay pinned. A delivery moving 5 modules on four nodes adds about 20 assignment entries of about 1 KiB and a
list of about 15 KiB, and 5 catalog entries and a list of about 4 KiB: about 0.7 MiB a day of assignment graph and
0.2 MiB of catalog graph, about 63 MiB and 18 MiB at the records' default retention of 90 days. The caps are 512 MiB
list of about 15 KiB, and 5 catalog entries of about 15 KiB and a list of about 4 KiB: about 0.7 MiB a day of assignment graph and
1.6 MiB of catalog graph, about 63 MiB and 144 MiB at the records' default retention of 90 days. The caps are 512 MiB
for each, many times the steady state. Placement-only and capability-only revisions are pinned by no record and go
when they stop being current.
- §181 **A store filling is said before it is full.** The indexer, the assigner and the gate each say their fill against
@@ -57,7 +57,8 @@ One file of milestone G's part of [design 47](../47-delivery-from-commit-to-deli
candidate's view is read only by the parts that judge a candidate, inside the check its manifest validator and
composer, and the indexer's candidate pass (ADR 0656, which changes ADR 0636 §4 in that the candidate pass joins
them). From G's delivery a running part acts on a revision of a watched branch as on a trunk revision, and a
candidate's view is read only by a run on request's steps and the indexer's candidate pass (ADR 0666 §6, §18).
candidate's view is read only by a run on request's steps, the grants part's `grants-for` (§441) and the indexer's
candidate pass (ADR 0666 §6, §18).
- §196 **Every other reader of which modules exist reads the catalog half, in the same milestone.** From milestone G the
indexer declares no state `modules` and serves neither its tool `indexer_read_modules` nor a view of it (*Reverted*,
above), so every module that learns from it which modules exist moves in milestone G, not after: the watchman (its
@@ -3,7 +3,7 @@ layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0666"
@@ -338,7 +338,7 @@ later change of a kept data's form, not as a case of this milestone.
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
kept data's `form`, the node agent applying it asks that seat's verb `restore` for the restore point of that module,
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
@@ -59,8 +59,8 @@ of each verb (ADR 0666 §7, §20, §25):
| `run` | the run on request of one or more members, each a module at a named commit; delivers nothing | the sessions (the agents' bus identities the tool runner proves) and the operator, nothing else |
| `start` | a delivering pipeline for a watched branch's head, by hand | the operator, the healers |
| `show`, `list`, `stands`, `why-carried`, `kept` | one pipeline whole with its step log; the pipelines and runs of a module, a branch or a commit; what stands still; where a result was carried from; the kept results | any person, an agent session, the healers |
| `review` | a code review's result for a commit and fingerprint | the reviewers the definition names; refused from the commit's author |
| `rerun`, `accept`, `cancel`, `ask-again` | one step again; a failed `optional` step accepted with why; a pipeline ended, its asks dropped; a declined `approval` asked again | the operator, the healers (`accept` and `ask-again` the operator) |
| `review` | a code review's result for a commit and fingerprint | sessions; refused from the commit's author (§394) |
| `rerun`, `accept`, `cancel`, `ask-again` | one step again; a failed `optional` step accepted with why; a pipeline ended, its asks dropped; a declined `approval` asked again | the operator, the healers (`accept` and `ask-again` the operator; §391, §394) |
§307 **The seat `mesh-catalog`** gains, from milestone G's delivery, `register`, `policy`, `watch`, `unwatch` and
`registrations`, and the event `registration-changed` (*A module's registration*), and `catalog-revision` names its
@@ -38,15 +38,17 @@ line says so.*
- §391 **Every verb takes a default of callers by its class.** From the operator's answers of 2026-11-11 (02:13 CET,
"Any declaring module"; 02:47 and 02:56 CET, "As proposed"), a verb's definition names its class in the field
`class`, and a verb whose definition names no `callers` takes its class's default, which the seat-definition reader
applies and the manifest validator says per verb ("`<seat>.<verb>` takes the default of `<class>`"): a `read`, the
class a verb names none of, takes `invokers`, `persons` and `sessions`, so every verb open today stays open; an
`act` (a new act that changes what the mesh does: registers, sets a policy, watches or unwatches a branch,
recovers) takes `operator` and `sessions`; a `warrant` (a verb that runs on the operator's warrant) takes `operator`
and carries the approval mark; `accept` is the operator's alone (§394). A verb that names `callers` keeps them, so
no verb is closed by §68 for lack of a list. Why: ADR 0666 §20 makes the definition the one source of caller
limits; the operator asked on 2026-11-11 why caller questions kept coming, and answered them as one rule (Q1, S3,
C3, D2's reads, Q18). How it is checked: unit (seat-definition reader), a table test with one row per class, and a
row for a verb with `callers` of its own; unit (manifest validator), the default each verb took is said.
applies and the manifest validator says per verb ("`<seat>.<verb>` takes the default of `<class>`"): a `read` takes
`invokers`, `persons` and `sessions`; an `act` (a new act that changes what the mesh does: registers, sets a policy,
watches or unwatches a branch, recovers, restores) takes `operator` and `sessions`; a `warrant` (a verb that runs on
the operator's warrant) takes `operator` and carries the approval mark; `accept` is the operator's alone (§394). A
verb on main that names no class is a `read`, so every verb open today stays open; a verb G adds must name its
`class`, and the definition reader refuses one that does not. A verb that names `callers` keeps them, so no verb is
closed by §68 for lack of a list; §68, §127 and §306 point here. Why: ADR 0666 §20 makes the definition the one source
of caller limits; the operator asked on 2026-11-11 why caller questions kept coming, and answered them as one rule
(Q1, S3, C3, D2's reads, Q18). How it is checked: unit (seat-definition reader), a table test with one row per class,
a row for a verb with `callers` of its own, and a row refusing a verb G adds without `class`; unit (manifest
validator), the default each verb took is said.
- §392 **A caller entry names its kind of name.** In `callers`, a holder role is written `seat:<seat>` and a module
`module:<module>`, beside `operator`, `healers`, `persons`, `sessions`, `invokers` and `holder:<seat>@same-node`
(§336), since a name such as `mesh-grants` is both a seat and a module. Why: the design's choice (Q2). How it is
@@ -63,21 +65,24 @@ line says so.*
operator and the healers (§83, §40). Why: the operator, 2026-11-11 02:56 CET on Telegram, "As proposed" (S4, S10,
C18, D2, D3, D4). How it is checked: whole (indexer, runner, operator-channel), a call from any other caller is
refused by the bus; unit (definition reader), each verb's class and callers.
- §395 **An accepted kind naming no callers keeps today's grant.** `operator-channel`'s kinds `ask`, `cancel` and
`condition` keep the grant they have today, open to every asker, so the runner's approval (§37), the seed ask (§8)
and every other asker are not refused (§338). Why: the operator's intent, a verb open today stays open (Q18). How it
is checked: whole (operator-channel), an asker's `ask` is taken.
- §395 **An accepted kind naming no callers keeps today's grant.** `operator-channel`'s kinds `ask` and `condition`
keep the grant they have today, open to every asker, so the runner's approval (§37), the seed ask (§8) and every
other asker are not refused (§338); `cancel` stays limited to each asker's own subject (ADR 0259 §3). Why: the
operator's intent, a verb open today stays open (Q18). How it is checked: whole (operator-channel), an asker's `ask`
is taken and its `cancel` of another asker's ask is refused by the bus.
### The forms of a seat's definition
- §396 **A dot only where no stream is named from the event; `keep`'s form.** An event whose definition names `keep`
has no dot in its name, since its stream `SEAT_<seat>_<event>` is named from it; an event naming no `keep` stays on
its seat's family stream and may have a dot. `keep` is an object `{max-age-days, max-mib, last-per-subject}`, its age
90 days and its cap 16 MiB unless it names others (§331). The seat `git`'s events take hyphens: `pull-updated`,
`pull-merged`, `pull-closed`, `forge-pull`, `forge-status`, `forge-delete` and `repo-writable` (§310), and `owed` is
kept on its seat stream 7 days and 64 MiB (§162). This amends §260. Why: the design's choice (Q3, Q4, S5, C1, C2).
How it is checked: unit (definition reader), a dotted event with `keep` refused, one without accepted, `keep`'s
defaults.
the shared stream EVENTS, its seat's family, kept 7 days, and may have a dot. `keep` is an object of three fields:
`max-age-days` (days; `0` is the one value for no age limit), `max-mib`, and `last-per-subject` (yes or no); unless
it names others its age is 90 days, its cap 16 MiB and `last-per-subject` no (§331). The seat `git`'s events take
hyphens: `pull-updated`, `pull-merged`, `pull-closed`, `forge-pull`, `forge-status`, `forge-delete` and
`repo-writable` (§310). The gate's `owed` is kept on its seat stream `SEAT_mesh-delivery_owed`, 7 days and 64 MiB
(§162), its one name. This amends §260. Why: the design's choice (Q3, Q4, S5, C1, C2). How it is checked: unit
(definition reader), a dotted event with `keep` refused, one without accepted, `keep`'s defaults and `0` read as no
age limit.
- §397 **The forms of `subject`, `acts.on` and `bound`.** `subject` is the words after `mesh.seat.<seat>.event.` with
`<part>` tokens, `<node>` written out; `acts.on` is one string, `every`, a language, or `kind:image`; `bound` is a
duration such as `30m`. Why: the design's choice (Q5). How it is checked: unit (definition reader), each form
@@ -102,21 +107,33 @@ line says so.*
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
and `at`; `composed` the module, the fingerprint and the composition's digest; `running` with `stopped` carries
`stopped: true` and no fingerprint (§365). Why: the design's choice (Q17). How it is checked: the seat's fixtures.
`stopped: true` and no fingerprint (§365), and mesh-sdk's `SeatsToPassOn` counts such a module as not running on that
node. Why: the design's choice (Q17). How it is checked: the seat's fixtures; unit (`SeatsToPassOn`), a row where a
`stopped` holder passes no seat.
### 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
`restore-kept`, their callers `holder:node-backup@same-node` (§337); the seat `node-backup`'s `now` and `restore`
gain `kept`, `form` and `fingerprint`, and say `ended.<module>` and `restored.<module>`, each keyed by the module, the
kept data, its form and the fingerprint; listing and discarding stay node-backup's `backed-up` and `discard-aside`
(§302), and so does `aside`, which sets a module's live kept data aside before a restore; `mesh-broker` serves
only `snapshot-kept` and `restore-kept`. Test 36's whole case lives in `mesh-host`. Why: the design's 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 is refused by the bus.
`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
nothing, and with the argument `discard-aside`, naming data set aside, it removes that data and restores nothing.
The seat `node-backup`'s `now`, `restore` and `aside` (class `act`, callers by §391's default) and `discard-aside`
(callers `operator` alone) gain `kept`, `form` and `fingerprint` and call those verbs; listing stays `backed-up`
(§302). Before any live data is set aside, the node agent stops the newer build of the module (§297). `now` and
`restore` say `ended` and `restored` on `mesh.seat.node-backup.event.ended.<node>.<module>.<kept>` and
`.restored.<node>.<module>.<kept>`, carrying the module, the kept data, its form, the fingerprint and `outcome`
(`done` or `failed`, with its `error`); the node agent's wait for either is bounded at 30 minutes, past which its
have says the wait, naming the seat, and the watchman's `delivery-waits` raises it. Test 36's whole case lives in
`mesh-host`, and starts the builds from before G by their fingerprints in the artifact store. Why: the design's
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
(§299), and the restored module's catalog graph entry grants that tool to `holder:node-engine@same-node`. Why: the design's choice (Q-H7).
How it is checked: whole (node-engine), a restore runs its recorded catch-up.
(§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
recorded build's entry.
- §407 **The object store package.** mesh-sdk's object store package is `go/stateobjects`; it also takes a foundation
part's old `objects` section (§58, §346). A reply or event over the limit is made an object by that package through
§345's put session, and read back through the same session form for a get; the call that reads a sent object is
@@ -132,9 +149,11 @@ line says so.*
`kind:image`, `version`, the `*` emitter, an observer's finding stream, and an artifact's `needs`; `check` stays
beside `pipeline` until the checker is removed (§97, §203). Why: readers first (ADR 0667 §4; S2, S18). How it is
checked: unit (the release's reader), each form read.
- §410 **A foundation part's sent store keeps its send sequence.** mesh-sdk's sender keeps the sequence of a foundation
part's sends in the owner's old `state` section and ages `sent-objects` from it at each send (§330). Why: an
`objects` section has no sequence (S7). How it is checked: unit (sender), a send past 7 days ages the oldest.
- §410 **A foundation part's sent store keeps its send sequence.** mesh-sdk's sender keeps, per node, the sequence of
a foundation part's sends in the owner's old `state` section `sent-log`, one key `<node>` written by compare-and-set
at each send, and ages `sent-objects` from it at each send (§330); `<module>_state_objects` reads it. Why: an
`objects` section has no sequence (S7). How it is checked: unit (sender), a send past 7 days ages the oldest, and two
sends at once lose none by compare-and-set.
### Versions
@@ -144,9 +163,11 @@ line says so.*
module (§52, §374; Q15, S1, Q-H3, D14). How it is checked: unit (manifest validator), `series` refused.
- §412 **The first fill's sources.** §372's fill reads, per module, the packages its catalog entry names in their
ecosystems (the Go module path, the npm name); where the builder holds no version of a package, the forge's tags of
its repository (`go/vMAJOR.MINOR.PATCH`) are the floor. Why: the live builder answers none for mesh-sdk while the
forge holds `go/v0.1.14` (S17, C9). How it is checked: whole (indexer), a module with tags and no builder versions
fills from the tags.
its repository (`go/vMAJOR.MINOR.PATCH`) are the floor, only tags the forge protects. The fill is a kept call that
waits until both sources have answered for every module, and fills nothing until then, never part of the modules.
Why: the live builder answers none for mesh-sdk while the forge holds `go/v0.1.14` (S17, C9). How it is checked:
whole (indexer), a module with tags and no builder versions fills from the tags; with the forge down, nothing is
filled and `call-unanswered` is raised after five minutes; an unprotected tag is not read.
- §413 **`tag-asks` and `version.numbered` go at G's delivery.** The builder's `tag-asks` and its event
`version.numbered` are removed at G's delivery, as §373 has for the tagged versions; the build modules consume
`version.numbered` no more from G. This amends §372. Why: the design's choice (D19). How it is checked: the
@@ -166,12 +187,16 @@ line says so.*
- §417 **Go's lint is gofmt and vet.** Go's lint step runs gofmt and go vet, in the lint step only (§328). Why: the
design's choice (C8). How it is checked: whole (Go build module), a vet failure fails lint and no unit step.
- §418 **The repository checks run as a step.** Each repository's own checks, which the checker runs today, run as a
pipeline step of its build seat from G's delivery. Why: the checker goes (C20). How it is checked: whole (runner),
a failing repository check fails its step.
- §419 **How tests are counted.** A test is a top-level Go test function or a TypeScript `test(` call; an existing
module ends G with no more tests than it starts with, and the new runner within 50 integration and 500 unit tests; a
module over the budget at its start is named in the pipeline's end. Why: the test budget per module (S16, D6). How
it is checked: `pipeline-ended`'s counts.
pipeline step of its build seat from G's delivery, keyed per repository and source revision, so a repository's
checks run once for a source revision however many of its modules it moves (ADR 0548); hq's check (its `go test
./...` at its root) runs on the seat `node-build-go`. Why: the checker goes (C20). How it is checked: whole (runner),
a failing repository check fails its step, and a revision moving two modules of one repository runs it once.
- §419 **How tests are counted.** A test is a top-level Go test function or a TypeScript `test(` call, counted per
catalog module. A module ends G with no more tests than it starts with, and a module over its budget at its start
whose tests G's change touches ends with fewer (the test budget); the new runner stays within 50 integration and 500
unit tests. `pipeline-ended` carries per module `tests` (`unit`, `integration`) and `over-budget` (yes or no); G's own
check, made by the running checker (§340), counts by the same rule. Why: the test budget per module (S16, D6). How it
is checked: `pipeline-ended`'s counts; unit (runner), a module over budget is named `over-budget`.
- §420 **A run on request's key, and the step-run package.** A run on request is keyed by the run's id under its named
branch (§54, §64, §85); the step-run code is the delivery SDK's package `sdk/steprun`, and the runner's
client sits beside the other seats' clients; the runner's kept data layouts are the seat's `states` (§27, §306). Why:
@@ -179,14 +204,19 @@ line says so.*
### Readers, branches and the node-engine
- §421 **The node-engine reads the catalog half for its branch.** The node-engine joins §196's readers: it reads the
catalog graph entries and verdicts of the modules it composes from the catalog half, never a manifest, of the branch each module's placement
names, and the parts by digest from its want (§379); its reads of the indexer's and builder's states go. The tool
runner follows every watched branch's newest `catalog-revision`, as §365. Why: ADR 0596 §2 (Q-H1, Q-H2, Q-T8). How it
is checked: the node-engine's manifest reads no other module's state; test 61.
- §422 **The node-engine's first-view tools are seat verbs.** The node-engine serves its view's two tools as seat verbs
of `node-engine`, beside its seat-list verbs, since it serves no module tools. Why: the design's choice (Q-H4). How
it is checked: the seat's fixtures.
- §421 **The node-engine reads the entry its want names.** 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
have says the phase `entry-missing`, naming the fingerprint. The tool runner follows every watched branch's newest
`catalog-revision`, as §365. Why: ADR 0596 §2 (Q-H1, Q-H2, Q-T8; review of step 0b). How it is checked: the
node-engine's account holds no read of another module's state; test 61, extended to the node-engine; whole
(node-engine), a want naming an entry the half does not hold says `entry-missing`.
- §422 **The node-engine's first-view tools are seat verbs.** The node-engine serves its view's two tools as the seat
`node-engine`'s verbs `first-views` and `first-view-again`, beside its seat-list verbs, since it serves no module
tools: `first-views` a `read`, its callers any person and the healers, and `first-view-again` an `act`, its callers
the operator and the healers, as §204 names them. Why: the design's choice (Q-H4). How it is checked: the seat's
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
@@ -217,15 +247,20 @@ line says so.*
(gate), test 36's gate half.
- §430 **The gate's sources before the first registrations.** The gate keeps `source.<branch>` per repository and
watched branch, the branch escaped as §73, following the registrations, and writes `source.<default branch>` for
every repository from its start, before the first registrations (§15, §95). Why: the design's choice (C12, D18).
How it is checked: whole (gate), `source` answers before any registration.
every repository from its start, before the first registrations (§15, §95). It starts from its own kept `source`
keys; the forge's heads it asks as a kept call (ADR 0668), so a gate started while the forge is down serves what it
kept, writes nothing for a repository it has no key of, and raises `call-unanswered` after five minutes. Why: the
design's choice (C12, D18). How it is checked: whole (gate), `source` answers before any registration, and with the
forge stopped it serves its kept keys and says `call-unanswered`.
- §431 **The fate of the gate's other parts.** From G's delivery the gate's `asks` go with the Approve per merge;
`deliveries`, `show`, `groups` and `stalled` are answered from the delivery records; `checks` and `settle` go with the
merge states, and the five verbs of release 6 leave the definition; the events `group` and `merged` go, their readers
moving to the seat `git`'s events, while `transition` stays; the reads of the teller's answers and the view's reach
are by their events, and of the checker's verdicts go with it. Readers of the gate's `source` move to the verb
`source` and `source-moved`, and the assigner reads no `merges`. Why: the design's choice (C13, C14, D9, D11). How it
is checked: whole (gate), each verb served or not as named.
merge states, and the five verbs of release 6 leave the definition; the events `group` and `merged` go, while
`transition` stays; their readers move: the knowledge base's delivery overview (what `knowledge-base.progress`
draws) reads the seat `git`'s `pull-merged` and the runner's `group-ended`, and the issue tracker and the tool runner
the same. The reads of the teller's answers and the view's reach are by their events, and of the checker's verdicts
go with it. Readers of the gate's `source` move to the verb `source` and `source-moved`, and the assigner reads no
`merges`. Why: the design's choice (C13, C14, D9, D11). How it is checked: whole (gate), each verb served or not as
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
@@ -239,42 +274,79 @@ line says so.*
the design's choice (Q16, Q-H1's candidates). How it is checked: the seat's fixtures at G's heads keep it.
- §436 **A presence event names the machine, not its publisher.** `machine-away` and `machine-back` carry the machine
as a subject part named `<machine>`, which the grants part does not bind to the publisher's node, since the
watchman says them for another machine. This amends §389. Why: `<node>` in a subject is the publishing node (§260;
S8). How it is checked: whole (watchman), the event for an away machine is published by the watchman's account.
watchman says them for another machine; publish on them is granted to the holder of `mesh-watchdogs` alone. This
amends §389. Why: `<node>` in a subject is the publishing node (§260; S8). How it is checked: whole (watchman), the
event for an away machine is published by the watchman's account; the grants part's table test, a row refusing the
same event from any other account.
- §437 **`finding-written` carries the finding's layout whole.** `finding-written` and each entry of `findings-stated`
carry the finding's layout as the state `findings` holds it whole (its `summary`, `source`, `observer`, `actions`,
`needs`, `resolver`, `ref` and `run` among it), plus `scope`, `id` and `since`, so a condition built from the event
alone loses nothing. This amends §383. Why: the design's choice (Q19). How it is checked: unit (writer), the event's
body holds every field of the key.
carry the fields §439 lists, so a condition built from the event alone loses nothing. This amends §383. Why: the
design's choice (Q19). How it is checked: unit (writer), the event's body holds every field of the key.
- §438 **The record names every repository G builds.** Design 47's milestones row and frontmatter `code:` name
mesh-tools and mesh-networking, and design 51's line on what a node-engine hears says it consumes the node-engine
seat's `running` (§199). Why: the design's choice (Q-T1, Q-H11). How it is checked: the index test.
### Lead-47's rulings on the four stopped items, and the operator's rule on manifests (2026-11-11)
- §439 **The finding events' layout is a seat contract, and its writers are generated.** The bodies of
`finding-written`, `finding-cleared`, `findings-stated` and the finding's layout are defined in the seat
`mesh-conditions`' definition, beside `findings` and `findings-asked`; the seat generator (seatgen) generates the Go and
the TypeScript writers from it, so the forge module's TypeScript writer comes with it. mesh-sdk's `go/views` writer
takes the subject handed in by the seat's client and names no seat (§262). This amends §383 and §386. Why: lead-47's
ruling, 2026-11-11, on Q10, S6 and C15, one answer for the three. How it is checked: unit (seat generator), both
writers generated from the definition say the same bodies; whole (forge module), its writer says `finding-written`.
- §439 **The finding events' layout is a seat contract, and its writers are generated.** The seat `mesh-conditions`'
definition defines, in its section `bodies` (the bodies other modules say on their own streams, from which the grants
part makes no seat event or stream), the finding and the bodies of `finding-written`, `finding-cleared` and
`findings-stated`, beside `findings` and `findings-asked`. A finding's fields, the one list: `kind`, `scope`, `id`,
`at`, `since`, `severity`, `summary`, `headline`, `explanation`, `subject`, `evidence`, `source`, `observer`,
`owner`, `waiting-on`, `actions`, `needs`, `resolver`, `ref` and `run`; `finding-cleared` carries `kind`, `scope`,
`id`, `at` and `why`; `findings-stated` carries `at`, `asked`, `findings` and `pending`. The seat generator (seatgen)
generates the Go and the TypeScript writers from it, so the forge module's TypeScript writer comes with it. mesh-sdk's
`go/views` writer takes the subject handed in by the seat's client and names no seat (§262). This amends §383, §386
and §434. Why: lead-47's ruling, 2026-11-11, on Q10, S6 and C15, one answer for the three; the list is the design's
choice. How it is checked: unit (seat generator), both writers generated from the definition say the same bodies;
unit (grants part), a body of `bodies` makes no stream; whole (forge module), its writer says `finding-written`.
- §440 **`process-builds` is a node-engine seat verb.** The read of the build each process was started with is the
seat `node-engine`'s verb `process-builds`, of class `read`, its callers by §391's default, since the node-engine
serves seat verbs and no module tools (§422). This amends §262 and test 48. Why: lead-47's ruling, 2026-11-11, on
Q11 and Q-H5. How it is checked: test 48; unit (definition reader), the verb's default callers.
seat `node-engine`'s verb `process-builds`, a `read`, its callers any person, agent sessions and the healers, since
the node-engine serves seat verbs and no module tools (§422). This amends §262 and test 48. Why: lead-47's ruling,
2026-11-11, on Q11 and Q-H5. How it is checked: test 48; the grants part's table test, its three callers.
- §441 **The seat `mesh-grants` answers the grants a candidate composes.** The seat `mesh-grants` gains the read
`grants-for`: given a candidate change, the grants that the change's catalog graph composes, against the watched
branch's; its callers by §391's default. The running runner reads a change's grants for `widens` through it, and its
serving parts from the seat list by fingerprint (§71). This amends §401. Why: lead-47's ruling, 2026-11-11, on D15
and Q12. How it is checked: whole (grants part, runner), a change widening a grant names it in `widens` from
`grants-for`.
- §442 **Only the indexer reads a manifest.** The indexer alone reads the manifests, to build the catalog graph; every
other part (the grants part, the condition keeper, the assigner, the runner, the tool runner, the node-engine, the
checks and the reviews) reads the catalog graph, the catalog half of the revision of the branch concerned (§196,
§421). This amends §50, §59, §89, §199, §249, §309, §386, §406 and §421, and the main file's node agent, its inputs
and the composition check. Why: the operator, 2026-11-11 about 03:05 CET at the terminal: "only the indexer uses the
manifest to build the module catalog". How it is checked: the catalog's validation finds no manifest read in any
module's code but the indexer's (code review), and the grants part's table test derives each grant from a catalog
graph fixture with no manifest.
`grants-for`. Its argument is the candidate: a source revision or a run's id, and its watched branch. The grants part
takes that candidate's catalog half by `mesh-catalog`'s `revision` (one of §195's readers of a candidate's view) and
answers, per module, the grants it would compose added and removed against the branch's newest, each named by its
subject and verb. Its callers are §391's default. The running runner reads a change's grants for `widens` through it,
as a kept call, and its serving parts from the seat list by fingerprint (§71); while the grants part does not answer,
`pipeline-ended`'s `widens` says `could not run`, naming `mesh-grants`, and holds nothing (§71: a widening is said,
never refused). This amends §401. Why: lead-47's ruling, 2026-11-11, on D15 and Q12. How it is checked: whole (grants
part, runner), a change widening a grant names it in `widens` from `grants-for`, and with the grants part stopped
`widens` says `could not run`.
- §442 **Only the indexer reads a manifest.** The indexer alone reads the manifests, to build the catalog graph, whose
entry carries the validated manifest whole (§443); every other part (the grants part, the condition keeper, the
assigner, the runner, the tool runner, the node-engine, the checks and the reviews) reads the catalog graph's entry
of the build concerned from the catalog half (§196, §421). Today's readers move: in mesh-catalog, `brother`,
`distribution`, the forge module's package registry and `mesh-build-go` read the entry they need by `mesh-catalog`'s
`entry` or `revision`; in `mesh-host`, the node-engine reads the entry its want names (§421). The one exception is
the bootstrap: `mesh-host`'s `mesh-bootstrap` and `mesh-compose` read the manifests from a checkout of the catalog repository, before
any indexer runs. This amends §50, §59, §89, §199, §249, §309, §386, §406 and §421, and the main file's node agent,
its inputs and the composition check. Why: the operator, 2026-11-11 about 03:05 CET at the terminal: "only the
indexer uses the manifest to build the module catalog". How it is checked: test 61, extended to the node-engine and
the grants part: whole cases whose accounts hold no read of a manifest; the grants part's table test derives each
grant from a catalog graph fixture with no manifest.
### From the review of step 0b (2026-11-11)
- §443 **The catalog graph's entry carries the manifest, and is kept while a build names it.** Each catalog graph
entry carries the validated manifest whole beside the fields §174 names, kept as an object of the indexer's old
`objects` section by its digest (§179), so no size limit applies to it. The indexer keeps an entry while a kept
revision lists it, or a want (heard by `mesh-wants`' `wanted`) or a running build (heard by the node-engine seat's
`running.<node>.<module>`) names its fingerprint, and removes it at the first revision after none does; the seat
`mesh-catalog` gains the read `entry`, which answers one entry by its fingerprint, so a build older than the newest
is read by the want's fingerprint. This amends §179 and §180. Why: §442; the design's choice. How it is checked:
whole (indexer), an entry no revision lists stays readable by `entry` while a want names its fingerprint, and goes
after the want moves on.
- §444 **The grants part derives each module's grants from the build placed.** The grants part derives each module's
grants from the catalog graph's entry of the build its want names (heard by `wanted`), never from a branch's newest
revision, so a change on a branch no placement names grants nothing. This amends §59 and §386. Why: §442; a grant
follows what runs. How it is checked: the grants part's table test, a widening on a branch no placement names
grants nothing, and the same widening once wanted is granted.
- §445 **The indexer keeps the manifests by digest for node-engines before G.** Until no node runs a node-engine from
before G, the indexer keeps writing each manifest by digest in its state `manifests`, which those node-engines
read; that state and its writing go in the first change after the node-engine seat's `running` events say every
node runs G's node-engine, the state through `drop` on a person's word (ADR 0595 §4). The node-engine's own manifest
in G uses no word the node-engine of the version before refuses, since that reader refuses unknown fields; the
manifest validator holds it to that reader's words at G's heads. Why: readers first (ADR 0667 §4). How it is
checked: unit (manifest validator), a row refusing a node-engine manifest with a word the older reader refuses;
whole (node-engine of the version before), it composes from a manifest by digest written by G's indexer.