Merge pull request 'Design 47: milestone G's part complete, every operator answer and three review rounds written in' (#539) from feat/47-G into main

This commit was merged in pull request #539.
This commit is contained in:
2026-11-11 01:34:16 +00:00
12 changed files with 3022 additions and 1843 deletions
+5 -3
View File
@@ -916,12 +916,14 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564, 0668.
(ADR 0283).
- **probe** — one look at one thing's health, run by whoever owns the verdict; what a probe returns is a
**finding**, before it becomes a condition. A finding is kept as a key `<scope>.<id>.<kind>` in its observer's
bucket `findings` until it is no longer observed (ADR 0564 §2).
bucket `findings` until it is no longer observed (ADR 0564 §2); from design 47's milestone G the observer also says
each as its own event, and states every finding it owns at its start, so the condition keeper hears findings and
reads no observer's bucket (ADR 0596 §2; design 47 G §383, §384).
- **node health report** — each node-engine's own report of its node's health, a key that node's engine writes
and the watchman reads (ADR 0564, as its progressive insight of 2026-10-29 has it); its layout is mesh-sdk's.
- **observer** — a part that writes findings for the condition keeper: the watchman, the self-check and the
healers (ADR 0564 §2), and each caller of a kept call, for its `call-unanswered` (ADR 0668 §3). An observer never
writes a condition.
healers (ADR 0564 §2), and each caller of a kept call, for its `call-unanswered` (ADR 0668 §3); from design 47's
milestone G, every module that writes findings (design 47 G §383). An observer never writes a condition.
- **condition keeper** — the holder of the seat `mesh-conditions`: the one writer of the
open conditions, raising each from an observer's finding and clearing it when the finding goes, and saying
`condition-raised`, `condition-changed` and `condition-cleared` (ADR 0564 §2, §3). The controller keeps them
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,418 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0664"
- "ADR 0661"
- "ADR 0656"
- "ADR 0633"
- "ADR 0619"
- "ADR 0614"
- "ADR 0596"
- "ADR 0595"
- "ADR 0593"
- "ADR 0584"
- "ADR 0573"
- "ADR 0567"
- "ADR 0564"
- "ADR 0548"
- "ADR 0448"
- "ADR 0429"
- "ADR 0398"
- "ADR 0363"
- "ADR 0283"
---
# 47, milestone G: pipelines per module: registration, watched branches, steps and policy, what holds a delivery, approval, groups, dependencies, a publish
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
## Pipelines per module
*ADR 0666, ADR 0667; ADR 0595, ADR 0596, ADR 0448 §2, §4, ADR 0548 §5, ADR 0564 §2, §4, ADR 0283, ADR 0656 §4, §6, ADR 0633 §5,
ADR 0664 §4, ADR 0398 §2, ADR 0593 §9; research 086, Direction.*
§1 From milestone G's delivery. A change of any module, the delivery system's own parts included, goes from a commit to
the machines through that module's **pipeline**: the pipeline steps its **policy** names, run for each commit or group
on each of the module's **watched branches** by the seat `mesh-pipeline-runner`, ordered by the catalog graph's edges
between artifacts, with no hand act (ADR 0666 §1). Milestone G is the one milestone that builds it, beside the two
graphs, the revision view and the delivery record (ADR 0667 §1). Until G's delivery the sections this one replaces
(*Checks*, *A try*, *The Approve and the merge*, the state table and the trunk-only want) hold as written, and they
check and deliver milestone G itself (ADR 0667 §2); each of them says so in a line of its own.
```
a session ──merge-pull (seat git)──► forge ──commit on a watched branch──► forge bridge ──► indexer (mesh-catalog)
│ registrations; a catalog graph
│ per watched branch; the revision
│ validated before take-in
▼ take-in.taken
pipeline runner (mesh-pipeline-runner) ── asks each pipeline step on the serving seat's work queue, hears step-ended:
│ lint, unit tests, integration tests ──► the build seats (node-build-go, node-build-node, node-build-image)
│ composition ──────────────────────────► the assigner (mesh-wants)
│ code review ──────────────────────────► the runner's own verb `review`, from the reviewer's bus account
│ approval ─────────────────────────────► operator-channel: accepted kind ask, then decided
▼ pipeline-ended (every step's result), keyed by source revision
assigner ──► wants from a revision whose required steps passed, on the watched branch a placement chose
gate ──► the delivery record keeps the step and pipeline events
```
### A module's registration
- §2 **What it holds.** The seat `mesh-catalog` keeps each module's **registration**: its git origin, the path of its
manifest, its watched branches and its policy, on the bus, every version with who changed it and the warrant of the
operator's Approve (ADR 0666 §2). The indexer is a foundation part, so the registrations are kept in its old sections
(ADR 0619 §1 to §3, ADR 0666 §4): one key per module in its old `state` section `registrations`, and every version on
its old `streams` section's stream `registration-history`, kept with no age limit.
The catalog graph is derived from the registrations and the manifests they point to; the mesh knows modules, and a
repository only as a module's git origin.
- §3 **Changing one is a trusted act.** The seat's verbs `register` (a module whole), `policy` (one module's step
statuses, per artifact), `watch` and `unwatch` (add or remove a watched branch) each raise an authorising ask of tier
approve through the seat `operator-channel` (design 46 §10), naming every value the change moves, one line each
("`mesh-grants:program` unit tests: `required` → `bypass`"), and write the version only on its warrant; a Decline or
an expiry writes nothing (ADR 0666 §2). Until a record binds each forge account to its bus identity, `register` and
`policy` refuse code review as a `required` step, naming why, so no review gates while its author cannot be told
(ADR 0666 §22; issue 1966, note 157). A manifest moved to another path is the one manifest change that changes the
registration; any other manifest change is a commit, judged at its revision.
- §4 **Said and read.** Each written change is said as the seat's event `registration-changed` (the module, the version,
what moved); the verb `registrations` answers every registration, or one with its history. The indexer derives the
modules it keeps from them, and the forge bridge its watched branches, each by a first view of `registrations` and
then that event, never by reading the ledger (ADR 0666 §3, ADR 0596).
- §5 **An unprotected branch is refused.** `register` and `watch` read a branch's protection through the seat `git`'s read
verb `branch` (its head, and whether a rule refuses a direct push and admits only merges), and refuse a watched branch
the forge does not protect, naming it (ADR 0666 §3).
- §6 **The foundation parts are the manifest validator's list.** The manifest validator's list (ADR 0619 §3) holds
exactly the foundation parts, ADR 0619 §1's and the runner, which joins them (ADR 0666 §4); the checker leaves the
list when it is removed (*How milestone G itself is checked and delivered*). A registration holds no foundation
value. How it is checked: a unit test of the manifest validator refuses a template in the manifest of every part on
its list, the runner's included, and judges every other part's manifest as any part's.
- §7 **No part fixes a name: a guideline the code review reads.** Every branch, repository and module name a part
needs is read from the registrations and the catalog graph, never fixed in its code. This is a guideline, not a
step: the code review reads it, and no lint step scans for names (the operator, 2026-11-11 about 00:05 CET: the scan was a flaky check; where
guidelines live is research 087's). It departs from ADR 0666 §5's refusal by the lint step, by the operator's choice,
and a later record brings it in line. The names G's own work removes (about 39 in 9 parts, found on 2026-11-09,
research 086, Findings) are removed by its subtasks, each naming the name it removes.
- §8 **The first registrations are one ask.** At its first start on G's build the indexer, holding no registration,
proposes one registration for every module its trunk revision holds, from what it took in (the origin and path of
each), its watched branch the repository's default branch read through the seat `git`, and the statuses the check
judged until then: lint, unit tests, integration tests and composition `required`, code review `optional`, approval
`bypass`. Each step the artifact's build seat answers `steps` with none is `bypass` instead, so no module is held by
a `required` step it has nothing for; the indexer asks `steps` of each build seat as a kept call (ADR 0668 §1, §2),
and the seed waits until every build seat has answered, never reading an unanswered seat as "none": a seat still
unanswered after five minutes is the indexer's `call-unanswered`, naming the seat (ADR 0668 §3), and the seat's new
build serving `steps` is an edge of kind 6 before the seed. A module whose default branch the forge does not protect
is left out of the seed and named in the ask, as `register` would refuse it (ADR 0666 §3). It raises the rest as one
ask naming every registration, as the seat `mesh-module-assigner`'s `adopt` proposes placement whole (ADR 0567).
While the ask is open the indexer's finding `seed-waits`, naming the ask, is raised by the condition keeper as
waiting on the operator (ADR 0283) and cleared by the warrant; a Decline or an expiry leaves the seed unwritten, says
so and keeps `seed-waits`, naming `indexer_seed_again`, which raises the ask again, recorded in the hand-act log.
Until that Approve the runner starts no delivering pipeline and says why, so the operator is told of this one ask
before G's delivery starts. Why: the design's choices under ADR 0666 §3, §7 and ADR 0668 (round 1 of the review by
architectural domain, 2026-11-10). How it is checked: whole (indexer), a build seat that never answers `steps`
holds the seed and raises `call-unanswered`, one that answers none gives `bypass`; an unprotected default branch is
left out and named; `seed-waits` is raised while the ask is open and cleared by the warrant.
- §9 **Kept for the life of the mesh**, never derived again (research 086, choice 25): the history stream's cap is 64 MiB.
About 120 registrations of about 1 KiB, and an estimated change a week each (a guess, not a measure), grow about 6 MiB
a year, so the cap holds about ten years; past 80 % the indexer says `filling`, as every store of *Two graphs, one
source of truth* does.
- §10 **How it is checked.** Whole (indexer): an unprotected watched branch is refused, naming it; a policy change without
the warrant writes nothing, and a declined one writes nothing; a change from `required` to `bypass` asks, showing
that line; `registration-changed` is said and `registrations` answers the history; the first start proposes the seed
as one ask, and `indexer_seed_again` asks it again after a Decline and after an expiry; a policy making code review
`required` is refused. Tests 75, 68 and 83.
### Watched branches and the catalog graph per branch
- §11 **Only a watched branch starts a pipeline.** The forge bridge hears every commit of every watched branch the
registrations name, through the seat `git`'s event `branch-moved` (a branch's new head, from the forge's push
webhook, which a merge and an admitted push alike say), and takes the commit from the forge's own record by the seat's
verb `branch`, never from the event's body (ADR 0666 §6). One event for every way a branch moves, because a merge is
not the only one. `branch-moved` is kept on the seat's stream `SEAT_git_branch-moved`, which the bridge hears through
a durable consumer, so a move said while the bridge is down is heard when it starts; at its start it also reads each
watched branch's head the same way.
- §12 **A head with no pipeline is a finding.** A watched branch's head that starts no pipeline within five minutes is the
watchman's finding `pipeline-stands`, naming the branch and its head, raised by the condition keeper (ADR 0564 §2),
unless it waits as progress not owed: a member of a gathered group is governed by ADR 0633 §5's 30-minute bound, and
a merge behind a pending revision of the same branch by its 60-minute bound (ADR 0666 §24). The finding clears on the
runner's `pipeline-started` for that head.
- §13 **The catalog graph per watched branch.** The indexer keeps the catalog graph once per watched branch, from that
branch's manifests at each source revision (ADR 0666 §6): each revision names its branch, and the stores, caps and
pins of *Two graphs, one source of truth* hold per branch.
- §14 **A placement names the branch.** Each module of a placement key names the watched branch its node runs, by default the
registration's first, set by a placement proposal like every other placement change (ADR 0567, ADR 0666 §6). The
assigner computes each node's entries from the catalog graph of the branch its placement names, so a module watched on
`main` and `beta` runs `beta` only where a placement chose it.
- §15 **A source revision per watched branch.** The gate keeps one source revision per watched branch, `source.<branch>`,
in place of `source.main` (ADR 0429 §11 to §13 as ADR 0666 §6 changes them), in the old `state` section `source`
already holds, its key's layout being the only change. The gate is no foundation part: the bus module's delivery
waits on no act of the gate's, since the indexer takes a revision in from the forge bridge's stream and the assigner
writes wants from the runner's events, so the gate's other kept data stay in ADR 0595's templates (ADR 0666 §4), and serves it by the seat
`mesh-delivery`'s verb `source` with the branch, saying `source-moved` on each new revision; the indexer and the
node-engine's catch-up read it there, never the gate's state (ADR 0596). Each want names a build-input fingerprint
computed at one such revision.
- §16 **Validated before any take-in, on every branch.** Before it takes a revision in, on every watched branch, the running
indexer asks the manifest validator built from the revision's heads to judge it in the build seat's sandbox (the
ask `run`, *What ADR 0656 leaves to this design*). A revision it refuses stays pending under ADR 0633 §5's
60-minute bound, its hand acts and its condition `take-in-waits`; the way back is a revert, an ordinary merge
(ADR 0666 §6, §26). A revision on the branch the indexer's own placement chose is taken in only by the indexer it
names (ADR 0633 §1). This validation is no pipeline step and waits on no pipeline, so every delivering pipeline runs
on a revision already taken in.
- §17 **A branch whose protection was removed starts nothing.** On every `branch-moved` the forge bridge reads the
branch's protection again; a move of a watched branch whose protection was removed starts no delivering pipeline, and
the forge bridge writes the finding `branch-unprotected`, naming the branch, which the condition keeper raises and
clears once the protection is back (ADR 0666 §6).
- §18 **A pending revision holds its own branch only.** A merge waits behind a pending revision only on the same watched
branch; other branches move on (ADR 0666 §6, which changes ADR 0633 §3).
- §19 **A refused new indexer holds its branch until a revert lands.** A merge of a new indexer whose own validator refuses
its revision has already landed when it is refused, since the validation runs before take-in and after the merge
(ADR 0666 §6). It stays pending, and every later merge on that watched branch waits behind it, said by
`take-in-waits` at the 60-minute bound, until a revert, an ordinary merge through its pipeline (ADR 0666 §26), lands
and is taken in; nothing takes a later merge in past it. This is the cost of validating every revision with the
validator built from its own heads, the design's reading of ADR 0666 §6, and the operator is told it with the
condition, which names the pending revision and the revert as the way on.
- §20 **How it is checked.** Whole (forge bridge): a commit of a branch no registration names starts nothing; a commit on a
watched branch is taken from the seat's `branch` read, not the event; a head missed while the bridge was stopped is
read at its start. Whole (indexer, assigner): a commit on `beta` gives a revision of the `beta` graph only and moves
only the want of the node whose placement chose `beta`; a revision whose own validator refuses it stays pending and
holds `beta` while `main` moves on. Whole (watchman): a head with no pipeline for five minutes raises
`pipeline-stands`, cleared by `pipeline-started` for that head, and a gathered member does not; a move said while the
bridge was stopped is heard from the seat's stream. Tests 64, 68, 76, 78 and 82.
### Pipeline steps and the policy
- §21 **The runner.** The seat `mesh-pipeline-runner`, held once for the mesh by the module `mesh-delivery-pipeline`,
runs each pipeline (ADR 0666 §7). It is a foundation part, since the bus module's own delivery waits on its pipeline
(ADR 0666 §4): its kept data are in old sections (ADR 0619 §2), it waits on no bus module process to start, and it
joins the manifest validator's list of foundation parts, which refuses its manifest asking a template (ADR 0666 §4,
which changes ADR 0619 §1 and §3). It reads the policy from the registrations, asks each step that is not `bypass` of
the seat that serves it, and says each step's start and end and the pipeline's end (*Health and going back*, below).
- §22 **The steps and who serves them.** Lint, unit tests and integration tests are the build seats' (`node-build-go`,
`node-build-node`, `node-build-image`): each gains the verb `steps`, which answers which pipeline steps an artifact
can run by its language's convention (as *The check from release 4* says the tests are found), and the accepted kind
`step` on its stream `check`, which runs one. Composition is the assigner's, an accepted kind `step` of the seat
`mesh-wants`. Code review comes in through the runner's own verb `review`. Approval is asked as the seat
`operator-channel`'s accepted kind `ask`, on its work queue by the runner's name (design 46 §10, *Asks, warrants and answers on the bus*;
the seat has no verb `ask`), and heard on its event `decided` (ADR 0666 §7, §10). A step with nothing to do ends at once as
`nothing to run`, never as passed.
- §23 **The policy.** Each step's status, per module and per artifact, is `required` (runs; holds the delivery until it
passes), `optional` (runs; a failure is shown and may be accepted) or `bypass` (nothing runs; said as bypassed),
kept in the registration and never in a manifest: the manifest validator refuses a manifest that names a status
(ADR 0666 §7; research 086, R3). A `required` step that ends `nothing to run` holds the delivery and names the step.
- §24 **Asked on a work queue, ended by an event.** The runner asks each step on the serving seat's work queue and hears
its end as that seat's event `step-ended` (the record's `step ended`), on
`mesh.seat.<seat>.event.step-ended.<node>`, the node that ran it in the subject and each holder's account granted only
its own node's subject, through a durable consumer of its own, so a step outlives a restart of either side (ADR 0666
§7, ADR 0363 §2). A build seat's holder restarted mid-step gets the step again from its work queue and finishes it
(ADR 0664 §1). A unit or integration test step says its catalog module's test counts in `step-ended`; `pipeline-ended` carries each
catalog module's counts against the test budget, which is counted per catalog module (at most 50 integration and
500 unit tests each), and `pipeline-ended` names any catalog module over it (ADR 0666 §8; the test budget, per
module, the operator 2026-11-11). A step's outcome is one of `passed`, `failed`, `nothing to run`,
`bypassed`, `carried` (with the pipeline it was carried from), `could not run` (naming the part not heard),
`waiting on <person>`, `accepted` (a failed `optional` step a person accepted, with who and why), `not asked` (an
`approval` step after a failed `required` step), `superseded` and `cancelled`.
- §25 **Carried, not run.** A step whose build-input fingerprint and policy did not change is carried by its package
digests, as ADR 0548 §5 holds, the runner making the carry instead of the checker: a test step runs only the packages
with no kept result for their digest, and a run on request's results are reused when the merged commit's inputs match
(ADR 0666 §8; research 086, choice 24). An `approval` step is never carried.
- §26 **Approval comes last.** The runner asks the `approval` step only once every other `required` step of the pipeline
passed, so the operator approves a commit the mesh already judged, the one place ADR 0661 §3 had the check gate the
ask. A failure after an approval is said, as a failed `optional` step is, and the approval stays.
- §27 **Kept on the bus, in old sections.** The runner keeps each pipeline's state in its old `state` section `pipelines`,
one key per pipeline keyed by its watched branch, source revision and module, written by compare-and-set so a holder
replaced mid-pipeline and its successor never both write one; its step log on its old `streams` section's stream
`step-log`; each step's output in its old `objects` section `step-outputs`, named in the log; and each package's
passing result per level in its old `state` section `kept`, under the digest of what its tests read (ADR 0666 §4, §8;
*The runner's kept data*, below). `kept` starts empty: the runner takes no verdict of the checker's, since a step's
result is taken only from the bus account of that step's seat holder (ADR 0666 §21), so the first pipelines after G
run each package's tests once and carry from then on (the design's choice, 2026-11-10).
- §28 **How it is checked.** Whole (runner): a step with unchanged fingerprint and policy is carried and says from where,
and an `approval` step is not; a module with no integration tests shows `nothing to run`, and a `required` unit-test
step on a module with no tests holds its delivery; a bypassed step is said; a runner restarted mid-step hears the
step's end and finishes the pipeline; two runners writing one pipeline key, one loses by compare-and-set; a test step's
counts are in `step-ended` and a repository over budget is named in `pipeline-ended`. Whole (Go build module): `steps`
answers the steps of a fixture artifact, a `step` ask ends with `step-ended` on its own node's subject, one on another
node's subject is refused by the bus, and a build module restarted mid-step finishes it. Unit (manifest validator): a manifest naming a status is refused. Tests 65, 66, 73
and 88.
### What holds a delivery
- §29 **The assigner writes a want only from a passed pipeline.** The runner says each pipeline's end as `pipeline-ended`
(the record's `pipeline ended`), keyed by source revision, with every step's result. The assigner consumes it, and
writes a want only from a source revision whose every `required` step passed for the modules it moves, on the watched
branch a placement chose, by compare-and-set on a newer revision, so an older revision ending after a newer one
writes nothing (ADR 0666 §9). The compare-and-set is keyed per module and watched branch: it compares a revision only
with the last one written for the same module on the same watched branch, so a revision of `beta` ending after a
newer one of `main` writes its own want and refuses neither; a later `pipeline-ended` of the same revision (after a
passing `rerun`, below) is taken, since its end count is higher (the design's choice under ADR 0666 §9: source
revisions are kept per watched branch, `source.<branch>`, and two branches' revisions have no order between them). This is one more rule beside the assigner's others (ADR 0429 §6), checked per module.
From G's delivery it replaces the trunk-only want (ADR 0656 §2 as ADR 0666 §9 changes it). For a group's source
revision the runner says one `group-ended` once every member's modules have ended, and the assigner writes the
group's wants only on it: a member's own `pipeline-ended` writes nothing (ADR 0666 §9).
- §30 **A failed `optional` step holds nothing.** It is shown in `pipeline-ended`, in the delivery record and in the runner's
`show`; a person may accept it by the runner's verb `accept`, recorded in the hand-act log with who and why.
- §31 **A newer commit supersedes an older pipeline of a module whose fingerprint it changed.** On a newer commit of the
same watched branch the runner ends a module's older running pipeline as `superseded` only when the newer commit
changed that module's build-input fingerprint (ADR 0666 §9); a module the commit leaves as it was finishes its
pipeline. A newer publish of a package likewise supersedes the rebuild pipelines an older publish of it started. A
superseded pipeline's waiting asks are dropped by each build seat's `drop` (ADR 0664 §4), naming
the newer commit as the cause, so a cancelled pipeline leaves no ask on a stream (a gap G's prepare found: a cancelled
build withdrew only the builder's wait).
- §32 **A step nothing starts ends its pipeline.** A step whose prerequisite nothing starts (a seat with no holder, a
prerequisite step `bypass`) ends the pipeline refused within five minutes, naming the step and what it waits for,
never as a silent wait (research 086, R5); a step whose seat has no holder ends its pipeline failed, naming the seat
(ADR 0666 §17).
- §33 **A failed head is said to its author, then to the operator.** A `required` step that failed, or ended `could not
run`, on a watched branch's head is the runner's finding `pipeline-failed`, naming the module, the branch, the commit
and the step; the condition keeper raises it as the commit's author's for two hours and then the operator's, as ADR
0505 §5 and §6 have a delivery whose cure is its author's new head, and clears it when a newer revision of that branch
passes that step (ADR 0666 §24). The author is the forge's record of the commit, read through the seat `git`.
- §34 **The delivery record keeps every step.** The gate's delivery record consumes the step and pipeline events and keeps
them as long as the record (ADR 0666 §9; *The delivery record*).
- §35 **How it is checked.** Whole (assigner): a failed `required` step writes no want; a failed `optional` step holds
nothing; an older revision's `pipeline-ended` after a newer one's writes nothing, and a revision of one watched branch
ending after a newer one of another writes its own want; a group with one member passed and
another still running writes no want until `group-ended`. Whole (runner): a newer commit that changed a module's
fingerprint supersedes its older pipeline and drops its asks, and one that left it unchanged lets it finish; a step
with no holder ends the pipeline naming the seat within five minutes; a failed `required` step on a head raises
`pipeline-failed`, the author's for two hours and then the operator's, cleared by a newer passing revision. Tests 66,
67, 87, 89 and 98.
### Approval is a step, and a session merges a pull request
- §36 **No Approve per merge.** The operator's approval is the pipeline step `approval`, its status in each module's policy;
no Approve is asked per merge, and the delivery gate's merge turns and its Approve per merge go (ADR 0666 §10, which
changes ADR 0661 §3 to §5). The operator is asked only where a module's policy says `approval`, and for registrations
and policy changes.
- §37 **The approval is an ask of the runner's.** The runner raises an authorising ask of tier approve as the seat
`operator-channel`'s accepted kind `ask`, under its own name, its `about` the module and the watched branch only, so a newer commit's ask
replaces the older one's (design 46 §10). The warrant ends the step passed; an ask that is declined, expires or is
refused ends it `declined`, and one that is replaced ends it `superseded`. Only a declined `approval` step is asked
again, by the runner's verb `ask-again` (ADR 0666 §10). While it waits the runner
says `wait-held` waiting on the operator, never a stall (ADR 0283).
- §38 **A session merges a pull request.** Any session the tool runner proves, and the operator, merges by calling the seat
`git`'s verb `merge-pull` over the mesh, which merges with the forge tools' account (ADR 0398 §2) at the head the
caller names. It judges that this is still the pull request's head and that the branch's protection refuses a direct
push, the protection read again on every `branch-moved` of the branch, and it judges the head's runs by its modules'
policies: it refuses a head when any run at its build-input fingerprint failed a step a policy marks `required`, and
merges a head whose `required` steps passed or that has no run yet, a failed `optional` or a `bypass` step refusing
nothing; it never waits for a run and never asks an Approve (ADR 0666 §11). The forge keeps only its branch protection.
- §39 **Judged from its own state, never by a call.** The holder of the seat `git` consumes the runner's `step-ended`
through a durable consumer of its own and keeps, in its own state `failed-steps`, each failed `required` step by
module and build-input fingerprint, with the commits it was seen at and the run that failed it, until none of those
commits is a watched branch's head or the head an open pull request names (*The runner's kept data*); so a head
merges while the runner is down, judged on what it already heard (ADR 0666 §11, ADR 0596). A head's fingerprints are
those its runs named for its commit; a step that failed at that fingerprint in any run, a group's or another commit's
of the same content included, refuses it, unless a later run of that same step at that fingerprint, asked again by
the operator or the healers (the runner's `rerun`), passed: that pass clears the failure from `failed-steps` (ADR 0666
§11). The runner's `step-ended` says that the step was a rerun and the caller the runtime proved, and the forge module
clears a failure only for a rerun whose caller is the operator or a healer; a plain pass, or a rerun asked by anyone
else, clears nothing. A run still running is judged by the steps it has ended, since the merge never waits. A refusal names the run, the module and the step, so the session knows what to fix. The gate merges nothing, and its forge merge token is
removed with the checker's buckets and bus account, after the first pipeline after G's delivery has delivered a change
end to end and on the operator's word (ADR 0667 §12; *How milestone G itself is checked and delivered*); the forge bridge hears the landed commit as it hears every commit of a watched
branch.
- §40 **The operator-channel hides no ask.** It keeps design 46's three open asks per asker, never holds the newest back,
and replaces an open ask by the same asker's ask with the same `about` (ADR 0666 §12; design 46 §6).
- §41 **How it is checked.** Whole (forge module, forge bridge): a session's `merge-pull` at its head merges with no run yet,
and with a passed one, and asks no Approve; with the runner stopped a head merges; a head at whose fingerprint any
run failed a `required` step, a head that moved and a branch whose protection was removed since its last move are
refused, each naming why; a failed `optional` step refuses nothing. Whole (runner): an `approval` step is asked last,
a Decline and an expiry each end it `declined` and `ask-again` asks it once more, and a newer commit's ask replaces the
older and ends its step `superseded`.
Live: a declined approval and a fourth ask each reach the operator. Tests 71 and 91.
### Groups
- §42 **Formed from the branch name.** The gate forms a group from a shared branch name carrying its issue number, as it
does today without the fixed list of trunk names (ADR 0666 §13).
- §43 **A member reaches its group by landing.** A member has reached its group when its commit lands on its modules'
watched branch. The forge bridge sets the group on each landed member's commit, reading the members from the seat
`mesh-delivery`'s verb `groups`, and publishes `forge.group.merged` when that verb says every member has (ADR 0666 §13,
which changes ADR 0633 §4).
- §44 **One source revision, one pipeline per branch.** The indexer keeps landed members pending until the group's end and
takes them in together as one source revision of that watched branch, saying `take-in.taken`; the runner starts the
group's one pipeline per watched branch on that event, and a member of a gathered group starts no pipeline of its own
(ADR 0666 §13).
When one member's `required` step fails, none of the group's modules is delivered (research 086, R5).
- §45 **A group that never completes is said.** By ADR 0633 §5's 30-minute bound and its condition; `bridge_group_end_again`
says a group's end again from the gate's group verb, never the gate's `merges` state (ADR 0666 §13, which changes ADR
0633 §5).
- §46 **How it is checked.** Whole (indexer, gate, forge bridge): a group of three repositories, its own packages included,
is one source revision and one pipeline; one member's failed `required` step delivers none; a group with a member that
never lands raises `take-in-waits` at 30 minutes. Tests 67 and 81.
### Dependencies between artifacts
- §47 **Six kinds of edge between artifact references.** The catalog graph's edges run between **artifact references**,
`<module>:<artifact>`, such as `mesh-grants:program`, of six kinds (ADR 0666 §14):
| Kind | B before A | Derived from |
|---|---|---|
| 1 | B published before A builds | a package A's build source imports (ADR 0573 §3) |
| 2 | B published before A delivers | an artifact A's manifest fetches by reference at its start |
| 3 | B delivered before A builds | a build module A is built by, and the toolchain its agreed version names (ADR 0584 §1) |
| 4 | B delivered before A delivers | a provision A requires and B provides, bus state among them (ADR 0614 §5), and a format's reader (below) |
| 5 | B has acted for A's new version | a seat's acting rule (*What ADR 0656 leaves to this design*): a grant, a credential, a verdict, bus state |
| 6 | B runs a version that serves A | a verb or a manifest word A uses and B serves or reads (ADR 0614 §2, §4), the build seats' `kept-artifact` among them |
Each kind is derived from what manifests and seat definitions already say, and declared per artifact in the manifest's
artifact entry, its field `needs`, only where nothing else says it. Today's edges run between modules, and only a
package edge names an artifact; at module level the tool runner and the grants part looked like a cycle, per artifact
they are a chain (`mesh-grants:program`, then `node-tools:runtime`, then `mesh-grants:tools`).
- §48 **Removal runs the other way.** When something goes away the order is reversed: a seat moved to a new holder has its
old holder released before the new one claims (ADR 0666 §14).
- §49 **The edges alone order.** The build and the delivery order by these edges alone; a cycle of delivery edges (kinds 2,
4, 5, 6) is delivered as one step, and a cycle of build edges (kinds 1 and 3) is refused, naming every artifact on it
(ADR 0666 §14; research 086, R17). The cycle rule is the indexer's, in the catalog graph it keeps, so the checking
program's cycle rule goes with the checker.
- §50 **A format's reader first.** A format a running reader cannot read is preceded by its reader, an edge of kind 4 on
every watched branch (ADR 0666 §14): the indexer's manifest validator for a new field of a seat definition or a
manifest, and on each node the node-engine that reads a manifest's new fields before any manifest that writes them
(ADR 0667 §4).
- §51 **How it is checked.** Unit (indexer's graph): a chain per artifact where modules would cycle; two programs needing
each other's new verb are one step; two artifacts each needing the other's new package to build are refused, naming
both; a seat moved to a new holder releases the old first. Live: the tool runner and the grants part delivered in
the order `mesh-grants:program`, `node-tools:runtime`, `mesh-grants:tools`. Test 79.
### A publish rebuilds
- §52 **A version is reserved before the build and released when every artifact is published.** A module's version
is its manifest's `version`, `MAJOR.MINOR`, and a patch the build system gives: the next after the highest version
the catalog holds released for that `MAJOR.MINOR`, 0 for one it holds none of. The builder reserves it when the
module's build starts and passes it to the build of every artifact of the module (§370), so code and packed files
carry it; each artifact published is said as the event `package-published` of the builder's seat `mesh-builds`
(ADR 0666 §15), and the version is released only once every artifact of the module at it is published (§349); only the first watched branch releases (§371). A
failed publish leaves the version reserved, and `builder_publish_again` publishes the missing artifacts under the
same version. The seat `mesh-builds` is new, held once for the mesh, defined in `mesh-delivery-contracts`; the
builder held none until now, and its waits stay on its own stream in this milestone. Why: the operator, 2026-11-10
about 21:40 CET, a design choice of G's part (gaps items 22, 23 and 43). This reserves the number when the build
starts, where ADR 0666 §15 numbers a package at its publication and design 51 publishes a held digest under a new
number: a packed file carries its version, so it must be known before the build; the operator's choice, which a
later research brings the records in line with. How it is checked: test 102.
- §53 **Every dependent behind is rebuilt against it, on `package-published`.** The builder says `package-published`
for every package build of a watched branch: with its released version on the registration's first watched branch,
and on another watched branch by its digest alone, the build stored by digest with no version (§371). The runner,
which consumes it, asks the catalog's verb `outdated` for that package on that branch, a kept call (ADR 0668 §1,
§2), and starts a pipeline for each dependent by kind 1 whose recorded part digest differs from the published one:
the catalog compares (§375), the runner starts, and no other part rebuilds anything. A `package-published` whose
version is lower than the highest the catalog holds released for that module starts nothing (§382). Nothing else is
rebuilt, and a change to tests alone publishes nothing (ADR 0666 §15; research 086, R18). Why: ADR 0666 §15 has the
runner consume `package-published`; the digest comparison is the operator's (2026-11-10), and one event for both
branches is the design's choice, round 2 of the review by architectural domain, 2026-11-10.
- §54 **A second start writes nothing.** The runner starts a pipeline on two events, the indexer's `take-in.taken` (ADR
0666 §13) and `package-published` (ADR 0666 §15). When both reach it for the same source revision and module, the
second start writes nothing: the runner writes a pipeline's key (watched branch, source revision and module, *The
runner's kept data*) by compare-and-set on its absence, so one revision and module has one pipeline. The design's
choice under ADR 0666 §13 and §15: the key the runner already keeps is the one place a start is judged, so neither
event's producer needs to know of the other.
- §55 **Unread packages refuse.** A build that cannot read the published packages refuses and names the unread state
(ADR 0666 §15).
- §56 **How it is checked.** Whole (builder, indexer): a mesh-sdk merge changing one package publishes one version, and only
the artifacts importing that package rebuild; a merge changing only tests rebuilds nothing; a build whose packages
cannot be read refuses naming the state. Whole (runner): a `take-in.taken` and a `package-published` for the same
source revision and module start one pipeline, the second writing nothing. Tests 70, 80 and 101.
@@ -0,0 +1,360 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0664"
- "ADR 0661"
- "ADR 0659"
- "ADR 0656"
- "ADR 0644"
- "ADR 0619"
- "ADR 0596"
- "ADR 0595"
- "ADR 0593"
- "ADR 0573"
- "ADR 0567"
- "ADR 0564"
- "ADR 0548"
- "ADR 0525"
- "ADR 0505"
- "ADR 0448"
- "ADR 0363"
- "ADR 0283"
- "ADR 0240"
---
# 47, milestone G: pipelines per module: any size, the pipeline makes the check, who may say what, health, recovery, tools, kept data, how G itself is delivered, the choices under ADR 0666 and ADR 0667
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
### Any size
- §57 **A value over the limit is an object.** mesh-sdk's own small object store package (ADR 0595 §9) puts a value over
the bus's per-value limit in the object store beside its state and keeps the object's name in the key; a verb's reply or an event over the
per-message limit carries the object's name (ADR 0666 §16). A replace writes the new object and sets the key by
compare-and-set; the object it replaced stays, as one whose key was deleted does, listed as `key gone` since no key
names it, and only the drop tool removes either, on a person's word: the mesh removes kept data only through `drop`
and `recreate`, each on a person's word (ADR 0595 §4; ADR 0666 §16).
- §58 **One object store per state, granted to its owner.** 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
**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), and it keeps an object 7 days, after
which the bus expires it. That age is transport, not kept data: an object sent exists only to carry one reply or one
event, read within its call's bound or its consumer's lag, so its expiry is not a removal of kept data that ADR 0595
§4 holds to a person's word (the design's choice; 7 days is a guess at the longest a consumer lags, read from the
fill tools once G runs). A full sent store is the bus module's `full` and its condition (ADR 0595 §6), and the sender
then refuses the large reply or event loudly, naming the store. The grants part derives from the manifests a standing grant to read them: the
callers its seat definition names for the verbs it serves, and the consumers whose `consumes` declare its events;
no other module may read them (ADR 0666 §16). The grant follows the manifests, so it is written when they change,
never per object. A consumer's reader of
the form is delivered before its producer writes it, an edge of kind 4.
- §60 **Its tools come with the package.** The package serves each module that imports it `<module>_state_objects` (the
objects of each state, with their size, the key that names each or `key gone`, and the fill against its cap),
`<module>_state_objects_drop` (drop the objects whose key is gone, on a person's word as ADR 0595 §4 removes kept
data), `<module>_sent_objects` (each sent object, its size, when it was sent and to which verb or event, its age
against the store's and the fill against its cap) and `<module>_sent_objects_drop` (drop sent objects older than an
age a person names); each drop names who asked and is recorded in the hand-act log.
- §61 **Its cost.** Only the modules that import the package are built with it, so no other module is rebuilt for it: a
package published before its users build (ADR 0573 §2, ADR 0667 §3).
- §62 **How it is checked.** Whole (mesh-sdk, against the real bus): values of one, ten and a hundred times the per-value
limit stored, sent as a reply and as an event, and read back whole by a caller and a consumer the manifests declare,
and refused to a module that declares neither; a replaced large value leaves its old object listed `key gone` beside
the new one, and a key deleted leaves its object listed `key gone`, each removed by the drop tool and by nothing else;
a sent object is readable within its 7
days and expired after; each drop without who asked is refused. Tests 69 and 90.
### The pipeline makes the check, and the checker goes
- §63 **Every step runs on parts built from the change.** Every pipeline step that runs a change's code runs on parts built
from the change's own heads, in the build seat's sandbox, with no network and no bus account (ADR 0666 §17, ADR 0448
§2, §4): the composition step composes each machine's declaration with the composer built from the heads and refuses
a change in which two modules own one path; the build seats' steps run lint and tests. A step whose serving part the
change alters runs from the change's build of it, built within 20 minutes or ending `waiting for a build node` or
`did not build`, the first tier of *The check on the change's own acting parts* made the runner's.
- §64 **The run on request.** The runner's verb `run` takes one or more members, each a module at a named commit, runs them
through their pipelines and delivers nothing, replacing the checker's try (ADR 0666 §18). Its callers are the
sessions, as the agents' bus identities the tool runner proves, and the operator, and nothing else, since a run of a
group builds every member and its dependents on the build capacity (§373). The runner says `run-started`, the indexer builds
the run's candidate from it, and the candidate's composition step writes no want (ADR 0656 §9); the candidate is
dropped on that run's `pipeline-ended`. A step that could not run ends `could not run`, never as passed. A
composition step for a node that sleeps passes it with a warning (ADR 0593 §9).
- §65 **The runner is an acting part in the checker's place.** The acting rule's step `check` becomes the runner's step
`pipeline` (*What ADR 0656 leaves to this design*): its own change runs on its own new version, a failing new runner
holds while the old one runs pipelines (ADR 0656 §4), and the pipelines in flight when its holder is replaced are
taken up again from its kept state (ADR 0666 §19).
- §66 **What the checker did, and where it goes.**
| The checker's | From G's delivery |
|---|---|
| the merge check, one status `mesh/check` | the pipeline, its result in `pipeline-ended`, the runner's `show` and the delivery record; no status on the forge |
| the try | the run on request |
| the composition check | the composition step, the assigner's |
| the kept verdicts and the carry | the runner's `kept`, which starts empty, and its carry (ADR 0548 §5); the checker's verdicts go with its buckets |
| the checking program: the manifest validator at the heads, the cycle rule, the composer, the rule names | the indexer's validation before take-in; the indexer's cycle rule; the composition step; `widens` in the pipeline's end |
| the `run` ask, published by the checker's account | published by the runner's account and the indexer's (its validation), each with step keys under its own name |
| its buckets and bus account | removed with the gate's forge merge token after the first pipeline after G's delivery has delivered a change end to end, on the operator's one word, the buckets through `drop` after its snapshot, whose restore is tested first (ADR 0667 §12) |
- §67 **How it is checked.** Whole (runner, assigner): a run on request of `feature/1966-x` delivers nothing, and a call of
`run` from another account is refused; a composition step refuses two modules owning one path; a step whose part
cannot be heard ends `could not run`; a node asleep passes with a warning; a runner stopped mid-step takes its
pipelines up again. Live: once the checker is removed no module claims the seat `mesh-delivery-checker`. Tests 64,
84 and 88.
### Who may say what
- §68 **Callers per verb, in the seat's definition.** Each seat definition names, per verb, the callers allowed to call it,
in its field `callers` (holder roles, modules, the operator, the healers), which the grants part reads as its one
source of caller limits; the runner's verbs are the first (ADR 0666 §20). A verb whose definition names no `callers`
admits only the seat's holder roles and the operator, so a field left out opens nothing. It replaces the holder's
trusted config value `callers` of the seat `git` and the package seats (design 51), which no holder set for most
verbs. A format its running reader cannot read, so its reader is delivered first (ADR 0667 §4).
- §69 **A step's result from its holder only.** The runner takes a step's result only from the bus account of that step's
seat holder, naming the commit and the build-input fingerprint; a result from another account, or naming another
commit or fingerprint, is refused and said (ADR 0666 §21).
- §70 **A review gates, once it can.** A code review's result is taken through the verb `review` from the reviewer's bus
account and refused from the commit's author, so a review gates where a policy says `required` (ADR 0666 §22, which
changes ADR 0659 §2). Until a record binds each forge account to its bus identity no policy may say it (*A module's
registration*; issue 1966, note 157).
- §71 **A widening is said, never refused.** A change that alters any part serving a step (the runner, step detection, the
lint rule, a build seat's test runner, the composer), or widens an acting rule, a grant or a credential, says so in
its `pipeline-ended`, its field `widens` naming each. The running runner computes it, from the sources of the change
against those of the watched branch, never the runner built from the change, so a change cannot hide its own
widening; nothing refuses it for that (ADR 0666 §23, the operator, 2026-11-10). The teller passes `widens` on to every
channel, as it does `recovery-merged`. This is what replaces ADR 0656 §8's naming in the Approve ask.
- §72 **How it is checked.** Whole (runner, grants part): a forged step result and an author's own review are refused; a
caller not in a verb's `callers`, and any caller but the holder roles and the operator of a verb with no `callers`,
are refused by the bus; a change widening a grant, and one altering the Go build module's test runner, each name it in
`widens`, computed by the running runner, and are delivered. Tests 85 and 86.
### Health and going back
- §73 **The runner's events.** Each on `mesh.seat.mesh-pipeline-runner.event.<event>.<module>.<branch>.<commit or group>`,
a branch or group name escaped as a key's name is, declared in the seat's definition and published by the runner's
account alone (ADR 0666 §24, ADR 0363 §2):
| Event | Says |
|---|---|
| `pipeline-started`, `pipeline-ended` | a pipeline's start, and its end with every step's result, its test counts, the source revision and `widens` |
| `group-ended` | a group's one pipeline's end, on which alone its wants are written |
| `run-started` | a run on request's start, with its members at their commits |
| `step-started`, `step-ended` | each step's start and end: the step, its status in the policy, its outcome, its build-input fingerprint, its test counts, for a carried result what it was carried from, and for a rerun that it is one and the caller the runtime proved; a bypassed step too |
| `wait-held`, `wait-ended` | a step's wait: on a person (approval, code review), on a build node, on the revision view |
- §74 **What a session hears after its merge.** Between its `merge-pull` and the delivery, a session waits with `mesh_wait`
on the runner's `pipeline-started` and `pipeline-ended` of its module, branch and commit, and then on the status
view's `delivered` of the source revision; a failed head reaches it as `pipeline-failed` (*What holds a delivery*).
- §75 **Nothing stands still unseen.** The watchman times each step's owed progress from `wait-held` and `step-started`,
never by run length; a step or a watched branch's head standing still five minutes is its finding `pipeline-stands`,
raised by the condition keeper and cleared on the step's end, or for a head on its `pipeline-started` (ADR 0564 §2,
§4). A step waiting on a person is said as waiting on that person (ADR 0283), never as a stall; a group waiting for
its members, and a merge behind a pending revision, are governed by their own bounds (ADR 0666 §24). At its start the
watchman seeds its open waits from the runner's `stands`, as it seeds `take-in-waits` from `indexer_pending`.
- §76 **The runner's own health.** Its manifest declares its health check, run outside its process (ADR 0240): the runner
answers `show`, and its durable consumers of `step-ended`, `take-in.taken`, `package-published`, `toolchain-agreed`
and `decided` are
each less than five minutes behind; a failed check names which.
- §77 **The runner waits for its view.** With no revision view it says so in `<module>_first_views` and raises `view-waits`
after five minutes, as every part does (ADR 0656 §6).
- §78 **Going back is one act.** A failing new version holds while the old keeps running (ADR 0656 §4); a return to an
earlier build is one value in the placement, the module's `build` in its placement key, set by a placement proposal
(ADR 0666 §24).
- §79 **How it is checked.** Whole (runner, watchman): a step whose worker dies raises `pipeline-stands` within five minutes;
a head with no pipeline raises it and its `pipeline-started` clears it; a step waiting on the operator raises nothing;
a runner restarted mid-step takes its pipelines up again; its health check fails naming a consumer behind. Whole
(assigner): a placement's `build` delivers the earlier build. Tests 76, 77 and 88.
### A recovery fix is an ordinary merge
- §80 **No recovery merge verb.** A fix that recovers the mesh is merged like any change on a watched branch, by a
session's `merge-pull`, and runs its pipeline; no trusted recovery merge verb is built (ADR 0666 §26). The shape this
design sketched for one (a verb `merge` of the seat `mesh-delivery` with `recovery-callers`) is dropped.
- §81 **Named a recovery, said everywhere.** Its author names it a recovery by the seat `mesh-delivery`'s verb `recovery`
(the commit and why), never by a pull request's text: the gate marks the delivery record `recovery` with the caller
the runtime proves, and says `recovery-merged`, which the teller passes on to every channel (ADR 0666 §26). A widening
it carries is said in its `pipeline-ended` like any other (*Who may say what*).
- §82 **How it is checked.** Whole (gate): a merge named a recovery is marked in its record and `recovery-merged` is said;
a recovery named by a description is not. Test 92.
### The tools
§83 Every new state has a tool that reads it and every new act a tool that starts it by hand (ADR 0666 §25), against the
four lines a module's tools answer; every read tool of a store also answers its fill against its cap:
| Part (seat) | Read everything it holds | Do by hand what it does itself | Recover without help |
|---|---|---|---|
| the runner (`mesh-pipeline-runner`) | `show` (one pipeline whole, its steps, step log and outputs), `list` (the pipelines of a module, a branch or a commit), `stands` (what stands still, since when), `why-carried` (where a step's result was carried from), `kept` (the kept results by digest) | `start` (a delivering pipeline for a watched branch's head), `run` (the run on request), `rerun` (one step again) | `accept` (a failed `optional` step, with why), `cancel` (a pipeline, dropping its asks), `ask-again` (a declined `approval`) |
| the indexer (`mesh-catalog`) | `registrations` (with each one's history); `indexer_edges` (an artifact's edges per watched branch), `indexer_order` (the order of a change, its cycles named), `indexer_catalog_difference` (two revisions); `indexer_pending` (a group with its missing members) | `register`, `policy`, `watch`, `unwatch` (each on the operator's Approve) | `indexer_seed_again` (the first registrations' ask raised again), `indexer_take_in_pending` (a group taken in as gathered), `indexer_drop_pending` (dropped) |
| the gate (`mesh-delivery`) | `source` with a branch (`gate_source`: every branch's source revision) | — | `gate_source_again` (say `source-moved` again for a branch) |
| the forge module (`git`) | `git_failed_steps` (the failed `required` steps it holds, by module and fingerprint, with the run that failed each) | — | `git_failed_steps_drop` (drop one, with why) |
| the grants part (`mesh-grants`) | `grants_callers` (the callers each verb admits, read from the seat definitions) | — | `grants_why_refused` (for an account and a verb, the grant it lacks and the definition's line that leaves it out) |
| the build seats | `steps` (the steps detected for an artifact) | `steps` with `again` (detect again) | `drop` (ADR 0664 §4) |
| the builder (`mesh-builds`) | `builder_reserved` (each module's reserved version, §369); the released versions are the catalog's `versions` | `builder_publish_again` (publish the missing artifacts under the reserved version) | — |
| the assigner (`mesh-wants`) | `assigner_why_not` (why a module is not on a machine yet: the pipeline it waits for, its hold, the placement, the node's last `running`) | — | `assigner_release` |
| the watchman | `watchman_views` (how far behind each part's view is, from each part's `view-waits` and first-view tools) | — | each part's `<module>_first_view_again` |
| the operator-channel | `asks` (with each asker's asks held back) | — | `ask-again` (offer a held-back ask now) |
| every module that imports mesh-sdk's small object store package | `<module>_state_objects`, `<module>_sent_objects` | — | `<module>_state_objects_drop`, `<module>_sent_objects_drop` |
§84 Every recover tool writes its act, who and why in the hand-act log of the seat `mesh-hand-acts`. How it is checked:
each tool answers its whole-module test's case, every store's read answers its fill, and `mesh_describe` reads each
(test 93).
### The runner's kept data, and the build capacity
§85 Each kept form names its section or template, its retention and its cap; a state template fixes its own size (ADR 0595
§3), up to 64 MiB. The numbers are estimates, to be read from the fill tools once G runs: an estimated 20 merges a day
(as *The delivery record*), each starting a pipeline for about 3 modules of its repository whose fingerprint it changes,
and an estimated 2 mesh-sdk publishes a day, each rebuilding about 120 artifacts and starting about 120 pipelines: about
300 pipelines a day, of about 6 steps each.
| Part | Kept data | Section or template | Retention | Cap and growth |
|---|---|---|---|---|
| the runner | `pipelines`: one key per pipeline, keyed by watched branch, source revision and module, its steps, statuses, outcomes, fingerprints, test counts and end count | old `state` section | a pipeline that ended passed on a watched branch, 90 days after its end; any other (failed, superseded, cancelled, a run on request's), 7 days after its end; always while it is its branch's newest for its module; deleted by the runner (§330) | 256 MiB; about 300 pipelines a day of about 4 KiB is at most about 105 MiB at 90 days, under half the cap |
| the runner | `step-log`: one entry per step's start and end, its output, its pipeline and the `kept` digests it wrote or carried, by name | old `streams` section | by its age, 100 days, ten days past the longest retention it ages (§330) | 128 MiB; about 3,600 entries a day of about 0.2 KiB is about 70 MiB at 100 days |
| the runner | `step-outputs`: a lint or test step's output | old `objects` section | 7 days | 1 GiB; about 1,200 steps run a day (the rest carried) of about 50 KiB is about 420 MiB at 7 days |
| the runner | `kept`: a package's passing result per level, by digest, with `last-read`, written by each carry that reads it | old `state` section | 30 days after `last-read` | 128 MiB; a publish changes the digest of about 1,000 importing packages at two levels, so about 4,000 entries a day of about 0.3 KiB, about 36 MiB at 30 days |
| the indexer | `versions`: one key per module and version, its artifacts' digests, the source revision and its edge entries (§349, §374) | old `state` section | a module's newest 100 versions, never its highest of a `MAJOR.MINOR`; the indexer deletes the older at each release | 64 MiB; about 120 modules of 100 versions at about 3 KiB is about 35 MiB; at the cap it records no release and says `full`, as *A store filling is said before it is full* |
| the indexer | `build-edges`: the newest build's edge entries per module and watched branch other than the first | old `state` section | replaced by the next build; deleted when the branch is unwatched | under 1 MiB |
| the builder | `reserved`: one key per module and `MAJOR.MINOR` (§369) | old `state` section | until its `version-released`, a release of another `MAJOR.MINOR` of the module, or `builder_reserved_drop` | a few KiB; at the cap the builder reserves nothing and says why |
| the assigner | `written`: the last source revision and end count per module and watched branch (§352) | old `state` section | until the branch is unwatched | a few KiB |
| the indexer and the assigner | `inputs`: a `run` ask's input by write time and sha256 (§354) | old `state` section | until the ask's `step-ended`, at most a day | 64 MiB; an estimated 300 asks a day of under 100 KiB each is under 30 MiB; at the cap the asker refuses the ask loudly, naming the store |
| the runner and the assigner | `findings` (§355), read by their own read tools alone; the keeper hears the findings as events (§383) | old `state` section | an entry deleted when its finding clears | a few KiB |
| every observer (§387) | the stream `<module>-findings`: `finding-written`, `finding-cleared`, `findings-stated` (§383) | old `streams` section for a foundation part, the module's `streams` otherwise | 7 days, its oldest dropped when full; the next statement covers a lost one (§384) | 16 MiB; an estimate of a few hundred findings a day of about 1 KiB is under 2 MiB a week |
| the condition keeper | `findings-views`: one key per observer and node, its last stated set, pending keys and open asks (§388) | the form of its `conditions` | deleted when the module leaves the catalog or the node (§365) | under 1 MiB; about 30 observers on four machines |
| the watchman, the keeper | `machine-away`, `machine-back` (§389); `findings-asked` (§390) | the seats' definitions | 90 days; 7 days | 16 MiB each |
| the runner | `aged`: three step-log sequences (§330) | old `state` section | never deleted | bytes |
| the teller | `widens-said` (§335) | the template `state` | until the branch is unwatched | a few KiB; the template's |
| each build module | `steps` (§326) | the template `state` | one key per artifact, dropped by `steps` with `drop` | under 1 MiB; the template's |
| the indexer | `registrations` and its stream `registration-history` | old `state` and `streams` sections | the life of the mesh, never derived again | 64 MiB (*A module's registration*) |
| the indexer | catalog graph revisions, per watched branch | objects in the old form (ADR 0619 §2) | while a record pins it or it is its branch's current | 512 MiB, as *Two graphs, one source of truth*; a second watched branch on a few modules adds a few MiB |
| the gate | `source`: one key per watched branch | its old `state` section, as today | 64 revisions of each key; a branch's key deleted when `registration-changed` says the last registration watching it unwatched it (`unwatch`) | a few KiB |
| the forge module | `failed-steps`: failed `required` steps by module and fingerprint, with the commits each was seen at | state | an entry deleted once none of its commits is a watched branch's head or the head an open pull request names, judged on each `branch-moved` and each pull request's close | the template's 64 MiB; an estimated 50 failed steps a day of about 0.5 KiB, most gone within days, is under 1 MiB |
| every module that imports the object store package | `<state>-objects` beside each state | objects | an object no key names, its key deleted or its value replaced, until `<module>_state_objects_drop` on a person's word (ADR 0595 §4, ADR 0666 §16) | the template's; nothing reserved until written |
| every sender of the new form, and a foundation part by its old `objects` section | `<module>_sent-objects` (`sent-objects`) | objects, planned by the bus module with no manifest field | 7 days, transport (*Any size*) | the objects template's cap; full is the bus module's `full` |
| the grants part, for each seat event a definition keeps | `SEAT_<seat>_<event>`, among them `SEAT_mesh-pipeline-runner_*`, `SEAT_<build seat>_step-ended`, `SEAT_git_branch-moved`, `SEAT_mesh-builds_package-published` and `SEAT_mesh-catalog_registration-changed` | a stream the grants part makes with its durable consumers | the seat definition's age (`keep`), 90 days unless the definition names less | the seat definition's cap (`keep`) |
§86 **The build capacity.** A pipeline runs a module's steps once per commit on a watched branch, carrying what did not
change, where the check ran before the merge and carried after each trunk move; a group runs one pipeline per watched
branch; a second watched branch runs the steps of the modules it carries once more. A mesh-sdk publish asks about 120
lint steps and the test steps of the importing packages, about 240 more, the rest carried; a newer publish of the same
package supersedes the older one's rebuild pipelines, so two publishes close together run the rebuilds once. The first
pipelines after G's delivery run every module's lint and tests once, since `kept` starts empty (ADR 0666 §21): about
120 modules, one round of the build capacity, taken in the `check` stream's order; from then they run only what
changed. Before G's delivery the operator is told the first round's estimated length, read from the checker's last
full check of every module (its verdicts' run times). A step queued on `check` behind the build capacity is said
`wait-held` on a build node and is owed no progress while queued; the watchman raises one finding per build seat,
`build-capacity-waits`, when that seat's oldest queued step has waited 60 minutes, cleared when it starts. A pipeline
whose commit is named a recovery has its steps asked on the build seats' stream `delivery`, ahead of the first round. The steps go on the build seats' stream `check`,
after `delivery` and before `recheck`, within each node's build capacity (ADR 0525 §4, §5), and a run on request on the
same stream. How it is checked: live, the build nodes' time per day in the week after G, read from the build modules'
step keys and the builder's build log, against the checker's last week (test 99).
### How milestone G itself is checked and delivered
- §87 **Under the clauses as they stand.** Milestone G is checked and delivered under ADR 0656 §5 and the clauses it changed
as they hold before its delivery: the acting parts (the indexer, the build modules, the assigner, the grants part,
the node-engine, and the checker, which makes G's own check) act from the milestone's heads, each first in its step,
in the order of their ordering edges; the trunk-only want, the checker and the Approve per merge as ADR 0661 leaves it
govern this delivery, no exemption is asked and no delivery goes ahead of it (ADR 0667 §2, ADR 0644 §1).
- §88 **Its other parts as any module.** mesh-sdk's small object store package is a package published before the modules
that use it build; the manifest validator moves with the indexer; the runner is placed like any module and acts only
from the next change on; mesh-delivery's `contracts` and `sdk` move under its `modules/` folder (ADR 0667 §3).
- §89 **Readers first** (ADR 0667 §4): an edge of kind 4 each, which the delivery plan derives: the indexer's manifest
validator for the seat definitions' new fields (`callers`, `acts`, `subject`, `keep`) before any definition using
them; the manifest validator for the manifests' new words (`version`, §368; a `*` emitter in `consumes`, §386; the
finding stream an observer declares, §383, §387) and the grants part that makes a `*` entry's durable consumers
(§386), before the keeper's or any observer's manifest using them; on each node the node-engine that reads `form`
and `catch-up` before any manifest writing them. The running checker's validator makes G's own check, so the reading
of these words goes in the reader release of §340, whether or not that validator already takes one of them.
- §90 **The stream maker first** (ADR 0667 §5): the grants part, which makes each kept seat stream and its durable
consumers, before the parts that hear a kept stream.
- §91 **The bus module first** (ADR 0667 §6): the bus module, with `<state>-objects`, the sent store and the grant of each
state's object store to its owner alone, before the modules requiring them, the grants part writing that grant from
the milestone's heads.
- §92 **A bus restart is said first** (ADR 0667 §7): a restart of the bus its new version needs is said to the operator
before it starts.
- §93 **A gap is built, not worked around** (ADR 0667 §8): an order the delivery plan cannot derive from the edges is
built in the delivery system within G, never a step done by hand.
- §94 **A node-engine of the version before applies what it can read** (ADR 0667 §9): it applies every want whose
manifests use no field it does not read, and a want that uses one waits, by the reader edge above, for the new
node-engine on that node.
- §95 **The new assigner and the new merge switch on the first registrations** (ADR 0667 §10). The new assigner, which
writes a want only from a passed pipeline, is delivered before the forge module's new `merge-pull`, which asks no
Approve, and before the runner: an edge of kind 4 from `mesh-delivery-assigner` to each, declared in their manifests'
`needs` since nothing else says it (the design's choice: without it a merge made with no Approve could reach the old
assigner, which writes wants from the trunk with no pipeline). Both switch on one fact, the first registrations
written, said as `registration-changed`: until it the new assigner keeps the trunk-only want and the new
`merge-pull` refuses every merge, naming the missing registrations.
- §96 **Until then, the operator merges a fix at the forge** (ADR 0667 §11). Until that fact an ordinary change waits for
it, and a fix, an indexer fix included, is merged by the operator's own hand at the forge (ADR 0661 §3) and
delivered under the trunk-only want still in force; `indexer_seed_again` asks the first registrations again. Before
the milestone's delivery every merge goes as ADR 0661 has it.
- §97 **What is removed, and when** (ADR 0667 §12). The checker's buckets and bus account and the gate's forge merge token
are removed only after the first pipeline after G's delivery has delivered a change end to end (test 63), the runner
then making every check in the checker's place (ADR 0666 §19), and only on the operator's word, asked once for all
three. The checker then leaves the manifest validator's list of foundation parts (ADR 0666 §4) and is no longer
placed, so no running module declares its buckets and they
are orphans `orphans` lists; they go through ADR 0595's `drop` on that word, after the stream snapshot `drop` takes
(ADR 0595 §4, §5, §10), kept 90 days as the delivery records are; a restore from that snapshot is tested before the
word is asked. The runner is on that list from G's delivery.
- §98 **Told to the operator before it starts**: G's own merges are asked as ADR 0661 has them (ADR 0667 §2, §11); the one
ask of the seed registrations; the bus's restart (ADR 0667 §7); and later the one word for the checker's removal
(ADR 0667 §12).
- §99 **How it is checked.** Tests 94 to 97, and 100 for the assigner's edge.
### Choices this design makes under ADR 0666 and ADR 0667
| Choice | Why |
|---|---|
| §100 The registrations in the indexer's old `state` section with their history on an old stream, capped at 64 MiB, kept for the life of the mesh | the indexer is a foundation part (ADR 0619 §1 to §3, ADR 0666 §4); a registration keeps every version with who and on whose Approve, and nothing derives it again |
| §101 The runner a foundation part, its kept data in old sections, on the manifest validator's list | the bus module's own delivery waits on its pipeline (ADR 0666 §4) |
| §102 The want's compare-and-set keyed per module and watched branch | source revisions are kept per watched branch, so two branches' revisions have no order; comparing across them would let `main` refuse `beta`'s want (ADR 0666 §9) |
| §103 A second start of a pipeline for the same source revision and module, on `take-in.taken` and `package-published`, writes nothing, by the pipeline key's compare-and-set | the runner starts on both events (ADR 0666 §13, §15); the key it already keeps judges a start in one place, so neither producer knows of the other |
| §104 A refused new indexer revision holds later merges on its branch until a revert lands | the validation runs after the merge and before take-in (ADR 0666 §6), so the refused revision has landed; taking later merges in past it would deliver them on a graph its own validator refused |
| §105 Each check at its row's level, every requirement R1 to R18 keeping its named test | the record's rows name the levels; the test budget and one check per release bound what runs |
| §106 The approval asked as the operator-channel's accepted kind `ask` on its work queue | the seat has no verb `ask`: an ask is its accepted kind, by the asker's own name (design 46 §10) |
| §107 The gate no foundation part, its `source` in the old section it already has | the bus module's delivery waits on no act of the gate's; only the key's layout changes |
| §108 The verbs `register`, `policy`, `watch`, `unwatch`, `registrations` and the event `registration-changed` on `mesh-catalog` | one verb per trusted act, each its own ask naming what moves; readers follow the event (ADR 0596) |
| §109 The seed of the first registrations proposed as one ask, with the statuses the check judged until then and approval `bypass`, raised again by `indexer_seed_again` | registering is a trusted act (ADR 0666 §3), and one ask for the whole seed is how placement was adopted (ADR 0567); a declined or expired seed must not leave the pipelines stopped with no way on |
| §110 Code review refused as `required` until forge accounts are bound to bus identities | a review is refused from its author (ADR 0666 §22), which cannot be told until then |
| §111 The seat `git`'s read verb `branch` and event `branch-moved`, kept on its seat stream | registering reads a branch's protection through the seat `git` (ADR 0666 §3); a branch moves by a merge or an admitted push; a stream keeps a move said while the bridge is down |
| §112 The watchman's finding `pipeline-stands` for a step or a head still five minutes, cleared by the step's end or the head's `pipeline-started` | the record's five minutes (ADR 0666 §24), one finding for both, timed from the runner's events |
| §113 The runner's finding `pipeline-failed`, the author's for two hours, then the operator's | ADR 0666 §24 with ADR 0505 §5, §6 |
| §114 A module's watched branch and its `build` in the placement key, set by a proposal | a placement names the branch and a return is one value in it (ADR 0666 §6, §24); every placement change goes by a proposal (ADR 0567) |
| §115 The gate's `source.<branch>`, its verb `source` and event `source-moved` | one source revision per watched branch (ADR 0666 §6), read through the seat (G's prepare, the indexer's watch of the gate's `source`) |
| §116 The build seats' verb `steps` and accepted kind `step` on `check`; the assigner's kind `step` on `mesh-wants`; `step-ended` with the node in its subject | steps are asked on the serving seat's work queue (ADR 0666 §7); the `check` stream already orders after `delivery`; a node's account says only its own node's ends (ADR 0363 §2) |
| §117 The outcomes, with `accepted`, `superseded` and `cancelled`; an approval expired or refused ends `declined`, replaced `superseded` | every end a step can have is said as itself (ADR 0666 §10) |
| §118 `approval` asked last, its `about` the module and the branch | the operator approves a commit the mesh already judged, the point ADR 0661 §3 kept in one place; one ask per module and branch, a newer commit's replacing it |
| §119 A superseded pipeline's asks dropped by `drop`, and a pipeline superseded only when its module's fingerprint changed | a cancelled pipeline must leave no ask on a stream (ADR 0664 §4; G's prepare); a module the commit did not change has nothing newer to run (ADR 0666 §9) |
| §120 A group's wants only on `group-ended` | a group is delivered whole or not at all (ADR 0666 §9; research 086, R5) |
| §121 The six kinds derived from existing words, `needs` per artifact only where nothing says it | one form of each fact (ADR 0656 §6); the catalog entry's `needs` already exists from G |
| §122 The cycle rule in the indexer's graph | the graph is the indexer's, and the checking program goes with the checker |
| §123 The builder's seat `mesh-builds` and its event `package-published` | the record has the builder say the publication as an event of its seat (ADR 0666 §15); its waits move only when a later change gives the seat its verbs |
| §124 Each state's object store `<state>-objects` through mesh-sdk's small package, sent objects readable by a standing grant derived from the manifests, 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 |
| §128 `widens` in `pipeline-ended`, computed by the running runner over every part serving a step, passed on by the teller | a widening is said and nothing refuses it (ADR 0666 §23, §26); a change must not compute its own |
| §129 `merge-pull` judged from the forge module's own `failed-steps`, heard from `step-ended`, any run at the head's fingerprint counting unless a later rerun of that step passed, the protection read again on every `branch-moved` | the record has it judge from the events it consumes, never by a call, so a head merges while the runner is down (ADR 0666 §11, ADR 0596) |
| §130 The new assigner before the new `merge-pull` and the runner, by `needs`, both switching on the first `registration-changed` | a merge with no Approve must never reach an assigner that writes wants without a pipeline (ADR 0667 §10) |
| §131 The forge bridge's finding `branch-unprotected` | a move of a branch whose protection was removed starts no delivering pipeline and is said (ADR 0666 §6) |
| §132 The verb `recovery` of `mesh-delivery` | a recovery is named by its author as a fact the bus keeps with who said it, never read from text (ADR 0666 §26, research 086 R10) |
| §133 No forge status for a pipeline | the step results live on the bus (research 086 R10), shown by the runner and the delivery record; a status would be a second copy outside it |
| §134 The runner's `pipelines` kept 90 days after a passed end on a watched branch, any other 7 days, its step log 100 days by age, its outputs 7, keyed by source revision | a retention the runner judges from its own facts, never from the gate's records (ADR 0596); the delivery record keeps the step events itself (§34), so nothing it traces needs the runner's key; one nothing delivered has no trace to serve after a week; about 300 pipelines a day must fit its caps, so they are 256 MiB and 128 MiB |
| §135 The forge module's `failed-steps` kept until no head it was seen at is a watched branch's head or an open pull request's | an entry judges only a head that can still be merged; once neither names it, nothing can be refused by it |
| §136 The gate's `source.<branch>` deleted when the branch's last watch goes | a branch no registration watches makes no revision, so its key would name nothing |
| §137 The sent store kept 7 days by age, with no person's word | an object sent carries one reply or event and is transport, not kept data (ADR 0595 §4); its readers fetch it within a call's bound or a consumer's lag |
| §138 A replaced object listed `key gone`, as a deleted key's is | no key names it, and one listing lets the drop tool take both on a person's word, as ADR 0595 §4 and ADR 0666 §16 have it |
| §139 The checker's removal after the first end-to-end pipeline, on one word, its buckets through `drop`, the snapshot kept 90 days | ADR 0667 §12 asks one word for all three; 90 days is the delivery records' retention, the longest a trace reads back |
| §140 The pipeline steps on the `check` stream after `delivery` | a delivery's build is never held by a pipeline, as a check never held one (ADR 0525 §4) |
@@ -0,0 +1,376 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
decisions:
- "ADR 0666"
- "ADR 0656"
- "ADR 0648"
- "ADR 0645"
- "ADR 0644"
- "ADR 0641"
- "ADR 0639"
- "ADR 0636"
- "ADR 0633"
- "ADR 0621"
- "ADR 0619"
- "ADR 0618"
- "ADR 0615"
- "ADR 0614"
- "ADR 0603"
- "ADR 0597"
- "ADR 0596"
- "ADR 0595"
- "ADR 0571"
- "ADR 0567"
- "ADR 0564"
- "ADR 0548"
- "ADR 0493"
- "ADR 0441"
- "ADR 0437"
- "ADR 0421"
- "ADR 0363"
---
# 47, milestone G: the delivery record, what the gate owes the forge, the state table, the two graphs and a change's graph difference
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
## The delivery record, and what the gate owes the forge
### The delivery record
*ADR 0656; ADR 0595, ADR 0596, ADR 0597, ADR 0603 §7, ADR 0493 §3, §11, ADR 0633 §4, §5, ADR 0441 §1, ADR 0548 §8,
ADR 0564 §2, ADR 0363 §2.*
§141 From research 085's milestone the gate, holder of the seat `mesh-delivery`, keeps one **delivery record** per
milestone's delivery, a ledger instance (ADR 0595's template `ledger`, ADR 0597) it alone writes (ADR 0656). It
holds:
- §142 **the group's state**: the heads, the verdicts, the checks and the asks, which `merges` holds today; from milestone
G's delivery the pipelines' `step-started`, `step-ended` and `pipeline-ended` in place of the verdicts, the checks and
the asks, kept as long as the record (ADR 0666 §9);
- §143 **the trace**: the milestone, its issues, its pull requests, their commits, and the graph revisions the delivery
moved between;
- §144 **what happened to each entry on each node**: for every entry of the graph difference, taken in, built, checked,
composed, wanted, running healthy, held (with the hold's reason, so a reader of the record sees why an entry stands
still without asking its holder), failed (with what failed), or away (ADR 0656).
§145 It holds no tiers, and only the gate orders work by it, so it is no second copy of the keys, the objection research
063 had to a stored plan (research 085, Findings 10). Its fields, retention and tools are this design's choices under
ADR 0656.
- §146 **The outcomes arrive as events, through a durable consumer of the gate's own.** The gate's manifest `consumes`
each step event of *What each step says* (below), and `wait-held` and `wait-ended` of each holder. The consumer
resumes from the last event applied; an event applied twice changes nothing; an event is applied only when it comes
from its owner's account; and running healthy is taken from the node-engine seat's `running` event, never from a
have read across the seam (ADR 0596).
- §147 **It ends when every present node runs its entries**, and the gate says `record-ended` on the seat `mesh-delivery`
with the record's milestone and its nodes away. It names the nodes away, each with since when, and ends without
waiting for them, as a node away holds nothing (ADR 0603 §7).
- §148 **A record open too long is a finding.** The watchman writes the finding `record-open` for a record still open 24
hours after its merge (a delivery of the whole mesh takes under an hour, so a day means something stands still) and
clears it on `record-ended`; the condition keeper raises it (ADR 0564 §2). The finding names the entries still held
or failed, read from the record's own events. It is written only while no other condition covers the record: while
an entry of it is held by a wait that raised `acting-part-late`, or a store that holds it raised `full`, that
condition already tells the operator why the record is open, and a second one for the same cause would only be noise.
- §149 **A node away that wakes adds its outcomes.** When it wakes and catches up by its wants (*A change's graph
difference is its delivery*, below), its outcomes reach the record through the same consumer as any other, and the
record shows the node no longer away.
- §150 **An Approve given holds at its heads, until G's delivery.** An Approve the operator gave for a group's heads holds while those heads
stand: a check of the same heads asked again, after a main move or for any other reason, records its verdict in
the record and asks the operator nothing again (issue 1972). The Approve is withdrawn only as ADR 0441 §1 has it,
when a member stops being mergeable, and a carried verdict is read as current (ADR 0548 §8). From G's delivery no
Approve is asked per merge; an `approval` step's answer is the runner's, kept in the record as its step's end
(ADR 0666 §10).
- §151 **From G's delivery, a record for every landed merge.** A session merges through the seat `git` and the gate merges
nothing, so every merge is one the gate did not make: the gate opens a record for each source revision of a watched
branch the indexer takes in, a group's members in one, and `forge-made` no longer marks a record apart. A merge its
author names a recovery is marked `recovery` (*A recovery fix is an ordinary merge*).
- §152 **A small fix has a record of its own.** A small fix is a milestone of one issue (ADR 0493 §3), so it has a
delivery record like any milestone; nothing is merged outside a released milestone (ADR 0493 §11).
- §153 **It pins the graph revisions it points at.** The gate says, as the seat `mesh-delivery`'s events `pinned` and
`freed`, which catalog and assignment graph revisions a record starts and stops pointing at, and as
`heads-replaced` that a candidate's heads were replaced or its pull request closed. Only the gate's account may
publish them (ADR 0363 §2). The indexer and the assigner hear them through a durable consumer each, declared in
their manifests' `consumes`, which resumes from the last event applied after a restart, so a `freed` said while one
of them was down is still applied; neither reads the gate's state (ADR 0596; *Two graphs, one source of truth*,
below).
- §154 **A record is kept for 90 days by default**, the gate's setting `record-retention`. Ninety days covers every trace
the operator has asked of a past delivery so far, which went back weeks, never months; the setting can keep records
longer. When the retention removes a record, the gate says `freed` for each revision it pinned.
- §155 **Its size, its cap and its growth.** A record's state of 30 modules on four nodes is about 30 KiB, against the
bus's 256 KiB value limit (research 085, Findings 8). The ledger keeps every operation beside that state: 30 modules
on four nodes with seven outcomes each is about 840 outcome operations a record, at about 0.2 KiB each about 170 KiB,
so a record costs about 200 KiB with its snapshot. At an estimated 20 deliveries a day (a guess from the days of
research 085, not a measurement) the ledger grows about 4 MiB a day, about 360 MiB at the default retention; its cap
is 768 MiB, twice that, so a busier month than estimated still fits. Removing a record at its retention removes its
operations and its snapshot with it, so the ledger's size follows the retention, not the mesh's age.
- §156 **The switch carries today's state in, once.** At the new gate's start, `merges` and every group in flight become
records, and from then on the gate writes `merges` no more; `turns`, which nothing writes while the check gates no
merge, is dropped with the hold (*Nothing merges into a repository under check*). The switch is safe to run
twice: a group in flight becomes a record keyed by its milestone and heads, and a switch that finds that record
already there leaves it as it is, so delivering milestone G again after a revert makes no second record and asks
nothing again. A revert of the gate is the delivery system's restore of its kept data (*A module's kept data on the
bus, backed up and restored with its delivery*).
- §157 **The forge bridge asks the seat for a group.** It reads a group's members from the seat `mesh-delivery`'s verb
`groups`, never the gate's `merges` state, both to set the group on each merged message and to publish a group's
end again (ADR 0656, which changes ADR 0633 §4 and §5; ADR 0596). Its account holds no grant to read `merges`.
- §158 **What the gate owes the forge is said, not watched, and a merge made at the forge has a record too** (*What the
gate owes the forge*, below).
- §159 **Its tools**, against the four lines a module's tools answer:
| Line | The gate |
|---|---|
| read everything it holds | `gate_record` (one record whole: the group's state, the trace, each entry's outcome per node, the nodes away), `gate_records` (every record with its fill against the value limit, and the ledger's fill against its cap), `gate_consumer` (its durable consumer: the last event applied per family, the events waiting, the age of the oldest) |
| do by hand what it does itself | `gate_record_repair` (for one record, asks each owner for the outcome of each entry missing one through that owner's read verb: the indexer's `take-in` read, the builder's build read, the checker seat's check read (from G's delivery the runner's `show`), the seat `mesh-wants`'s `entry`, and the node-engine seat's verb `composition` of that node; applies the answers as the consumer would) |
| recover without help | `gate_record_end` (end a record left open, a node gone for good or an entry that never runs, with the reason, recorded in the hand-act log of the seat `mesh-hand-acts`) |
| one seat, many tools | the seat `mesh-delivery`'s verbs, with `groups` answering a group's members and the acts said for it |
- §160 **How it is checked.** In the gate's whole-module test: the gate stopped and restarted in the middle of a delivery
leaves the record complete with no outcome counted twice; the events `pinned`, `freed`, `heads-replaced` and
`record-ended` are said; a `freed` from an account other than the gate's is refused by the bus; a group approved
before the switch is not asked again after it, nor after milestone G is delivered again following a revert; a record ends once every present node runs its
entries, naming the nodes away; a held and a failed entry are kept with their reason; a record open past 24 hours
raises `record-open`, cleared by `record-ended`; a node away that wakes adds its outcomes and is no longer shown
away; a group's heads approved and checked again ask the operator nothing again; a small fix's merge gets a record
of its own; a record past its retention is removed and frees its revisions; the tools read a record, list the
records with their fill, and a repair completes a record missing an outcome through the owners' read verbs. In the
forge bridge's whole-module test, a group's members are read from `groups` and its end published again from it, and
a read of `merges` by its account is refused. Live: one call of `gate_record` traces an issue of the registry
milestone to each node's running build, also after six later merges; a node away is named with since when and its
outcome taken on waking; a record of 30 modules on four nodes stays under 256 KiB; a group checked again at old
heads after a main move is not asked of the operator again (issue 1972).
### What the gate owes the forge
*ADR 0596 §1, §2, ADR 0656 §7, ADR 0437 §8, ADR 0441 §5, ADR 0633 §1, §4, ADR 0363 §2, ADR 0564 §2; from research 085's
milestone, this design's choices under ADR 0596 and ADR 0656 §7.*
§161 The forge bridge today performs what the gate's states `merges` and `owed` ask (a merge, a status, the delivery's
view, a comment, a line on the commit's note) by watching those keys (ADR 0437 §8), and the forge module writes the
checker's `mesh/check` from a watch of the checker's state `owed`: reads of another module's bus state that ADR 0596
§2 ends when their owner moves. From research 085's milestone:
- §162 **Every act is an event, under one id.** The gate says each act it owes as the event `owed` of the seat
`mesh-delivery`, on `mesh.seat.mesh-delivery.event.owed.<repository>.<number>`, carrying the act, the pull request,
the head and an id; the checker says its status the same way, as the event `owed` of the seat
`mesh-delivery-checker` on `mesh.seat.mesh-delivery-checker.event.owed.<repository>.<number>`. Only the owner's
account publishes each (ADR 0363 §2). The subject per repository and pull request lets the bridge, a person and a
test read one pull request's acts alone. The seat events are kept on each owner's stream `owed`, 7 days and at most
64 MiB (an estimated 20 deliveries a day, with about 15 acts of about 1 KiB each, is about 2 MiB over 7 days).
- §163 **Every act is safe to repeat at the forge.** A status and the delivery's view replace what stands; a merge of a
pull request already merged at that head is answered performed; a comment and a line of the note carry the act's
id, and the seat `git`'s verb skips an id the pull request or note already holds. This is chosen over recording the
id before the act: an id recorded and then not performed (the bridge stopped between the two) would be lost, while
an act repeated changes nothing. The bridge's own state `performed`, a key-value state in the new form (ADR 0595),
form 1, one key per id with the outcome and when, kept 7 days as the stream is, its size the one its template fixes
(ADR 0595 §3), is therefore a
saving and a read, never what keeps an act from happening twice.
- §164 **Every outcome is said, and the gate records it.** The bridge says each act's outcome on its own stream `forge` as
the event `owed.ended`, with the id and one of `performed`, `refused` (the forge's own words), `could not be
performed` (the forge did not answer within the call's bound, with the error) or `undecodable` (an act it cannot
read, with why): an act it cannot decode is said, never only logged. The gate hears the stream `forge` through its
durable consumer, writes the outcome into the delivery record, and says an act's id is answered only once an
outcome is recorded. The checker does the same for its own acts: it hears `owed.ended` for its own ids, records each
outcome in its own state (it watches only its own state), says again at its start, under the same id, every act of
its own still unanswered, and lists one unanswered past 15 minutes in its own stalled read.
- §165 **What is unanswered is said again, and stalls by a bound.** At its start the gate says again, under the same id,
every act its records hold unanswered. An act unanswered 15 minutes after it was said is listed by `stalled`, which
the condition keeper raises as the stalled line (ADR 0421 §7, ADR 0564 §2): fifteen minutes is many times a
forge call's time, and short beside the check's hour.
- §166 **A group's merges follow one another, until G's delivery.** The gate says a group's next `merge-pull` only once the previous member's
merged message is heard (ADR 0441 §5), so a member the forge refuses holds the rest, and none is merged ahead.
- §167 **From G's delivery the gate owes no merge, and the checker nothing.** A session calls `merge-pull` itself (ADR 0666
§11), and a group's members land one by one, the group ending when every member has (*Groups*); the checker's `owed`
goes with the checker, and the runner owes the forge nothing, a pipeline's result living on the bus. The gate still
owes the delivery's view, a comment and the note's line, performed by the forge bridge as above.
- §168 **The bridge catches up from the seat.** At its start the bridge resumes its durable consumer and also reads
`groups`, which answers, per group, the acts the gate said with their ids and their recorded outcomes; an id said
and not answered is performed. Its tools: `bridge_owed` (the ids it performed with their outcomes, and its
consumer's backlog: the last event applied, the events waiting, the age of the oldest), `bridge_owed_again`
(perform one id again now) and `bridge_owed_drop` (drop one id with its reason, written in the hand-act log of the
seat `mesh-hand-acts`, and said as `owed.ended` with the outcome `dropped`).
- §169 **The switch carries what is owed.** At the new gate's start (*The switch carries today's state in, once*) every key
of `owed` and `merges` the old bridge has not performed becomes an act of its record, with an id made of the key and
its revision; the new bridge counts as performed every id whose key and revision the old bridge's own bookkeeping
names, so nothing is done twice and nothing dropped. A merge handed to the forge during the switch is such an act:
its merged message finds the record holding it, so it opens no `forge-made` record.
- §170 **A merge made at the forge has a record too.** A merge the forge reports for which no record holds a `merge-pull`
of that pull request at that head (a person merged it in the forge's own page, as on 2026-11-07 and 11-09) is taken
in like any other merge (*Taken in by the indexer it names*), and the gate opens a delivery record for it, marked
`forge-made`, with no Approve, so its trace and its outcomes on the nodes are kept like every delivery's and no
later merge waits behind a merge the gate never heard. The gate says it as the delivery's `transition` with the why
`forge-made`, which the teller passes on to the operator (the operator, 1921's note of 2026-11-07: "a merge made in
the forge is taken in like any other").
- §171 **How it is checked.** In the forge bridge's whole-module test: an `owed` said while the bridge is stopped is
performed once after its restart; the bridge stopped between an act and its `owed.ended` performs it again on its
restart and the forge holds it once (the comment and the note by their id); an act said twice is performed once; an
undecodable act is said `undecodable`; `bridge_owed`, `bridge_owed_again` and `bridge_owed_drop` answer, the drop
writing the hand-act log; its account cannot read the gate's or the checker's state. In the gate's: an act left
unanswered is said again at the gate's start under its id and listed by `stalled` after 15 minutes; a group's second
`merge-pull` is said only after the first member's merged message; the switch with a merge handed and not yet
merged makes no `forge-made` record; a merged message with no record's merge opens a `forge-made` record, said as
`transition` with why `forge-made`, and a later merge in the same repository is taken in behind it. Acceptance
tests 53 and 51.
## The state table, from milestone G's delivery
§172 **From milestone G's delivery the gate keeps no merge states.** With no check before a merge and no Approve per merge
(ADR 0666 §10, §17), nothing is `proposed`, `ready`, `rejected` or `held` before a merge: a commit's states are its
pipeline's steps, kept by the runner (*Pipeline steps and the policy*). The table, the gate's `table`, `recheck` and
`close` and the one key per pull request go with the check; `stalled` lists the acts owed to the forge past their 15
minutes and the records `record-open` names.
## Two graphs, one source of truth
*ADR 0656, which supersedes ADR 0645 to ADR 0648; ADR 0595,
ADR 0596, ADR 0603 §2, §6, §7, ADR 0615 §1 to §6, ADR 0621 §1, §2, ADR 0614 §3, ADR 0567 §5, ADR 0571 §8, ADR 0614 §4, ADR 0618 §2, ADR 0619 §1 to §3, ADR 0636 §1,
§3, §4, §7, ADR 0639 §7, §8, ADR 0564 §2, ADR 0363 §2.*
§173 From research 085's milestone. One graph answered two questions, which modules exist and which version runs where,
and one module running as several placed versions would have given one entry two shapes and one key two writers; two
graphs answer them, the second computed from the first, so there is one source of truth (ADR 0656; research 085,
Findings 11). How each is kept, read and retained is this design's choice under ADR 0656.
```
manifests ──► indexer (mesh-catalog) ──► catalog graph revision ──┐
├──► assigner (mesh-wants) ──► assignment graph revision
placements (mesh-module-assigner) ───────────────────────────────┤ │ │
node capabilities (node-engine seat) ────────────────────────────┘ │ └─► wants, from a trunk revision only
▼
a change's assignment graph − the current one = its graph difference = its delivery
```
- §174 **The catalog graph is the indexer's.** The indexer, holder of the seat `mesh-catalog`, keeps the modules as their
manifests declare them; it is today's module graph, and *module graph* stays its other name (ADR 0656). At each
revision it holds one entry per module: its bases, its packages, the toolchain version and recipe version of the
build module that builds it as ADR 0603 §2 has them, its provides and requires, its invokes and serves, and its
fingerprint, with the rules the seats declare (*An acting part's new version acts first*, below).
- §175 **The assignment graph is the assigner's, computed and never written by hand.** The assigner, holder of the seats
`mesh-module-assigner` and `mesh-wants`, computes the module versions placed on nodes, with the edges between
versions, from the catalog graph, the placements and the nodes' capabilities, and nothing else writes it
(ADR 0656). Each entry is one module version on a node; its edges are provisioning, contribution, and the acting
parts' versions that are to act on it (ADR 0656), whose rules and versions come in with the catalog graph. It is
kept, but it is no stored plan: it has one writer, no part orders work by a stored plan, and any revision is computed
again from the inputs it names and compared with the one kept, by a tool (below), so a kept revision is a checked
derivation, never a second source of the kind research 063 removed (research 085, Findings 5).
- §176 **Each graph is kept per revision, and each revision names its inputs** (ADR 0656). The indexer keeps a catalog
graph revision for each source revision it takes in; the assigner keeps an assignment graph revision for each change
of its inputs, naming the catalog graph revision, the placements and the capability readings it was computed from.
Both parts are the foundation (ADR 0619 §1), so both keep their revisions as objects declared in an `objects`
section in the old form, each with a cap it declares there, never in the new form (ADR 0619 §2), and neither waits
on the bus module to start; the manifest validator refuses either manifest that declares them in the new form
(ADR 0619 §3). An assignment graph revision is announced by an event of the seat `mesh-wants` and served by its
verbs. This replaces ADR 0603 §2's one key per module with five past values as the graph's form (ADR 0656, which
changes ADR 0603 §2); the keys are written no more, and a revert of milestone G restores them from their backup
(*Reverted*, below).
- §177 **A revision is made by a merge, and also with no merge.** The indexer makes a catalog graph revision when it
takes a source revision in. The assigner makes an assignment graph revision on each new catalog graph revision, on
the seat `mesh-module-assigner`'s event `assignments-changed` (ADR 0567 §5, ADR 0641), and on the node-engine seat's event
`capabilities-changed`, which a node-engine says when the capabilities it states in its node record change
(ADR 0614 §4), so the assigner follows an event and never reads a node record across the seam (ADR 0596). A
placement change is traced like a merge. At its first start with no revision kept, the indexer takes in the head the
gate's source names for every repository by its catch-up path and keeps that as revision 1, and its verb `revision`
answers that it waits until then (lead-47's ruling in G's prepare: a first start has no merge to make a revision).
- §178 **A revision is kept while a record pins it or it is current.** A revision of either graph, and the input revisions
it names, is removed when no delivery record pins it and it is not current: neither trunk's newest revision nor the
newest of an open candidate. Pins are counted per revision, so a revision two records point at stays until both
have said `freed`. The indexer and the assigner remove one only on applying `freed` or on a revision ceasing to be
current, never on a timer and never by reading the gate's state (ADR 0596; *The delivery record*, above). A trace
must read every revision it moved between, and a revision neither pinned nor current is read by nobody.
- §179 **An unchanged entry is stored once.** A revision is kept as its list of entries' digests, and each entry as an
object of its own named by its digest, so an entry that two revisions share is stored once; an entry object is
removed when no kept revision lists it. 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
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
for each, many times the steady state. Placement-only and capability-only revisions are pinned by no record and go
when they stop being current.
- §181 **A store filling is said before it is full.** The indexer, the assigner and the gate each say their fill against
its cap in their read tools, and the event `filling` of their seat when a store passes 80 % of its cap; the watchman
writes the finding `store-filling` naming the part and the store, the condition keeper raises it (ADR 0564 §2), and
it clears when the store falls below 70 %. A full cap refuses loudly, naming the part and the store: the indexer
keeps no new revision and says the merge pending with the reason `objects full`, the assigner keeps its last
revision and writes no new want, and the gate opens no new record and holds the merge; each says it as the event
`full` of its seat, which the condition keeper raises as urgent. When a removal brings the store back below its cap,
the part says `room` of its seat, takes the refused work up again (the pending merge, the next revision, the held
record) and the condition clears; a condition that never clears would hide the next fill. Nothing is dropped to make
room: a removal is only the rule above, and the setting `record-retention` or a larger cap is the operator's.
- §182 **What happened is not in the graph.** An entry says what is to run and what it waits on; whether it was built,
wanted or runs healthy is said by the parts as their events and kept in the delivery record (ADR 0656).
- §183 **The want stays the one record a node reads.** The assigner writes `want.<node>.<module>` from a trunk
revision's assignment graph only; a node-engine reads its want, never the assignment graph, and the have stays what
it runs (ADR 0656, which changes ADR 0571 §8).
- §184 **From G's delivery, per watched branch and between artifacts.** The catalog graph is kept once per watched branch,
each revision naming its branch, and its edges run between artifact references, of the six kinds of *Dependencies
between artifacts* (ADR 0666 §6, §14). The assigner computes a node's entries from the catalog graph of the branch its
placement names, and writes a want from a revision of that branch whose `required` steps passed, in place of a trunk
revision's alone (ADR 0666 §9, which changes ADR 0656 §2).
- §185 **Reverted.** Only the new form is written: the indexer, the assigner and the gate keep nothing of ADR 0603 §2's
keys or of `merges` and `turns` beside the new forms, since nothing reads them after milestone G. A revert of
milestone G is a change like any other, checked and delivered, and the delivery system restores what it must
(*A module's kept data on the bus, backed up and restored with its delivery*, under *A backup before the apply*).
Milestone G changes the form of these kept data, and raises their `form` (a kept data declaring none reads as form
0, so G's own move takes their restore points):
| Part | Kept data before G (form 0) | After G | Restored at a revert |
|---|---|---|---|
| the indexer | the state `modules` (ADR 0603 §2's one key per module) and its durable consumer's position on the forge bridge's stream `forge`; the states `holds`, `engine-reads` and `hold-bounds` of its mesh-wide hold | catalog graph revisions as objects, form 1; `modules` no longer declared, nor its tool `indexer_read_modules` or its view; the mesh-wide hold, its three states and its tools `indexer_holds` and `indexer_engine_reads` gone, the wait per node being the assigner's (*Composing waits per node, by the assigner*); every reader of it reading the catalog half (*Every other reader of which modules exist*, under *The revision view*, below) | yes: `modules` and the hold's three states as they stood at G's delivery, and the consumer's position |
| the gate | the states `merges` and `turns` | the delivery record's ledger, form 1; `merges` and `turns` no longer declared | yes: `merges` and `turns` as they stood at G's delivery |
| the assigner | the wants, written from the indexer's `modules` | the same wants, form 0, written from the assignment graph; the assignment graph revisions as objects, form 1 | no |
The wants keep their form: their keys and values are unchanged, only what the assigner computes them from changes,
so G raises no `form` on them and a revert restores none. Restoring the wants would send nodes back to older
fingerprints until the reverted assigner computed them again; leaving them, the nodes keep what runs and the reverted
assigner writes them again from the caught-up indexer. The assigner's placement and every other kept data of the
three keep form 0 and are never restored, so nothing decided since (a placement, a verdict, the source revision) is
set back. The restored `modules` is as it stood at G's delivery, so a module added since is missing from it until
the reverted indexer takes in its next source revision, and a reverted reader that learns which modules exist from
`modules` does not name it until then. What a reader granted since stays: the forge module's write grants on a
repository it learnt of from the catalog half are kept, since it grants writes and takes none away, so a revert
costs no repository its account's writes.
### A change's graph difference is its delivery
- §186 **The difference names everything a plan named.** For a change (a pull request's heads, a delivery group's
heads, or a merge), the indexer gives its catalog graph and the assigner its assignment graph. Compared with the
current revision, the difference names the new versions, the moved edges, the touched nodes and the order, read off
the edges, and no plan is stored (ADR 0656). The assigner serves it by the seat `mesh-wants`'s verb `difference`,
for two revisions of the assignment graph or for a candidate against trunk. ADR 0603 §6's tiers, as ADR 0633 §5
changes it, are a view worked out from that difference (ADR 0656, which changes ADR 0603 §6), read through
`difference`, with the waits and holds read through their owners' tools and the pending revisions through
`indexer_pending`.
- §187 **The preview is a candidate's difference.** Issue 1874's plan preview of a group before its merge is its
candidate's difference, read by one call of `difference`, and needs no code of its own: a candidate's graphs are
computed for its check anyway, so the preview costs nothing more.
- §188 **A changed fingerprint with no difference is refused by name.** When a module's fingerprint in the change
differs from the running one while the difference is empty, the assigner refuses it, naming the module, and the
check fails naming it.
- §189 **A candidate's graphs are kept apart, and dropped with its heads.** A candidate's two graphs are computed per
check, kept apart from trunk's, and dropped on the gate's event `heads-replaced`, said when the candidate's heads
were replaced or its pull request closed. A candidate costs a computed graph per check.
- §190 **Only a trunk revision writes wants**, whether a merge taken in or a placement or capability change made it; a
candidate's graph writes none (ADR 0656). From G's delivery a want comes from a revision of the watched branch a
placement chose whose `required` steps passed, and a candidate is a run on request's: the indexer builds it on the
runner's `run-started` and drops it on that run's `pipeline-ended`, the gate's `heads-replaced` no longer making
candidates (ADR 0666 §9, §18).
- §191 **Each part acts on its own entries.** The builder, the build modules and the grants part each act when their own
entries in the new revision differ from what they run (ADR 0656), reading them through the seat `mesh-wants`'s verbs
and events, never its bus state (ADR 0596), and naming no other module (ADR 0656). The node-engine acts on its want
alone, as it does today: the want is the one record a node reads, and handing every node the assignment half would
have each node read every other node's placements.
- §192 **A node asleep catches up when it wakes.** A node away holds nothing and catches up by its wants (ADR 0603 §7);
a want's new form reaches only a node-engine that says it reads it (ADR 0614 §4, ADR 0618 §2).
- §193 **No delivery goes ahead of a change.** A change is delivered as its own difference, its acting parts' new versions
first in their own steps (ADR 0656); the one exception is the delivery before research 085's milestone (*The
bootstrap delivery, and the one milestone after it*, under *Milestones*; ADR 0644).
@@ -0,0 +1,354 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0663"
- "ADR 0660"
- "ADR 0656"
- "ADR 0644"
- "ADR 0641"
- "ADR 0639"
- "ADR 0636"
- "ADR 0635"
- "ADR 0625"
- "ADR 0621"
- "ADR 0619"
- "ADR 0615"
- "ADR 0614"
- "ADR 0603"
- "ADR 0596"
- "ADR 0584"
- "ADR 0564"
- "ADR 0548"
- "ADR 0525"
- "ADR 0498"
- "ADR 0493"
- "ADR 0448"
- "ADR 0441"
- "ADR 0404"
- "ADR 0363"
---
# 47, milestone G: the revision view, what ADR 0656 leaves to this design, the choices under ADR 0656, what each step says and their tools
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
### The revision view
- §194 **A pair, each half served by its one owner** (ADR 0656). For a trunk revision, or for a candidate's heads before
any merge, the indexer serves the catalog half by the seat `mesh-catalog`'s verb `revision`: the catalog graph, the
heads, the seat definitions, the manifests' verdicts and the fingerprints. The assigner serves the assignment half by
the seat `mesh-wants`'s verb `revision`: an assignment graph revision, named by its own revision and naming its
inputs, the catalog graph revision, the placements and the capability readings. A part's view for the current
revision is the newest assignment half and the catalog half it names. `seat-definitions` stays the seat list's verb
(ADR 0636 §4), and the catalog half carries the same definitions at its revision.
- §195 **Every part takes a first view, then follows the events.** The checker, the builder, the grants part and the
indexer's own later passes take a first view from the two verbs and then follow the two seats' events, never a list
compiled into their build nor another module's bus state (ADR 0656, ADR 0596). The node-engine reads only the catalog
half, for the seat definitions it composes with, and its want; it never reads the assignment half (*Each part acts on
its own entries*, above). Of the view the assigner reads only the catalog half, never its own output back; its other
inputs stay the placements and the capabilities (ADR 0656). A running part acts only on a trunk revision's view; a
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).
- §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
manifest findings), the status view (`view_reach`), the forge module (which repositories its account writes), the
holders of the seats `mesh-config`, `mesh-provisioner` and `mesh-self-check`, the claude-code module and the tool
runner. Each takes a first view of the catalog half by the seat `mesh-catalog`'s verb `revision` and follows its
event `catalog-revision`, through `meshcatalogclient` in Go or the seat's generated TypeScript contract in the forge
module, names the seat and never the indexer, and keeps no list of its own (ADR 0656 §6, ADR 0596 §2; ADR 0615 §1,
under which every reader of the indexer's state moves in one milestone). A reader that misses a `catalog-revision`
(a gap in the revisions it heard) asks `revision` once more, said in its log (ADR 0615 §5). The design's choice
under ADR 0656 §6: one form of the fact, never `modules` kept beside the catalog graph for these readers, which
would be a second source of the same fact. How it is checked: test 61.
- **The forge module's tools come from mesh-sdk's TypeScript side.** mesh-sdk's TypeScript side has no view
today; milestone G adds there a view follower with its read and ask-again tools, beside the proved caller, a
verb's `callers` and a seat's publish, so the forge module, reading through the seat's TypeScript contract,
answers `<module>_first_views` and `<module>_first_view_again` as the Go parts do from `go/views`, with no
follower or tool of its own. Its cost: a release of the TypeScript side rebuilds every TypeScript module, which
the delivery carries like any change of a package its users build in.
- **The order is the module graph's, and the foundation's own rule where the graph has no edge.** Six of the
readers declare the verb in their `invokes`, so the served-verb edge delivers none of them to any node before a
build serving `revision` runs (ADR 0615 §2, ADR 0614). Two are left out of the graph's order (ADR 0614 §3, ADR
0615 §2): the tool runner, a foundation part, and the forge module, on a cycle with the indexer, which reads the
forge through the seat `git`. For both the order is carried by ADR 0615 §4 and §6: the owner answers at once from
what it holds, and a reader started before it waits, saying so. The tool runner asks `revision` of the indexer's
own runtime, never through itself, the tool's `callers` list naming it (ADR 0663, which changes ADR 0621 §1, §2 and
ADR 0615 §3; accepted); the forge
module answers the indexer's reads through the seat `git` before it has a first view of its own, so neither waits
on the other.
- **With no view, a reader waits; it never reads "no modules".** All eight follow *A part with no view says it
waits* (below): an empty or missing view is never taken as a mesh with no modules, each says `view-waits` after
five minutes, and once it has a first view it keeps working on what it heard while the indexer is down (ADR 0615
§6).
- **A candidate's revision moves no reader.** `catalog-revision` says whether it carries a trunk revision or a
candidate's, and a candidate's changes nothing in any of these readers; the forge module grants no write before
its first view and none from a candidate's revision (ADR 0656 §6: a running part acts only on a trunk revision's
view).
- §197 **The trunk's heads come from the catalog half.** The checker takes the trunk heads a check merges onto from the
catalog half, which carries the heads, and never reads the gate's state `source` (ADR 0596 §2); with the hold and
the turns removed it reads nothing of the gate at all (*Nothing merges into a repository under check*).
- §198 **The indexer reads every definition before it judges.** At its start the indexer reads the seat definitions of
every contracts module's trunk through the forge (the seat `git`'s verbs) before it judges a manifest or answers
`seat-definitions`, and only then follows the forge bridge's stream; a definition merged while it was down is
therefore never judged against a list that lacks it. The design's choice under ADR 0636 §1 and §4, as the operator
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
seat's pass by mesh-sdk's `SeatsToPassOn` (ADR 0660 §3), reading the manifest of the build each assignment runs
(ADR 0660 §2). 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`,
so a pass is decided with no call to a node that may be away. The seat's stream keeps the last message per subject
with no age limit, so the last `running` of every module on every node stays on it however long ago it was said,
and each part's durable consumer starts from the last message per subject, never from now: a consumer made after a
node's last `running`, and two parts' consumers made at different times, hold the same builds. The event carries the module, the fingerprint and the
digest of the build's manifest, which is the indexer's manifest of that build (ADR 0660 §2's "the indexer's
manifest of it"), read by digest from the catalog half; the builder's done key is not needed, since the event names
the digest it would give. A node that has said no `running` for a module since the part's consumer began counts by
the trunk's manifest, as ADR 0660 §2 has it where the running build cannot be read, and both parts apply that same
fallback in the one function (ADR 0660 §3). Never another node's have key, its want or the assignment half (ADR 0596
§2; *Each part acts on its own entries*). The design's choice under ADR 0660 §2: the node that runs a build says what
it runs, and an event kept by a durable consumer answers when that node is away. How it is checked: test 56.
- §200 **A part with no view says it waits.** It names the revision it has no view for, acts on nothing new, and keeps
running what it runs, as ADR 0636 §1 has a reader of the seat list do; the checker, the builder and the grants part
wait too, and wait for the view, not only the seat list (ADR 0656, which changes ADR 0636 §1); the node-engine waits
for the catalog half as ADR 0636 §1 already has it wait for the seat list. Each answers in its runtime's first-view
tool (below) the revision it holds, the one it waits for and since when, and after five minutes without a view writes the finding
`view-waits` naming itself and the revision, as a reader of the seat list does, which the condition keeper raises
(ADR 0564 §2); it clears when the view arrives. Five minutes is the bound the seat list's readers already keep, so an
indexer or assigner down for a restart raises nothing. A part restarted while the indexer or the assigner is down
waits and says so.
- §201 **Each process knows its own build from its start.** The node-engine hands every process it starts the
fingerprint of the build it starts it with, and the process takes its own build from that, never learning it later
(ADR 0656, which changes ADR 0639 §7 from the indexer to every process). *The fingerprint handover*, below, says its
form.
### What ADR 0656 leaves to this design
§202 ADR 0656 leaves how each part is built to this design, under issue 1921; these four needed a shape of their own.
- §203 **An acting rule's fields.** The seat's definition is where ADR 0656 has an acting part declare what it acts on. A
seat's definition gains one optional field `acts`, a list of rules, each with three fields of its own: `step`, one of `take-in`,
`validate`, `check`, `build`, `compose`, `grant` and `place`, and from G's delivery `pipeline`, the runner's, in
`check`'s place (ADR 0666 §19); `on`, either `every` (every module) or a `language`
naming the language of the modules it acts on, matched against what the manifest says its language is, as the
indexer's language map matches today; and `bound`, how long an entry waits in that step for the acting part's new
version (*An acting part's new version acts first*, below, gives each step's default). The definition format's own
schema (design 26) gains the field, and the format stays 1. The indexer reads it into the catalog graph, the assigner
into edges, and the checking program from the revision view. **Its cost in mesh-sdk:** the seat-definition reader in
mesh-sdk's runtime refuses a definition with a field it does not know or a format other than 1, so a reader built
before the field refuses every definition that carries `acts`, and a refused definition blocks new claims of that
seat only (ADR 0636 §3), never what runs. The runtime therefore learns `acts`, which rebuilds every module once
(ADR 0656, Consequences); that is the same rebuild the fingerprint handover makes, so the milestone rebuilds every
module once in all, and no other module's code changes. A definition carrying `acts` reaches only readers built with
that runtime: the seat definitions that gain it are members of milestone G's group, and every reader is rebuilt in
the same delivery; the format's reader itself is delivered before them, as a release of mesh-sdk's seat-definition
reader merged ahead of G's check (the operator's choice of a reader first, 2026-11-09; ADR 0667 §4). The bootstrapped
definitions every reader carries from its start (ADR 0636 §2), of which `node-backup` carries no `acts`, are made
again from the new definitions in the same change, and mesh-sdk's existing test that each bootstrapped copy equals its definition holds
them to it.
- §204 **One wiring of the seat list's reader.** The reader of the seat list and its two tools of ADR 0636 §7 are wired once,
by mesh-sdk's runtime (`go/seatlist`), for every part that reads the list; no part wires its own. Four delivery
parts (the assigner, the checker, the indexer and the grants part) each carry a hand-wired copy today, each with its
own pair of tools, and each copy would have to learn `acts` apart. The design's choice under ADR 0636 §7, which puts
the tools in the runtime bundle: one wiring learns `acts` once, and every part's two tools are the same, named by
their module, with the same callers (any person and the healers to read, the operator and the healers to read
again), the node-engine's among them. How it is checked: mesh-sdk's seat-list test registers both tools for a
fixture part; a search of the delivery parts' code finds no wiring of its own.
- §205 **The fingerprint handover.** The node-engine starts each process, a service or a container alike, with the
environment value `MESH_FINGERPRINT`, the fingerprint of the want it applies; mesh-sdk's runtime reads it once at the
process's start and holds it as the process's own build (ADR 0656). A process started with no value says `own build
unknown` in its runtime tool and its journal, and the indexer acts on it as ADR 0639 §8 has it. **Its cost:** one
change of mesh-sdk's runtime, which rebuilds every module once (ADR 0656, Consequences), and one of the
node-engine; the order between them is the composing step's wait of the node-engine (*Order without a person*,
below), so a module whose runtime reads the value is composed only by a node-engine that hands it.
- §206 **The build seat's ask that runs a program in the sandbox.** The checker asks the seat `node-build-go` for an ask
`run` on the build seats' `check` stream: one whole job on one node (ADR 0525 §3), which fetches the named heads,
builds the named program at them, runs it in the run step (ADR 0448 §2) with the input the ask names by its sha256 in
the asker's own state (the checker's `inputs`, a day), asked of the seat `mesh-delivery-checker`'s verb `input` and
checked against that digest, laid as a file, and writes the program's exit and its answer, carried whole, on the
build module's step key named by the ask's id (ADR 0525 §6). It is an accepted ask of the seat, not a request verb, because it is a
long job pulled where there is capacity, never a call to a named node. Only the checker's account may publish `run`,
and from G's delivery the runner's and the indexer's (its validation before take-in), each under its own name.
Every step key of a `run` ask starts with the asker's name (`checker.<ask id>`), so the checker's read grant is fixed
in advance to its own prefix and never follows ask ids one by one (ADR 0363 §2); no other part can have a program run
or read its verdict. From milestone G the seat definition of `node-build-go` in `mesh-delivery-contracts` names it; B0
brings the ask with its types in mesh-sdk's `go/delivery` and the grants check asks have today (*The bootstrap
delivery, and the one milestone after it*). The checking program, the acting parts and the composer a check builds
from a change all run through it (ADR 0656, ADR 0644). A program is built once per fingerprint: the build is the
builder's build of that fingerprint (ADR 0498 §1), kept like any build and reused by every later ask naming it, so two
checks of changes that leave the checking program unchanged ask one build. Only a build made in a run step that runs
nothing but the build is kept and reused: a program's own run happens in a separate run step over a copy, so a program
that rewrites its own binary while it runs changes no kept build. A candidate's build is never delivered to a node: a
want names only a trunk revision's fingerprint (*Only a trunk revision writes wants*), and a candidate's build is kept
only for checks.
- §207 **The first tier's own bound.** A check's first tier has 20 minutes for all its builds together, inside the check's
one-hour bound of the state table and the checker's step bound (ADR 0639 §5); an acting part not built within it is
one the first tier cannot build, and the check fails naming it (ADR 0656). The verdict tells two cases apart: `waiting
for a build node` when no build node of the language took the ask within the bound, and `did not build` when one
took it and the build failed, so the operator knows whether to free a build node or fix the change.
### Choices this design makes under ADR 0656
§208 ADR 0656 decides the direction and leaves every choice made in working it out to this design, each with its reason
(ADR 0656, Consequences; the operator, 2026-11-06 20:3x). The choices, and why:
| Choice | Why |
|---|---|
| §209 Both graphs kept as objects in an `objects` section in the old form, each with a cap of 512 MiB | both parts are the foundation, whose new kept data goes on the old sections so neither waits on the bus module to start (ADR 0619 §1, §2); the caps are many times the estimated 90 days |
| §210 An unchanged entry stored once, a revision being a list of its entries' digests | consecutive revisions differ by a few entries, so the stores grow by what changes; a larger cap over whole revisions would only delay the same fill |
| §211 No old form written beside the new; a revert restores the parts' kept data on the bus from a restore point taken before they moved, then they catch up forward | only the new form is looked after (the operator, 2026-11-06 21:3x), and a revert still works, as the delivery system's capability for every change of a kept data's form |
| §212 A module's kept data on the bus backed up before a `form` change and restored beside the live data at a revert, a missing restore point holding the revert | ADR 0404 covers only the items on a module's node, so the bus's kept data had no restore point; a restore never goes over live data, and a revert with nothing to restore must not start on the newer form unasked |
| §213 Only kept data whose `form` changes restored, the wants never; at a revert the node-engine last on its node, and the reverted assigner and gate only after the reverted indexer took in the trunk's head | restoring anything else would set back what was decided since; restored wants, or an assigner reading an indexer not yet caught up, would send nodes to older fingerprints; the node-engine of before may not know how to restore |
| §214 A restore point holding the consumers' positions and the catch-up tool the newer manifest declares, called after a restore | the reverted build then hears everything since as after any restart; the stream `forge` keeps 7 days, so what fell out of it is taken in from the trunk by the part's own tool |
| §215 A revision removed when no record pins it and it is not current, pins counted per revision | a trace must read every revision it moved between, and a revision neither pinned nor current is read by nobody; counting pins keeps a revision two records share |
| §216 The indexer and the assigner hear `pinned`, `freed` and `heads-replaced` through durable consumers | an event said while either was down is applied when it comes back, so no revision leaks and none is dropped early |
| §217 Records kept 90 days by default, the gate's setting `record-retention`, the ledger capped at 768 MiB, a record's operations removed with it | the traces asked so far went back weeks; about 840 outcome operations make a record about 200 KiB, so 90 days at an estimated 20 a day is about 360 MiB, and the cap is twice that |
| §218 `record-open` only when no other condition covers the record | the cause is already told once; a second condition for it is noise |
| §219 `full` cleared by `room` when the store falls back below its cap | the refused work is taken up again, and a condition that never clears would hide the next fill |
| §220 The switch into records keyed by milestone and heads, safe to run twice | delivering milestone G again after a revert makes no second record and asks no group again |
| §221 A store passing 80 % of its cap said as `filling` and raised; a full cap refused as `full`, naming the part | the operator hears of a store filling while there is time, and a full store stops work loudly instead of dropping what a trace needs |
| §222 A candidate's graphs kept apart and dropped on `heads-replaced` | a candidate must never write wants or be read by a running part, and its graphs mean nothing once its heads are gone |
| §223 The verbs `revision`, `entry` and `difference` on the two seats | each half of the view, and the difference, is served by its one owner through its seat, never read from its state |
| §224 The node-engine reads its want and the catalog half, never the assignment half | the want is the one record a node reads; the assignment half would have each node read every node's placements |
| §225 Composing waits per node, held by the assigner, with no new field in the want: a node's node-engine want first, its other wants once its new node-engine runs | a node away holds only itself (ADR 0603 §7); an old node-engine reads its want as it is, so it is reached with no form it cannot read |
| §226 Capability changes said as the node-engine seat's event `capabilities-changed` | the assigner follows an event instead of reading node records across the seam (ADR 0596) |
| §227 The step events, their fields and their subjects, with the node in the subject of every event a node publishes | every outcome reaches the delivery record as the event of the part that did it (research 085, Findings 7), and a node's account can be granted only its own node's subjects (ADR 0363 §2) |
| §228 A first tier of only the changed acting parts, bounded at 20 minutes, its verdict telling `waiting for a build node` from `did not build` | an ordinary change pays nothing more; a bound turns a build that never ends into a named failure, and the two words tell the operator which act frees it |
| §229 The build seat's ask `run`, published only by the checker's account, its step keys starting with the asker's name | the program is a long job pulled where capacity is, run in the sandbox every check already uses (ADR 0448 §2), and no other part can have a program run or read its verdict |
| §230 A program built once per fingerprint and reused, only from a run step that runs nothing but the build; a candidate's build never delivered | a check that leaves the checking program unchanged costs no build, and a program run from a change can never change a kept build or reach a node |
| §231 Every program built from a change run with no network and no bus account | a change not yet merged is not trusted, and the sandbox already keeps the logins outside (ADR 0448 §2, §4) |
| §232 Every change of the checking program's fingerprint named in the verdict and the Approve ask, with the rule names of the trunk's and the heads' programs compared by the running checker outside the sandbox as its detail | only the fingerprint covers a helper a rule calls; the comparison is made from the sources by a part the change did not build, so a program that drops a rule and hides it is still named; a rule widens or narrows what judges or acts on every module, so the operator sees it before approving |
| §233 A rule matching a module with no holder for its step held by name, and its check failed | a rule nobody serves would otherwise pass in silence |
| §234 Each step's wait held by the part that does the step, with its own read and release tools and the events `wait-held` (at the wait's start, with its bound) and `wait-ended`, timed by the watchman, whose clock starts when the new version's build is done | the part that does the step already holds its state; the watchman timing from `wait-held` still raises a wait whose holder stopped, and counting from the build done leaves a slow build to the build's own wait |
| §235 The bound per step in the acting rule, with a default per step | the time a step's new version needs is the acting part's to know, not another module's |
| §236 A build module's wait ends on the agreed toolchain naming the new build module | the agreed toolchain is what a fingerprint takes (ADR 0584 §1), so before it names the new build module no module could be built once by it |
| §237 The running indexer makes a catalog graph revision with the named indexer's entry | the newer indexer can then be wanted and granted, so a revision waiting for it is never stuck behind it |
| §238 The acting rule's field `acts` with `step`, `on` and `bound`, the definition format staying 1 | the steps and the language match are what the indexer's map and the checker's cases held in code (research 085, Findings 2); the reader refuses an unknown field, so the runtime learns it in the one rebuild the milestone makes |
| §239 The fingerprint handed as `MESH_FINGERPRINT` at a process's start | one value every runtime reads before anything else, a service and a container alike |
| §240 The delivery record's consumer, held and failed outcomes, end and `record-ended`, the finding `record-open`, node away and wake, switch done once, and tools | a restart must lose and double nothing; a reader sees why an entry stands still; a node asleep must not hold a delivery open; a switch run twice must ask no approved group again |
| §241 A small fix's own record | a small fix is a milestone of one issue (ADR 0493 §3) |
| §242 An Approve given held while its heads stand (issue 1972) | the operator approved those heads; a check of them asked again changes nothing they approved (ADR 0441 §1, ADR 0548 §8) |
| §243 The milestone events on a subject of their own | a waiter on one member's pull request otherwise wakes on every member's |
| §244 The issue tracker's link kinds to a decision and a research effort, and the knowledge base's link from a decision to its milestone | the trace from research to the running build is then links all the way, with no text match (research 085, Findings 7; ADR 0493 §4) |
| §245 The view's state read and asked again by mesh-sdk's `go/views` tools `<module>_first_views` and `<module>_first_view_again`, which gain the revision a part holds and what it waits for; the typed first view and the following of each half in the seats' client packages `meshcatalogclient` and `meshwantsclient`, built on `go/views`; the finding `view-waits` after five minutes; and `node_process_builds` | every part's view and every process's own build is read and steered by a tool; `go/views` already follows an owner's events durably and serves those two tools, so a second follower or a second pair of tools would be two forms of one thing; a view tool typed by the two seats would make mesh-sdk import contracts (ADR 0635 §4), so the typed half sits in the seats' client packages (ADR 0625 §4); a part waiting for the view is raised on the bound the seat list's readers keep |
| §246 The seat list's reader and its two tools wired once by mesh-sdk's runtime | ADR 0636 §7 puts them in the runtime bundle; four hand-wired copies would each learn `acts` apart |
| §247 The trunk's heads from the catalog half, and nothing read of the gate by the checker | ADR 0596 §2; with the hold and turns removed nothing of the gate is left for the checker to read |
| §248 The indexer reads every contracts trunk's definitions at its start, before it judges | a definition merged while it was down is never judged against a list that lacks it (ADR 0636 §1) |
| §249 Another node's running build kept from the node-engine seat's `running` events by a durable consumer, the event carrying the fingerprint and the manifest's digest, the trunk's manifest for a node that said nothing | ADR 0660 §2 reads the manifest of the build each node runs; an event kept by a durable consumer answers while a node is away, where a call would wait on it; one fallback in the one function keeps the two parts agreed (ADR 0660 §3) |
| §250 What the gate and the checker owe the forge said as the event `owed` per repository and pull request, each act safe to repeat at the forge, each outcome said as `owed.ended` and recorded, an unanswered act said again at the gate's start and stalled after 15 minutes | ADR 0596 §1, §2 end the watches of the gate's and the checker's keys; an act safe to repeat cannot be lost between the act and its record, where an id recorded first could; an outcome said and recorded leaves no act unseen |
| §251 A merge made at the forge opens a `forge-made` delivery record | its trace and outcomes are kept like every delivery's, and no later merge waits behind a merge the gate never heard |
| §252 Every delivery part and the gate reach the forge only through the seat `git`'s verbs and events, the act verbs granted by the holder's trusted `callers` to the forge bridge alone, a failed read answered as an error | the delivery parts name no other module (ADR 0656 §1, acceptance test 27); the forge's answers are typed in the seat's contract instead of guessed in each caller; the bridge stays the one part that acts on the forge; an error read as an empty answer would hide a forge down |
| §253 A check that failed because a part of the mesh was not heard says `could not run`, naming the part, releases its claim, and is asked again once per heads and part on that part's `running` event, raising `check-could-not-run` after 60 minutes | the operator sees a mesh fault apart from a failing change, and the check is asked again on the one event that can change its outcome, never on a timer (issue 1373) |
| §254 A module held for its claim raises `module-held` until it is composed | a module left out in silence would look delivered (the mesh's rule that failure is loud) |
| §255 The indexer waits for the seat `git` at most 2 minutes at its start, then judges from its kept definitions and raises `view-waits` after five | longer than a forge's restart, and no start held behind a forge down for good |
| §256 The forge before the forge bridge before the gate, reverted the other way | the verbs are served before they are called, and `owed` is said only to a bridge that hears it |
| §257 The store seat's holder's own new build kept on its build node and fetched by `kept-artifact` when its seat is broken | its own build needs the store it replaces; a step of the delivery replaces the hand bootstrap |
| §258 Every drop and release tool writes the hand-act log | a hand act that changes what the mesh runs or keeps is recorded with who did it and why, as every hand act is (seat `mesh-hand-acts`) |
§259 Each choice is checked by the tests its section names; the review by architectural domain judges them.
### What each step says
§260 Each step says what it did as its own event, the event of the part that did it. A catalog graph revision kept
carries the source revision; an assignment graph revision kept and a difference worked out carry the source revision
and the assignment graph revisions; an entry taken in, built, checked, composed, wanted and running also carries the
entry's fingerprint and its node. The gate's delivery record is built from them. The seat's event comes first where
the parts follow it through a seat (ADR 0656); a holder's own event stays under its own subject, declared in its own
manifest's `streams` and published on `mesh.stream.<module>.event.<event>` (ADR 0363 §1, §2). A seat's event is
declared in its definition and its holder's manifest claims the seat; a seat event's name has no dot, since it is read
as a subject's token, and it is kept on the stream `SEAT_<seat>_<event>`, which the grants part makes with its durable
consumers, keeping it by the age and cap the definition's `keep` names, 90 days unless it names less. **An event a node publishes carries the node in
its subject**, and the grants part grants each node-engine's account only its own node's subjects, so one node cannot
say another runs a build (ADR 0363 §2); the gate's events are granted to the gate's account alone.
| Step | Event | Owner: the seat, then the module | Subject | Declared in |
|---|---|---|---|---|
| a catalog graph revision kept, saying whether it is a trunk revision or a candidate's and its watched branch | `catalog-revision` | `mesh-catalog`, the indexer | `mesh.seat.mesh-catalog.event.catalog-revision`, the watched branch in the subject from G's delivery | the seat's definition in `mesh-delivery-contracts` |
| an assignment graph revision kept | `assignment-revision` | `mesh-wants`, the assigner | `mesh.seat.mesh-wants.event.assignment-revision` | the seat's definition in `mesh-delivery-contracts` (ADR 0641 §2) |
| a difference worked out | `difference` | `mesh-wants`, the assigner | `mesh.seat.mesh-wants.event.difference` | the same |
| an entry taken in | `take-in.taken`, which gains the entries | the indexer's own | `mesh.stream.mesh-delivery-indexer.event.take-in.taken` | the indexer's manifest |
| an entry built | `build.ended` | the builder's own | `mesh.stream.mesh-delivery-builder.event.build.ended` | the builder's manifest |
| an entry checked | `checked` | `mesh-delivery-checker`, the checker | `mesh.seat.mesh-delivery-checker.event.checked` | the seat's definition in `mesh-delivery-contracts` |
| an entry wanted | `wanted` | `mesh-wants`, the assigner | `mesh.seat.mesh-wants.event.wanted` | the seat's definition in `mesh-delivery-contracts` (ADR 0641 §2) |
| an entry composed, and running healthy | `composed`, `running` | `node-engine`, the node-engine on each node | `mesh.seat.node-engine.event.composed.<node>`, `.running.<node>.<module>`, the latter on a stream kept last per subject with no age limit | the seat's definition in `mesh-contracts` |
| a node's capabilities changed | `capabilities-changed` | `node-engine`, the node-engine on that node | `mesh.seat.node-engine.event.capabilities-changed.<node>` | the same |
| a wait held, and ended | `wait-held` (step, acting part, its version, the entry, since when, its bound), `wait-ended` (the same, and how: ran, released) | each holder: `mesh-wants` for the assigner (placing, wanting and composing), `mesh-grants` for the grants part, the builder's own stream | `mesh.seat.<seat>.event.wait-held`, `.wait-ended`; `mesh.stream.mesh-delivery-builder.event.wait-held`, `.wait-ended` | each seat's definition; the builder's manifest |
| a store filling, full, and back below its cap | `filling`, `full`, `room` | `mesh-catalog`, `mesh-wants`, `mesh-delivery` | `mesh.seat.<seat>.event.filling`, `.full`, `.room` | each seat's definition |
| a record pins or frees a revision; a candidate's heads replaced; a record ended | `pinned`, `freed`, `heads-replaced`, `record-ended` | `mesh-delivery`, the gate | `mesh.seat.mesh-delivery.event.<event>` | the seat's definition in `mesh-delivery-contracts` |
| a milestone's ask asked, withdrawn and answered, until G's delivery | `milestone-asked`, `milestone-withdrawn`, `milestone-answered` | `mesh-delivery`, the gate | `mesh.seat.mesh-delivery.event.<event>`, the milestone in the event, on the seat's family stream, apart from each delivery's `transition` | the same |
| a delivery record opened, and a merge named a recovery | `record-opened`, `recovery-merged` | `mesh-delivery`, the gate | `mesh.seat.mesh-delivery.event.<event>` | the same |
| a pipeline, its steps, its waits and a run on request, from G's delivery | `pipeline-started`, `pipeline-ended`, `group-ended`, `run-started`, `step-started`, `step-ended`, `wait-held`, `wait-ended` | `mesh-pipeline-runner`, the runner | `mesh.seat.mesh-pipeline-runner.event.<event>.<module>.<branch>.<commit or group>` | the seat's definition in `mesh-delivery-contracts` |
| a pipeline step ended, from G's delivery | `step-ended` | each build seat, `mesh-wants` | `mesh.seat.<seat>.event.step-ended.<node>`, the node that ran it | each seat's definition |
| a registration changed; a package published, from G's delivery | `registration-changed`; `package-published` | `mesh-catalog`, the indexer; `mesh-builds`, the builder | `mesh.seat.mesh-catalog.event.registration-changed`; `mesh.seat.mesh-builds.event.package-published` | the seats' definitions in `mesh-delivery-contracts` |
§261 The events, their fields, their subjects and which are a seat's are this design's choices: a seat's event where
other parts follow the step through the seat, a holder's own event where only the delivery record hears it, so that
nothing reads another module's bus state (ADR 0596). The builder's waits go on its own stream because the builder
holds no seat of its own; the watchman and the gate follow them there. How it is checked: each seat definition's
conformance fixtures name the subject of every event it gains, and a case of the holder's whole-module test matches an
event it publishes against them, as for every seat (design 26); the gate's whole-module test hears each row through
its durable consumer, with exactly the fields this section gives it; a `running` event published by one node's account
on another node's subject is refused by the bus.
### Their tools
| Line | The assigner | The indexer |
|---|---|---|
| read everything it holds | `assigner_revision` (one assignment graph revision whole, with the revisions of its inputs), `assigner_revisions` (every kept revision with what pins it, and the store's fill against its cap), `assigner_entry` (one entry, with the seat rule that gave each edge), `assigner_difference` (a change's difference), `assigner_waits` (*Order without a person*), `assigner_candidate_graphs` (every candidate's graphs: its heads, its revisions, since when), `assigner_consumer` (its consumer of the gate's events: the last applied, the waiting), `assigner_puts` (each put with its step, since when, held or not, the `running` that ended it, and for a put without hold whether it is past its 30-minute bound; a put older than the trunk's newest build of its module said so) | `indexer_catalog_revision` (one catalog graph revision), `indexer_catalog_revisions` (every kept revision with what pins it, and the store's fill against its cap), `indexer_candidate_graphs` (every candidate's catalog half), `indexer_consumer` (the same as the assigner's) |
| do by hand what it does itself | `assigner_recompute` (compute a revision again from its named inputs and compare it with the one kept) | `indexer_recompute` (compute a catalog graph revision again from its source revision and compare it with the one kept) |
| recover without help | `assigner_drop_candidate_graph` (drop a candidate's graphs whose gate event never came), `assigner_release` (*Order without a person*) | `indexer_drop_candidate_graph` (the same for its half) |
| one seat, many tools | `mesh-wants`: `revision`, `entry`, `difference` | `mesh-catalog`: `revision`, beside `seat-definitions` |
§262 Every drop and release tool writes its act, who did it and why, in the hand-act log of the seat `mesh-hand-acts`.
Beside ADR 0636 §7's two tools in mesh-sdk's runtime bundle, which read a reader's seat list and make it read the
list again, the view is read and steered by the two tools mesh-sdk's `go/views` already serves every part that takes a
first view: `<module>_first_views` gains which view revision the part holds, what it waits for and since when, beside
what it heard and how far it is behind, and `<module>_first_view_again`, granted as ADR 0636 §7's second is, makes it
read the view again now. `go/views` stays seat-neutral, naming no seat and no seat's type (ADR 0635 §4); the typed
first view of each half and the following of its events are the seats' client packages, `meshcatalogclient` for
`mesh-catalog` and `meshwantsclient` for `mesh-wants`, each built on `go/views` (ADR 0625 §4), so no part follows the
view by a follower of its own. A tool
of the node-engine, `node_process_builds`, reads the build each process was started with. The tools answer the four
lines every module's tools answer (design 26), so that every state these parts keep is read and every act they do is
steered by a tool.
§263 **How it is checked.** Unit (assigner): a revision of the assignment graph computed again from its named inputs
equals the one kept; a fixture change with a new fingerprint and an empty difference is refused, naming its module;
one revision of about 120 modules on four nodes stays under the bus's value limit; of the view the assigner reads only
the catalog half. Unit (indexer): a catalog graph revision holds one entry per module of its source revision. Whole
(indexer): a source revision taken in gives one catalog graph revision kept as objects in an `objects` section in the
old form within the cap its manifest declares; the manifest validator refuses the indexer's or the assigner's manifest
that declares them in the new form. Whole (indexer, assigner): a revision a delivery record pins is not removed; one
two records pin stays until both free it; one neither pinned nor current is removed; a `freed` said while the part
was stopped is applied after its restart; a candidate's graphs are dropped on `heads-replaced`; a store past 80 % says
`filling` and raises `store-filling`, and a full one says `full` and refuses its next revision naming itself; for a
candidate's heads both `revision` verbs answer, the assigner's half named by its own revision and naming its inputs; a
placement change with no merge, and a `capabilities-changed`, each give a new assignment half on the same catalog half,
whose difference moves only the module concerned, and a part takes that pair as its view; after its first view a part
acts on a seat event without asking a verb again; a running part does not act on a candidate's view; a pull request
that adds a seat and a module using it is validated by the indexer's candidate pass and checked by the checker with
the same definitions; the tools of the table answer and the drop tools write the hand-act log. Whole (assigner): a
pull request's graph leaves every node's want unchanged; a placement-only revision moves the want of the module it
places; the builder and the grants part read their entries only through the seat `mesh-wants`'s verbs and events, and
the node-engine reads its want and the catalog half and never the assignment half. Whole (node-engine): a part started
while its node's want differs from what runs reports the build it was started with. Live: a search of the readers'
code finds none of the reads the revision view replaces, each by name: the indexer's read of the checker's previews,
the checker's watch of the indexer's `candidates.<try id>`, the forge bridge's read of the gate's `merges` and `owed`,
the checker's read of the gate's `source` and `turns`, the forge module's watch of the checker's `owed` (its `mesh/check`
status, now the checker's `owed` event performed by the forge bridge) and its read of the gate's `source`, the grants
part's read of the assigner's state `wants`, any module's read of the indexer's `modules` (its tool
`indexer_read_modules` in an `invokes`, its view in a `consumes`), the
checker's validator request with no seat definitions, a seat list compiled into the node-engine, the checker, the
builder or the grants part, a seat list's reader wired by a part itself, and a call of the forge module's own tools by
a delivery part or the gate; an old node-engine that wakes takes its new build in a form it reads.
@@ -0,0 +1,406 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0663"
- "ADR 0662"
- "ADR 0661"
- "ADR 0656"
- "ADR 0648"
- "ADR 0646"
- "ADR 0644"
- "ADR 0639"
- "ADR 0636"
- "ADR 0633"
- "ADR 0619"
- "ADR 0614"
- "ADR 0603"
- "ADR 0596"
- "ADR 0595"
- "ADR 0584"
- "ADR 0564"
- "ADR 0551"
- "ADR 0548"
- "ADR 0539"
- "ADR 0534"
- "ADR 0498"
- "ADR 0448"
- "ADR 0404"
- "ADR 0391"
- "ADR 0363"
- "ADR 0235"
- "ADR 0233"
---
# 47, milestone G: the acting parts, the removed hold, the check on the change's own acting parts, after the merge, taken in, and kept data on the bus
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".
## Order without a person: the acting parts
### An acting part's new version acts first, a wait with a bound
*ADR 0656, which supersedes ADR 0646; ADR 0603 §1 to §4, §8, §11, ADR 0614 §1, §3, §6, ADR 0633
§1, ADR 0539 §7, ADR 0584 §1, §2, ADR 0564 §2.*
§264 From research 085's milestone. A seat's definition may carry a rule, the step its holder acts in and the modules it
acts on, and a holder of such a seat is an **acting part** (ADR 0656): the indexer taking in and validating, the
checker checking, the build modules building, the node-engine composing, the grants part granting, the assigner
placing. *What ADR 0656 leaves to this design*, above, gives the rule's fields. Adding one needs its manifest and
its seat's rule, and no code elsewhere: the indexer's map from language to build module and the checker's own module
names go, since every new acting part needed code changes elsewhere (research 085, Findings 2).
- §265 **The rule becomes edges between versions.** In the assignment graph the assigner gives an edge from the acting
part's version to each version its rule names, and no part names another module in its code to order by (ADR 0656).
- §266 **Within a revision the new version acts first, a wait with a bound.** The entries an acting part acts on wait for
its new version in its own step, with the bound its rule's `bound` gives (ADR 0656), the part's own as ADR 0603 §8
has every wait. A bound's clock starts when the new version's build is done, as the builder's `build.ended` of that version says, its
time carried in `wait-held` as `clock-from`, not when the wait begins, so a slow
build of the acting part is the build's own wait and not counted twice. When a rule names none, the step's default holds: 60 minutes for building (a build module's own
build and its spread to a majority of build nodes), 30 for composing (a node-engine's apply on one node), 10 for granting and placing (a write of one part), and the first tier's 20 for checking; from G's delivery 30 for the runner's `pipeline`, whose wait ends when the new runner runs healthy, one module's apply on one node as composing's is (the design's choice under ADR 0666 §19, 2026-11-10). A module changed with
its build module is no longer built with the running recipe and again later, and a build module's edge, a trigger no
want waited on, becomes this wait (ADR 0656, which changes ADR 0603 §1, §3 and ADR 0614 §1, §6).
- §267 **A build module's wait ends on the agreed toolchain.** For building, an entry waits until the agreed toolchain of
its language (ADR 0584 §1) names the new build module, never merely until the new build module runs on one node: a
fingerprint names the agreed toolchain (ADR 0603 §3), so only then is the module built once, by the new one. A build
node still on the old build module builds nothing of that language until it reports the agreed one (ADR 0584 §2).
Every other step's wait ends when the acting part's new version runs healthy, as the node-engine seat's `running`
event says.
- §268 **Composing waits per node, by the assigner.** A node's wants wait only for that node's own node-engine, never for
another node's, so a node away holds only itself (ADR 0603 §7). The want gains no field for it: the assigner, which writes
the wants, holds a node's other wants until the node-engine seat's `running.<node>.<module>` says the new node-engine version
runs on that node, and writes that node-engine's own want first. A node-engine of before milestone G thus gets its
own want first and every other want once it runs the new one, so an old node-engine never composes a module it
cannot read. The clock of that node's bound starts when the node is present and its node-engine's want is written,
so a node asleep raises nothing while away.
- §269 **A failed or late new version holds its modules, and a condition names it.** When the new version fails, or its
wait passes its bound, the entries waiting for it stay held, the modules stay on their running builds, and the
watchman writes the finding `acting-part-late` naming the acting part, its step and since when, and the condition
keeper raises it (ADR 0564 §2). The holder says `wait-held` at the start of the wait, carrying the acting part's new
version, its bound and `clock-from`, and the watchman times it by that bound alone, raising no `pipeline-stands` for
a step held in it: a holder stopped in the middle of a wait still has its wait timed, so a stopped holder hides
nothing. At its own start the watchman reads the open waits from each holder's read tool, as it seeds
`take-in-waits` from `indexer_pending`, so a wait said while it was down is timed too. An entry moves on only once the new
version runs, or its holder's release tool releases it; either way the holder says `wait-ended`, and the finding and
the condition clear. The operator sees each stuck acting part as one condition. Going on with the running version
instead would build or check a change with a part it replaced, the cause of the failures of research 085, Findings
4; stopping the whole delivery would hold modules the failed part does not act on (the operator, 2026-11-06 17:0x).
- §270 **A wait holds only within its step, and skips the holder's own inputs** (ADR 0656). An entry waits only for its
own step's holders, so two acting parts that act on each other in different steps (the indexer taking in the
checker, the checker checking the indexer) form no cycle. Within a step a holder's own change is its first act, and
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.** 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
then is the new one wanted. When the running holder is what is broken (the seat refuses or does not answer a store,
as in the hand bootstrap of 2026-11-07 to 11-09), the build module keeps that one build on its own node among its
kept builds (*The bootstrap delivery*: `build_go_kept`), says in its step key that it is kept on that node, and the
builder's done key names it so with its digest, which the want carries as it carries every artifact's digest; the
node-engine composing the holder fetches it through the build seat's verb `kept-artifact` of that node, and the new
holder, once it runs, takes the store of every build after it.
- The verb sits in the build seats' definitions (`node-build-go`, `node-build-node`, `node-build-image`, in
`mesh-delivery-contracts`), and its callers are the node-engines alone: the definition names them, and the grants
part grants it to no other account.
- It answers only for the module the seat list names as the holder of `oci-artifact-registry` at the running
revision, and refuses every other, naming the module, so a manifest that only claims the seat, and a store outage,
never turn into delivering from build nodes.
- The bytes go as the reply's object in the build module's sent store `<module>_sent-objects` (*Any size*), never in
a value, since an artifact is over the bus's value limit; the build module is no foundation part, so it keeps no
old `objects` section, its kept data being in ADR 0595's templates (ADR 0666 §4), and the build itself stays among
its kept builds on its node. The sent store's cap is the objects template's; a holder image larger than its free
room refuses `kept-artifact` loudly, naming the store, as a full store does. The node-engine checks the bytes it
fetched against the digest its want names, never a builder's key (ADR 0596), and applies nothing that differs.
- A kept build that a step key names as kept is never swept until the module's `running` event names a later
fingerprint, which the build module hears through a durable consumer of the node-engine seat's events. Each build
seat reads and drops its kept builds by its own tools: `build_go_kept`, `build_node_kept` and `build_image_kept`,
with their `_drop`. The disk cost is one image of the store's holder on one build node (a guess: tens of MiB).
- While the keeping node is away, or its sweep removed the build, the want waits, and the node-engine says in its
have that it waits for that node or for a build no longer kept. A want that waits so for 60 minutes, building's
default bound, is the watchman's finding `kept-build-waits`, naming the module, the node and the build node; the
operator or the healers recover it by `builder_build_again`, which builds and keeps it again; the finding clears when
the node's have runs the want.
- A node-engine of before G does not know `kept-artifact`: the holder's want on its node waits for its new
node-engine (ADR 0667 §9). A new node-engine is itself fetched from the store; while the store is broken it waits,
so on a node that has neither the store's holder's new build nor G's node-engine, the want waits and
`kept-build-waits` names the node-engine's version as the cause, for the operator's hand at the forge as ADR 0667
§11 has it.
This is the one artifact the mesh delivers from a build node instead of the store, and only for the holder of
`oci-artifact-registry`; the design's choice under ADR 0656 §3 (a wait skips what the holder's own new version needs) and ADR
0526 §2, whose build by a session's call it makes a step of the delivery instead of a hand act; the digest, the
holder check, the sweep, the bound and the old node-engine are the design's choices of 2026-11-10 (round 1). How it
is checked: test 60, and whole (build module, node-engine): a module claiming the seat but not its holder in the
seat list is refused; bytes differing from the want's digest are not applied; a want waiting 60 minutes raises
`kept-build-waits` and `builder_build_again` ends it.
- §272 **The check refuses a cycle among the waits within one step** (ADR 0656, which changes ADR 0603 §2's refusal of a
cycle), counted beside the edges ADR 0614 §3 and §6 list; a cycle through an acting part's edges across two steps, or
through a holder's own inputs, is none.
- §273 **The indexer's own steps have no wait, and a newer indexer is reached.** Taking in and validating stay as *Taken in
by the indexer it names* has them: a revision is taken in only by the indexer it names, which judges its own change
at its start, and no build waits on which indexer judged it (ADR 0603 §4, as ADR 0633 §1 changes it). So that the
named indexer can start at all, the running indexer makes, for a merged revision naming a newer indexer, a catalog
graph revision that is the previous one with only the indexer's entry replaced by the one its manifest at the merge
gives, and nothing else of the revision, so no other want moves before the named indexer takes the revision in; the assigner wants it and the grants part grants it like any module, and once it runs it takes the revision
in whole. Without that, a revision waiting for its indexer would wait for good.
- §274 **A rule with no holder is no silent pass.** An entry a rule matches with no holder for its step is held by the
assigner, naming the missing holder as the hold's reason, and the check fails naming it.
- §275 **An acting rule grants nothing.** A holder's bus grants come from the grants part as today (ADR 0539 §7). An
acting rule changes what a part acts on across every module it matches, so the operator's Approve ask of a change
names any acting rule it adds, and any it widens: a `step`, an `on` or a longer `bound` that now matches more
modules or holds them longer is named like an added rule. From G's delivery there is no Approve ask per merge, and
the pipeline's end names it in `widens` instead, refusing nothing (ADR 0666 §23).
- §276 **Each step's wait is its own part's, read and released by its own tools.** The part that does a step holds its
wait, so each wait has one owner that already holds that step's state, and says `wait-held` and `wait-ended` (*What
each step says*) with the step, the acting part, its version, the entry and since when:
| Step | Holder | Reads its waits | Releases one |
|---|---|---|---|
| building | the builder, on its build key | `builder_waits` | `builder_release` (build the entry with the running build module), beside `builder_build_again` (ADR 0603 §11) |
| placing and wanting | the assigner, on its hold | `assigner_waits` | `assigner_release` (ADR 0603 §11) |
| granting | the grants part | `grants_waits` | `grants_release` |
| composing | the assigner, holding a node's other wants until its new node-engine runs there (*Composing waits per node*) | `assigner_waits` | `assigner_release` |
| checking | the check's first tier | `checker_first_tier` | its ask-again (*The check on the change's own acting parts*) |
| delivery of a dependency (kinds 2, 4 and 6), from G's delivery | the assigner, holding a dependent's new version until `version-delivered` of its dependency's (§376) | `assigner_waits` | `assigner_release` (want it now), and `assigner_delivered_again` |
| pipeline, from G's delivery | the runner, holding the pipelines its new version is to run until it runs healthy | `runner_waits` | `runner_release` (run them with the running runner) |
Each release writes the hand-act log of the seat `mesh-hand-acts`, with who released and why.
- §277 **How it is checked.** Whole (assigner): a fixture seat declaring a rule, with a fixture holder, gets its edges with
no code change elsewhere. Whole (assigner, builder, three build nodes): a group changing the shared contracts and the
build module that builds them, the new build module updated on the first build node first, builds no module with the
new build module while one node of three runs it, builds the contracts with the running build module, and once the
second node reports it and the agreed toolchain names it, builds each other module once, by the new one; the third
node builds nothing of the language until it reports the agreed version. Whole (assigner, builder, grants part,
watchman): a fixture acting part whose new version fails, and one whose wait passes its bound, each leave their
modules on their running builds, say `wait-held` at the wait's start with its bound, and raise `acting-part-late`
naming it; a holder stopped in the middle of a wait still raises it, and a watchman restarted mid-wait reads the
wait from the holder's read tool and times it; a bound counts from the new version's build done; each holder's release tool
moves a held entry on, says `wait-ended` and writes the hand-act log, and the condition clears; a wait ends on its
own when the new version runs; each holder's read tool lists its waits with step, acting part, version and since
when; a rule's `bound` overrides the step's default; a change to the runner holds its pipelines until the new runner
runs, raises `acting-part-late` past 30 minutes and no `pipeline-stands`, and `runner_release` moves them on; a
`wait-held` carries `clock-from` from the builder's `build.ended`. Whole (assigner, node-engine): during a node-engine change with
one node away, the present nodes take their new node-engine and then their other wants, nothing waits on the node
away and no condition is raised for it; a node asleep since before milestone G wakes, takes its new node-engine
first, then the rest. Whole (indexer, assigner): a merged revision naming a newer indexer gives a catalog graph
revision that is the previous one with only the indexer's entry replaced, and no other want moves; the new indexer is wanted and granted, starts,
and takes the revision in, with no build waiting on which indexer judged it. Whole (assigner): a module with no
holder for its step is held by name, and the check of a change bringing it fails naming the missing holder. Unit
(graph): an acting part acting on itself, two acting parts acting on each other in different steps, and a cycle
through a build module's edge each pass the cycle check, each new version first in its own step; a cycle among
waits within one step is refused. Unit (assigner): the Approve ask of a change that adds an acting rule, and of one
that widens one, names the rule. The code review reads that no delivery part's code names
another module (§7).
## Nothing merges into a repository under check: removed
§278 **Removed at research 085's milestone.** The hold, the merge turns, their stalled line, the gate's tools for them and
the setting `check-gates-merge` go in milestone G, the gate's and the forge module's alike (the operator, on Telegram,
2026-11-09 01:11 UTC, choosing "Remove them in G"; the forge's merge then judges no check, *The seat `git`*). The setting has been false since 2026-10-25, so the gate wrote no turn and held no merge in that time, and the
operator's word for G is that no check gates delivery (1921's note of 2026-11-07). From G's delivery no merge waits on a check, and none on an Approve either (ADR 0666 §10): the checker is gone, and the
pipeline runs on the landed commit (*Pipelines per module*). **Its record is accepted:** ADR 0661 ends ADR 0548 §2
to §4, §9 and §10 and ADR 0551 and changes ADR 0534 §4's judgement of `mesh/check` by the merge; until G's delivery the
rule below holds as those records have it. ADR 0662 is withdrawn; G builds ADR 0661 §1, §2 and §6, and ADR 0666 §10
changes its §3 to §5 (the merge on the Approve alone), which G therefore builds for no merge. Beside it, ADR 0663 (accepted: the indexer answers `revision` in its own runtime to
exactly the parts that invoke it, the tool runner among them) holds the tool
runner's first view of the catalog half (*Every other reader of which modules exist*). How it is checked: in the gate's whole-module test an approved merge into a
repository under a running check is handed at once, and the gate's manifest declares no `turns` and no setting
`check-gates-merge`; in the checker's, a check claims at once with no gate state granted to read.
## Checks: the check on the change's own acting parts
### The check on the change's own acting parts
*ADR 0656, which supersedes ADR 0648, ADR 0644; ADR 0448 §2, §4, ADR 0391 §1, ADR 0603 §10, ADR 0614 §7, ADR 0633
§6, ADR 0636 §4, ADR 0639 §5, ADR 0498 §1.*
§279 From research 085's milestone. ADR 0603 §10 and ADR 0614 §7 let a check pass while the part that would build or
check the change never ran: 7 of 24 failures since 2026-11-01 (research 085, Findings 4). The acting parts a change
alters, and the checking program, the rules every check applies, are built from the change's heads and check it; the
running versions never check in their place, and a check fails, naming the part, when one cannot be built or run
(ADR 0656).
- §280 **Two tiers on the change's assignment graph.** The first tier builds, at the change's heads, only the acting parts
whose fingerprint in the change's assignment graph differs from the running one and which are to act on a module
the check acts on; a change to an ordinary module asks no first-tier build, since building every acting part for
every check would make an ordinary change pay for builds that change nothing. The second tier runs the check's
levels with those programs and with the revision view of the change's heads (ADR 0656). This keeps ADR 0603 §10's
building of changed build modules first and widens it to every acting part.
- §281 **The judging rules are a checking program built from the change.** Every check builds its checking program from
the change's heads and runs it with the revision view as its input; when it cannot be built or run, the check
fails naming it, and the running checker's own rules never judge in its place (ADR 0656). It is built once per
fingerprint and reused (ADR 0498 §1): checks whose heads leave the checking program unchanged ask one build between
them.
- §282 **Every program built from a change runs only in the build seat's sandbox.** The acting parts the first tier builds
and the checking program run in the run step, with no network (ADR 0448 §2) and no bus account, the logins staying
outside (ADR 0448 §4), through the seat `node-build-go`'s ask `run` (*What ADR 0656 leaves to this design*), as
the validator, the composer and the Go build module already run; a change not yet merged is not trusted. The running
checker alone asks, reads the program's verdict, records it and publishes it.
- §283 **A first tier that cannot build an acting part fails the check, naming it.** The verdict never passes on the
running version's word: a module whose changed build module the check cannot run is no longer composed from its
manifest (ADR 0656, which changes ADR 0603 §10 and ADR 0614 §7), and ADR 0614 §7's reason, a changed build module
invoking a verb no running holder serves, is one reason the first tier cannot run an acting part: the check fails
naming the build module and the verb. An acting part not built within the first tier's bound of 20 minutes is one
it cannot build, and the verdict says `waiting for a build node` or `did not build`.
- §284 **A change to the checking program is named, by a part it did not build.** Every change of the checking program's
fingerprint is named in the verdict and the Approve ask, whatever changed: a helper a rule calls changes what the rule
judges as much as the rule does, and only the fingerprint covers every input. The rule-name comparison is its detail:
each checking program declares its rules by name in its source. The running checker, outside the sandbox, reads the rule names from the
trunk's sources and from the heads' and compares them; every rule removed, added or altered (its source changed) is
named in the verdict and in the operator's Approve ask. The program built from the change is never asked which rules
it has, so a program that drops a rule and hides it is still named.
- §285 **ADR 0633 §6 stays, beside the change's own validator.** Whether the running parts can take the change in is
still checked with the trunk's manifest validator, built from the trunk (ADR 0633 §6); the change's own validator,
built in the first tier, judges the change's manifests besides (ADR 0656). A change fails if either refuses it.
- §286 **The asking and recording are covered by the checker's whole-module test**, which starts the changed checker on a
real bus and asks one check of a fixture group end to end (ADR 0391 §1).
- §287 **One seat list for both readers of a change's heads.** Inside every check, a lone pull request's as a group of one,
the checking program built from the heads makes one seat list from them, and the checker hands it to the manifest
validator and the composer (ADR 0644, ADR 0636 §4 with its insight of 2026-11-06); the running checker reads no
definition of the heads itself (*The bootstrap delivery, and the one milestone after it*).
- §288 **Its tools.** The checker's read of a check names the checking program's build that judged it, its fingerprint and
the heads it was built from, and the rules named changed; `checker_first_tier` lists a check's first-tier builds,
each with its acting part, its fingerprint and its outcome (`built`, `waiting for a build node`, `did not build`),
and asks one of them again. Asking a whole check again stays the checker's existing ask-again.
- §289 **What it costs.** A check of a change to an acting part costs one build per changed acting part, within the
checker's step bound (ADR 0639 §5); an ordinary change costs nothing more; a checking program costs one build per
fingerprint, shared by every check that uses it.
- §290 **How it is checked.** In the checker's whole-module integration test: a fixture change whose acting part does not
build, and one whose checking program does not run, each fail their check, naming the part; a checking program that
builds but does not run fails naming it; a first-tier ask no build node takes within the bound says `waiting for a
build node`, and one that fails says `did not build`; a change that breaks a rule of the checking program fails its
own check; a change that removes a rule passes, and its verdict and the ask name the removal, also when the
program built from the change hides it; a change to a helper a rule calls, with no rule renamed, names the checking
program's new fingerprint in the verdict and the ask; a program that rewrites its own binary while it runs changes
no kept build; a fixture checking program that tries to reach the bus or the network
fails; a `run` ask published from an account other than the checker's is refused by the bus, and the checker's
account cannot read another account's step key; a fixture build module invoking a verb no running holder serves
fails naming it and the verb; a change to an ordinary module asks no first-tier build; two ordinary checks with the
same checking program ask one build of it; a group that changes the assigner's placing is checked with its own
assigner; a fixture indexer change whose manifest the trunk's validator refuses fails its check, even when its own
validator passes it; a fixture group whose heads add a seat has that seat in the list both the validator and the
composer receive; the check's read names the checking program's build, heads and changed rules; `checker_first_tier`
lists a check's first-tier builds and asks one again; the changed checker asks one check of a fixture group end to
end. Live: the registry group passes its one check with its own checker, validator and composer.
## After the merge, from milestone G
- §291 **From milestone G's delivery, per watched branch.** The gate writes one source revision per watched branch,
`source.<branch>`, as a commit or a group lands there; the pipeline runs on each revision the indexer takes in, and
the wants follow a revision whose `required` steps passed, on the branch each placement chose (ADR 0666 §6, §9).
- §292 **From research 085's milestone, the wants follow the trunk's assignment graph.** A merge taken in makes a
catalog graph revision and then an assignment graph revision; the assigner writes each want from that revision,
its rules unchanged (ADR 0656), and each acting part's new version acts first in its own step (ADR 0656; *Two
graphs, one source of truth*, above).
## Taken in by the indexer it names, from milestone G's delivery
- §293 **From milestone G's delivery** (ADR 0666 §6, §13, which change ADR 0633 §1 and §3 to §6): before any take-in, on every
watched branch, the running indexer validates the revision with the manifest validator built from its heads, in the
sandbox, and a revision it refuses stays pending, as the bullets above have it, with the way back a revert; a pending
revision holds only its own watched branch; a group's member reaches it by landing, and the group ends when the gate's
group verb says every member has; `bridge_group_end_again` reads that verb; and the check's refusal below goes with the
check (*Watched branches and the catalog graph per branch*, *Groups*).
## A backup before the apply: kept data on the bus
### A module's kept data on the bus, backed up and restored with its delivery
*ADR 0404, ADR 0235, ADR 0233, ADR 0539 §16, ADR 0595, ADR 0619 §2, ADR 0633 §1, §5, ADR 0363 §2; from research
085's milestone, this design's choice under ADR 0656.*
§294 **ADR 0404's backup does not cover a module's kept data on the bus.** It backs up the items a moved module keeps on
its own node (ADR 0404 §2, ADR 0233), and the bus's streams are dumped whole by the bus module on its own rhythm
(ADR 0235), not per module and not before a delivery. A key-value state, a stream or an object store a module keeps on
the bus (ADR 0595, ADR 0619 §2), and its durable consumers' positions on the streams it reads, therefore have no
restore point taken before its module moves. That is a gap of the delivery system, and milestone G builds it, because
a revert of milestone G is the first change that needs it. It is built as the delivery system's capability for every
later change of a kept data's form, not as a case of this milestone.
- §295 **Its form is declared.** Each kept data a manifest declares names a `form`, a number its holder raises whenever it
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
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
(ADR 0235 §5). A restore point is keyed by the module, the kept data, its form and the fingerprint it was taken
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
kept data's `form`, the node agent applying it asks that seat's verb `restore` 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
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;
- at a revert, a node's node-engine goes back last on that node, after every module it restores there has said
`restored` and runs, since the node-engine of before may not know how to restore;
- the reverted assigner and gate are wanted only once the reverted indexer has taken in the trunk's head, as its
`take-in.taken` naming the current source revision says (ADR 0633 §1), since the assigner of before computes the
wants from the indexer's `modules` and would otherwise write the fingerprints of the restore point. Until then the
running assigner writes no want, and every module keeps the fingerprint it runs.
- §299 **The restored part catches up forward, with the code it already has.** Read on main, mesh-delivery at 2d201f78
and mesh-catalog at 1d07778c:
- **The indexer** (mesh-delivery, `modules/mesh-delivery-indexer`): its durable consumer `<node>_<module>` on the stream
`forge` redelivers every message since the restored position, and `handle` takes each
merge through `Indexer.Arrive` into `pending` and then into `modules` (`applyMerged`); `applySource` takes the
gate's `source.main` in, and `ApplySource` leaves a revision older than the latest it has seen. **The stream
`forge` keeps 7 days**: the forge bridge's manifest declares no age, and a stream's default is 7 days, at most 30
(mesh-sdk `go/catalogue` `StreamDefaultDays`, `StreamMostDays`), so it does not cover the records' 90 days. A
merge older than the stream's first message is never redelivered, and the code then leaves that repository's
modules at the restore point's fingerprints. So the restore runs one forward step: once the reverted indexer
runs, the node agent that restored it calls its tool `indexer_take_in` with each repository the gate's source names
(`NewTakeInRepository`, which takes in the head the source names for a repository). The catch-up tool is declared
by the newer build's manifest, beside the kept data whose form it changes, and recorded in the restore point when
the backup is taken; the node agent takes it from the restore point, never from the manifest being applied, since
at a revert that is the manifest from before the change, which names none. So the delivery system names no
module's tool.
- **The assigner** (mesh-delivery, `modules/mesh-delivery-assigner`): it keeps nothing restored. Its pass
(`passer.wants`) writes, from what the world holds now, each node's want by the six rules from the indexer's
newest fingerprint at the trunk, and only where the value differs; so once the indexer has caught up it writes
the trunk's wants, and nothing older.
- **The gate** (mesh-catalog, `modules/mesh-delivery-gate`): `Holder.Load` reads the restored deliveries and groups
back; `readOnStart` then reads what happened while it was away: `wantForge` reads the forge's open pull requests
at the next look, `hearAsks` reads the teller's answer to every ask it still holds as pending, and
`SourceCatchUp` brings the newest merge of each repository into `source.main`, never moving a repository back. An
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
`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
the answer writes the node's want again naming it (ADR 0539 §16).
- §301 **What the operator is told.** A revert across a change of a kept data's form puts that kept data back to the
moment before the change moved it: the deliveries made since stay delivered, but what the part wrote there since
(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
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`
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
removed only by `discard-aside`, which writes the hand-act log; a revert of a group holding the node-engine and a
module whose form changes restores the module before the node-engine goes back; a revert whose restore point is
missing is held, naming it, and moves on only on the operator's answer. The catch-up is proved with the real builds
of the indexer, the assigner and the gate from before milestone G, never fixtures (acceptance test 36).
@@ -0,0 +1,207 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-10
decisions:
- "ADR 0667"
- "ADR 0666"
- "ADR 0660"
- "ADR 0659"
- "ADR 0656"
- "ADR 0625"
- "ADR 0614"
- "ADR 0603"
- "ADR 0596"
- "ADR 0551"
- "ADR 0548"
- "ADR 0534"
- "ADR 0493"
- "ADR 0443"
- "ADR 0441"
- "ADR 0398"
---
# 47, milestone G: the verbs G adds and what retires
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
## The verbs milestone G adds
§304 From research 085's milestone the seat gains:
| Verb or event | Answers or does |
|---|---|
| `groups` with a group | that group's members, which the forge bridge reads in place of the gate's `merges` state (ADR 0656), and the acts the gate said it owes the forge for it, each with its id and its recorded outcome (*What the gate owes the forge*) |
| `owed` | an act the gate owes the forge (a merge, a status, the delivery's view, a comment, a line on the commit's note), with its pull request, head and id, on `mesh.seat.mesh-delivery.event.owed.<repository>.<number>`, said only from the gate's account and heard by the forge bridge through a durable consumer (*What the gate owes the forge*) |
| `milestone-asked`, `milestone-withdrawn`, `milestone-answered` | until G's delivery: the one Approve ask per milestone (ADR 0493 §9 as ADR 0659 §1 changes it, issue 1783), held until it is answered, or withdrawn by a new head and asked again (ADR 0441 §1); said on `mesh.seat.mesh-delivery.event.<event>`, the milestone in the event, apart from each delivery's `transition`, which stays as it is, so a waiter on one member's pull request wakes only on that member's `transition`. G's delivery ends the ask and its three events with the Approve per merge (ADR 0666 §10) |
| `record-opened` | a delivery record opened, which the watchman times for `record-open` |
| `pinned`, `freed`, `heads-replaced` | the events by which the indexer and the assigner keep or drop graph revisions, said only from the gate's account (*The delivery record*) |
| `record-ended`, `filling`, `full`, `room` | a delivery record ended; the ledger past 80 % of its cap; the ledger full; the ledger back below its cap (*The delivery record*) |
§305 From milestone G's delivery the seat's `table`, `recheck` and `close` go with the merge states (*The state table*),
the milestone ask and its three events with the Approve per merge, and it gains (ADR 0666 §6, §26):
| Verb or event | Answers or does |
|---|---|
| `source` with a watched branch | that branch's source revision; the indexer and the node-engine's catch-up read it, never the gate's state |
| `source-moved` | a new source revision of a watched branch |
| `recovery` | marks a merge its author names a recovery (the commit and why), its record marked `recovery` with the caller the runtime proves |
| `record-opened`, `recovery-merged` | a delivery record opened; a merge named a recovery, which the teller passes on to every channel |
§306 **The seat `mesh-pipeline-runner`**, from milestone G's delivery, defined in `mesh-delivery-contracts` with the callers
of each verb (ADR 0666 §7, §20, §25):
| Verb | Answers or does | Callers |
|---|---|---|
| `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) |
§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
watched branch in its subject. **The build seats** gain the verb
`steps` and the accepted kind `step`, and **the seat `mesh-wants`** the kind `step`, each saying `step-ended`. **The
seat `mesh-builds`**, new and held once for the mesh, says `package-published` and `toolchain-agreed` and serves
`reserved` (*A publish rebuilds*, §369, §376). From G's answers (G-7), `mesh-catalog` also gains the verbs `versions`
and `outdated` and the events `version-released` and `versions-filled`, `mesh-wants` the event `version-delivered`, and
the seat `git` the verb `tag` (§349, §372, §375, §376, §370).
§308 **The seats `mesh-catalog` and `mesh-wants`** gain, from research 085's milestone, `revision` on each (the two halves
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
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 it (ADR 0660 §2; *Who holds a seat is applied by one rule*). No verb is added.
§310 **The seat `git`.** The seat `git` (design 26, *The git seat*), of the Delivery domain, defined in
`mesh-delivery-contracts`, holds no verb today, and the delivery parts and the gate reach the forge by naming the forge
module's own tools and events and guessing the shape of their answers (on 2026-11-09 one guess, an answer wrapped in a
`result` field, made 316 conditions stand, issue 1966). From research 085's milestone the seat's definition carries the
verbs and events below, their inputs and answers typed in its contract, served and said by the module that claims the
seat; every delivery part and the gate call and hear the seat, never the forge module by name (acceptance test 27).
They are this design's choice under ADR 0596 §1, §2 (a module's events are subscribed to and its data asked through
its seat verbs) and ADR 0625 §1 (a seat's contract is generated from its definition). The table is built from what the
parts' manifests declare in `invokes` and `consumes` and what their code calls, read on main on 2026-11-09 (mesh-delivery
042b8d1f, mesh-catalog's gate); a declaration nothing calls (the checker's `gitea_commits`, `gitea_compare`,
`gitea_list_branches`) gets no verb and is dropped from its manifest:
| Verb | Answers or does | Called by (today's call) |
|---|---|---|
| `pull` | one pull request, by its repository and number or by its merge commit: number, base, head branch, head, merge commit, author, state, and `merging` while the forge merges it; an unknown one answers the error kind `not-found` | the forge bridge (a merge the gate did not make; `gitea_api`), the gate (a stalled line's author) |
| `branch` | from milestone G's delivery: a branch's head, and whether a protection rule refuses a direct push and admits only merges | the indexer (`register`, `watch`), the forge bridge (a watched branch's head) |
| `open-pulls` | every open pull request of every repository, its head and base, the statuses its base requires and those its head has | the forge bridge at its start and each reconnect (ADR 0443 §5), the indexer, the gate (`gitea_open_pulls`) |
| `tags` | a repository's tags, paged, the page's bound said when more exist | the forge bridge, for the builder's floor (`gitea_api`) |
| `trees` | the trees of one commit | the forge bridge, the checker, the indexer (`gitea_commit_trees`) |
| `file` | one file at one commit | the forge bridge, the checker, the indexer (`gitea_get_file`) |
| `contains` | whether one commit contains another | the indexer, the gate (`gitea_contains`) |
| `statuses` | the statuses a commit carries | the gate (`gitea_commit_statuses`) |
| `status`, `comment`, `view`, `note` | a commit status, a comment, the delivery's one view comment, a line on the commit's note under `refs/notes/mesh-plan`; a comment and a note line carry the act's id and an id already held is skipped | the forge bridge alone, performing the gate's and the checker's `owed` (`gitea_commit_status`, `gitea_add_comment`, `gitea_delivery_view`, `gitea_note_append`) |
| `merge-pull` | until G's delivery: merges a pull request at the head the operator's Approve names; refuses a head that moved, a branch no rule guards, a head the Approve does not name and a merge git refuses; judges no check (below). From G's delivery: merges with the forge tools' account (ADR 0398 §2) at the head the caller names, judging that it is still the pull request's head and that the branch's protection refuses a direct push, the protection read again on every `branch-moved`, and refusing a head when any run at its build-input fingerprint failed a `required` step, judged from its own state `failed-steps` heard from the runner's `step-ended`, never by a call; a head with no run yet or a passed one merges, it never waits for a run and never asks an Approve (ADR 0666 §11). A pull request already merged at that head is answered performed | until G's delivery the forge bridge alone (`gitea_merge_pull_request`); from it any session the tool runner proves, and the operator |
| Event of the seat | Says | Heard by (today's event) |
|---|---|---|
| `pull.updated`, `pull.merged`, `pull.closed` | a pull request's new head, its merge with the merge commit, its close | the forge bridge, the gate (`gitea.pull.*`) |
| `forge.pull`, `forge.status`, `forge.delete` | the forge's own webhook of a pull request, of a commit status, of a deleted branch | the gate (`forge.pull`, `forge.status`), the forge bridge (`forge.delete`) (`gitea.forge.*`) |
| `repo.writable` | a repository the forge can write again | the gate (`gitea.repo.writable`) |
| `branch-moved` | from milestone G's delivery: a branch's new head, by a merge or an admitted push, from the forge's push webhook | the forge bridge |
§311 Each is said on `mesh.seat.git.event.<event>`, declared in the seat's definition, and heard through the hearer's
`consumes` of the seat.
- §312 **A failed read answers an error, never an empty answer.** When the forge does not answer, refuses or answers what
the contract cannot type, the verb answers an error naming why; an empty list or an absent file is answered only
when the forge said so. A caller never reads an error as "none". How it is checked: the seat's fixtures hold an
error answer for each verb, and each caller's unit table has a row for it (the forge bridge, the checker, the
indexer, the gate).
- §313 **Who may call an act.** The holder's trusted config value `callers`, which today names per verb who may call the
two package seats' verbs (design 51), gains a line per verb of the seat `git`: the act verbs name the forge bridge
alone, the reads name each part that declares them in `invokes`; the grants part grants by it, and the holder
refuses a caller it does not name. From G's delivery the callers per verb are the seat definition's field `callers`,
the grants part's one source (ADR 0666 §20): `status`, `comment`, `view` and `note` the forge bridge alone,
`merge-pull` the persons and agent sessions the tool runner proves, the reads the parts that declare them. The forge module's own acting tools (`gitea_merge_pull_request`,
`gitea_commit_status`, `gitea_delivery_view`, `gitea_note_append`, `gitea_add_comment`) retire with the milestone,
and so do its watch of the checker's state `owed` and its read of the gate's `source`, so the bridge stays the one
part that acts on the forge. How it is checked: test 51 reads the grants, not the code.
- §314 **`merge-pull` judges no check, and from G's delivery no Approve, only the runs at the head's fingerprint.** The operator's answer that no check gates delivery (Telegram, 2026-11-09 01:11 UTC,
"Remove them in G") covers the forge module's own setting `check-gates-merge` as well as the gate's: both are
removed, and `merge-pull` merges an approved head whatever its `mesh/check` says. The record that ends ADR 0551 and
ADR 0548 §2 to §4, §9 and §10 also changes ADR 0534 §4's judgement of `mesh/check` by the merge; it is to be accepted
before the push (*Nothing merges into a repository under check*). From G's delivery it judges no Approve either, its caller
is any session the tool runner proves, and it refuses a head when any run at its fingerprint failed a `required` step,
never waiting for one (ADR 0666 §11; *Approval is a step, and a session merges a pull request*). The reads the
gate would have needed to merge (the forge's word for a conflict, the base's required statuses, the merging account's
write access) are not added: no part of the mesh but `merge-pull` merges, and it answers git's own refusal of a merge
as refused.
- §315 **The order is the module graph's, and each step has its way back.** The verbs are a served-verb edge (ADR 0614):
the forge module's new build runs before the forge bridge's, and the forge bridge's before the gate's, since the
bridge calls the verbs and performs the gate's `owed`, and the gate says `owed` only to a bridge that hears it; all
three are ahead of the acting parts' steps, which read through the seat. While the new forge runs and the old bridge
still calls the retired acting tools, the old bridge's acts fail loudly and stay unperformed in the old gate's keys,
and the switch carries them (*What the gate owes the forge*). A revert goes back in the other order: the gate, then
the bridge, then the forge, each wanted only once the one after it in the forward order runs its reverted build, so
no part calls a verb nobody serves. ADR 0656 §3 stands beside it: the acting parts' new versions act first in their
own steps. Two things cover the gap while the forge is still on its old build and serves no verb of the seat: the
indexer starts on its kept definitions (below), and a check whose read through the seat `git` fails says `could not
run`, naming the forge, and is asked again on the forge module's `running` (*A check runs to its end*).
- §316 **A part that cannot read the forge keeps what it has, and says so.** The indexer, at its start, waits for the
seat `git` at most 2 minutes; with no answer it judges from the last definitions it kept, says in its
`<module>_first_views` that it waits for `git` and since when, writes `view-waits` after five minutes, and reads the
definitions once the seat answers; it then judges again every manifest it judged from kept definitions. Two minutes is longer than a forge's restart, and short enough that a start is
not held behind a forge down for good. How it is checked: test 54.
- §317 **How it is checked.** The seat definition's conformance fixtures hold one answer and one error of each verb and one
of each event, and the forge module's tool tests answer each fixture; the forge bridge's, the checker's, the
indexer's and the gate's unit fakes answer from the same fixtures; a call of an act verb from an account other than
the forge bridge's is refused (test 51); live, a search of the delivery parts' and the gate's code finds no name of
the forge module's tools or events.
§318 **Links of the trace.** No part reads a review line (ADR 0659 §1 to §3); who may review and the review verb's grant
stay issue 1782's, outside milestone G, and that verb gates nothing (ADR 0659 §2). The trace's links are this
design's choices under ADR 0656, so that one call traces research to the running build
with no text match (research 085, Findings 7). The issue tracker gains link kinds from an issue to the decision and to the research effort
it carries, made by a tool call and never by text, as ADR 0493 §4 has every link made; the knowledge base gains a
typed link from a decision to the milestone of the issue tracker that builds it, made by a tool call, which joins the
link kinds of its link-fits rule (design 55), beside the `became` link a research effort gets at its graduation
(playbook 02). How it is checked: whole (gate), one ask per milestone, withdrawn by a new head;
a waiter on one member's pull request wakes only on that member's transition. Whole (issue tracker, knowledge base):
an issue linked to a decision and to a research effort reads both back; a decision linked to its milestone reads it
back. Live: research 085's milestone's record reads back research 085 to its decisions, its milestone, its issues and
its pull requests with no text match.
§319 **A session hears that a record was accepted** (issue 595). The knowledge base already says, on its holder's own
stream, the event `reviewed` for every judgement of a record; from milestone G that event carries, when the operator's
Approve accepts a record, that it was accepted and by the operator, with the warrant, and the tool runner's account is
granted to hear it, so a session waiting on a record wakes on it. The design's choice under ADR 0596 §1 (a module's
events are subscribed to): a new event of the seat `knowledge-base` would be a second path for the same fact (lead-47,
2026-11-09). How it is checked: in the knowledge base's whole-module test an Approve that accepts a record says
`reviewed` with what was accepted and by whom, and the tool runner's account hears it.
## What retires at milestone G
- §320 At research 085's milestone's delivery: the indexer's map from language to build module, and the checker's own
module names and its cases for a change to the manifest validator and to the Go build module (ADR 0656); a build
module's edge as a trigger, with the extra build per recipe change (ADR 0603 §3, as ADR 0656 changes it); composing
from manifests a module whose changed build module the check cannot run (ADR 0603 §10 and ADR 0614 §7, as ADR 0656
changes them); the module graph's one key per module with five past values as what parts read (ADR 0603 §2, as
ADR 0656 changes it); the tiers worked out from five live sources (ADR 0603 §6, as ADR 0656 changes it); a seat
list compiled into a part, and every read of another module's bus state the revision view replaces (ADR 0656); the
gate's `merges`, carried into the delivery records and written no more, and its `owed`, said as events; the hold,
the merge turns, their stalled line and tools and the setting `check-gates-merge`, once their record is accepted;
the last three reads of a pull request's description (ADR 0659 §4); every delivery part's call of the forge module's
own tools, for the seat `git`'s verbs; and the four seat-list readers the delivery parts wire by hand.
- §321 At milestone G's delivery, from research 086 (ADR 0666, ADR 0667): the tagged versions, `tag-wanted` and
`tag-asks` (§373); the checker (`mesh-delivery-checker`), its seat,
its try with `tryevent` and the indexer's try candidates, its kept verdicts and carries (the runner's from then), the
checking program and `mesh/check`, and its buckets and bus account, removed with the gate's forge merge token only
after the first pipeline after G's delivery has delivered a change end to end and on the operator's one word, its
buckets through `drop` after a snapshot whose restore is tested (ADR 0667 §12); the bundle of steps run before a merge; the Approve per
merge, the milestone ask and its three events, and the gate's merges; the gate's merge states, one key per pull request and its verbs `table`, `recheck` and
`close`; the trunk-only want; `source.main`, for one source revision per watched branch; the fixed branch,
repository and module names G's subtasks remove (§7); the recovery merge verb this design sketched, never built; and the holders'
trusted `callers` values, for the seat definitions' `callers`. The words **merge check**, **try**, **trunk commit**
and **merge turn** go to *Not:* lines with the as-is update after that delivery, as the glossary's entries say.
@@ -0,0 +1,516 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0656"
- "ADR 0636"
- "ADR 0635"
- "ADR 0625"
- "ADR 0619"
- "ADR 0603"
- "ADR 0596"
- "ADR 0595"
- "ADR 0584"
- "ADR 0573"
- "ADR 0564"
- "ADR 0563"
- "ADR 0548"
- "ADR 0363"
- "ADR 0289"
- "ADR 0253"
---
# 47, milestone G: the answers to the builders', the reviewers' and the operator's 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".
## What milestone G's builders found open, answered by this design (2026-11-10)
*ADR 0666, ADR 0667, ADR 0668; ADR 0656, ADR 0596, ADR 0595 §4, §9, ADR 0619 §1, §2, ADR 0563, ADR 0564 §2, ADR 0625
§1, §4, ADR 0635 §4, ADR 0548 §5, ADR 0363 §2; design 27, *Which module answers*. The operator, 2026-11-10: a small
choice within what a record decides is the design's own, written here with its reason and reviewed by the reviewers
by architectural domain.*
§322 Milestone G's prepare and develop met these questions (issue 1966). Each line is the design's choice, with the
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
`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
ends failed, naming the seat (ADR 0666 §18). Why: the language is already in the manifest and the catalog half is
the runner's one view (ADR 0666 §24, ADR 0596). How it is checked: whole (runner), a Go and a TypeScript fixture
module each ask their own build seat, and one with no holder ends failed naming the seat.
- §324 **An acting rule's `on` may name an artifact kind.** Beside `every` and a language, `on` may be `kind: image`,
matched against the manifest's artifact entry's kind; the seat `node-build-image`'s rule says `kind: image`. Why: an
image has no language, and the rule's fields are this design's (*What ADR 0656 leaves to this design*). How it is
checked: a unit row of the definition reader accepts `kind: image` and refuses another kind.
- §325 **An approval never asked ends `not asked`.** When a `required` step failed before the `approval` step was asked,
the `approval` step ends `not asked`, naming the failed step, never as passed; a `rerun` that passes the failed step
asks it then, and the pipeline ends again: the runner says `pipeline-ended` once more for it, its end count one
higher, which the assigner takes (*What holds a delivery*). Why: approval comes last (above),
and every end a step can have is said as itself (ADR 0666 §10). How it is checked: whole (runner, assigner), a failed
unit step leaves the approval `not asked`; its passing rerun asks it, and once approved the second `pipeline-ended`
writes the want.
- §326 **A build seat keeps its `steps` answer.** The build module keeps each artifact's detected steps in its state
`steps`, from the template `state`, one key per artifact holding the answer for that artifact's newest build-input
fingerprint, replaced when a newer fingerprint is detected and by `steps` with `again`; a key whose artifact no
longer exists is dropped by `steps` with `drop`, on a person's word, written in the hand-act log. With one key per
artifact (about 120) the state needs no aging and no read time. Why: ADR 0666 §25 asks a tool for "the steps
detected for an artifact and detect again", which a reply computed anew each time would not need; a build module is
no foundation part, so the template form (ADR 0666 §4). How it is checked: whole (Go build module), `steps` answers
from the key, a newer fingerprint and `again` replace it, and `drop` removes one.
- §327 **The step code lives in the domain SDK.** The step run and the fetch-and-release code the three build modules
share move, in this milestone, into a build-step package of `mesh-delivery-sdk`, which the Go, TypeScript and image
build modules import; none keeps a copy. Why: code shared between modules lives in an SDK, and the delivery
domain's shared code is `mesh-delivery-sdk`'s (ADR 0635 §4); the copies were met in develop. How it is checked: a
search of the three build modules finds no fetch, release or step-run function of their own.
- §328 **Lint per language.** Go's lint step is the Go build module's linter; TypeScript's is the type-check; an image
has no lint, unit or integration test step, so `steps` answers none for it and the seed has those `bypass` (*A
module's registration*). No lint step scans for module, branch or repository names (§7). Why: ADR 0666 §7 lets a
build seat answer only the steps an artifact can run; the scan's removal is the operator, 2026-11-11 about 00:05 CET. How it is checked: whole
(TypeScript and image build modules), `steps` answers the type-check for a TypeScript artifact and nothing for an
image.
- §329 **Withdrawn.** It named what the fixed-names scan exempted; the scan goes (§7, the operator, 2026-11-11 about 00:05 CET).
- §330 **The runner's own kept data age by its own deletes, in the step log's order.** The runner deletes its own
`pipelines` keys, `kept` entries and `step-outputs` objects by the retentions of *The runner's kept data*. It keeps,
in its old `state` section `aged`, three step-log sequences, one per retention (7, 30 and 90 days), each the last
entry it aged at that retention. At each step's end it reads the step log onward from each sequence, at most 20
entries per sequence, while the entry is past that retention: at 7 days it deletes the entry's output and the
pipeline it names unless that pipeline ended passed on a watched branch or is its branch's newest for its module; at
30 days each `kept` digest the entry names whose `last-read` is past 30 days; at 90 days the passed pipeline the
entry names, unless it is its branch's newest. It then moves the sequence; nothing is scanned whole. Each delete
writes a line in the runner's journal naming the key or object and its retention. A foundation part's sent store
(`sent-objects`) is aged the same way by its owner at each send, from the sequence of its own sends. Nothing waits on
a timer, and nothing but the owner deletes. Why: an old section cannot declare an age, and deleting one's own entries
is the owner's act, not the removal of a store ADR 0595 §4 holds to a person's word; §57's rule that only the drop
tool removes an object holds for a template's `<state>-objects`, while the runner's `step-outputs` are its own old
section's objects. How it is checked: whole (runner), an output 7 days old is gone after the next step's end, a
passed pipeline stays to 90 days, and one step's end deletes at most 20 per sequence.
- §331 **The kept event streams' caps, and when one is full.** On the seat `mesh-pipeline-runner`, each kept 90 days:
`pipeline-started` 32 MiB, `pipeline-ended` 192 MiB, `group-ended` 64 MiB, `run-started` 16 MiB, `step-started`
and `step-ended` 128 MiB each, `wait-held` and `wait-ended` 32 MiB each; the seat `git`'s `branch-moved` 64 MiB; each
build seat's `step-ended` 128 MiB; `mesh-wants`'s `step-ended` 32 MiB; `mesh-builds`' `package-published` and
`mesh-catalog`'s `registration-changed` 16 MiB each; `package-published` 128 MiB; `version-released` 128 MiB,
`version-delivered` 64 MiB, `toolchain-agreed` 16 MiB, and `versions-filled` 1 MiB with no age limit; all others
90 days. At about 300 releases a day (a mesh-sdk publish rebuilding about 120 modules), `package-published` of about
two artifacts each and `version-released` each say about 0.6 MiB a day, about 54 MiB in 90 days, and
`version-delivered` about 27 MiB. Why: about 300 pipelines a day of about 6 steps
(*The runner's kept data*) say about 1,800 step events a day of about 0.5 KiB, about 80 MiB in 90 days, and about 300
pipeline ends of about 4 KiB, about 105 MiB, 55 % of its cap; a build seat runs about 1,200 steps a day, about 54
MiB; registration changes are a few a day; estimates, read from the fill tools once G runs. A kept
seat stream at its cap drops its oldest message, as a stream with a cap does; the grants part, which makes the
streams, says `filling` on its seat `mesh-grants` past 80 % and `room` below 70 %, which the watchman's
`store-filling` raises and clears as for every store (*A store filling is said before it is full*). How it is
checked: each seat definition's fixtures name each stream's `keep`; whole (grants part), a stream past 80 % says
`filling`.
- §332 **A group's end is said under the group.** `group-ended` is said on
`mesh.seat.mesh-pipeline-runner.event.group-ended.<branch>.<group>`, with no module part, since a group spans
modules; a session that merged a group's member waits on `group-ended.<branch>.<group>` of its group after its own
`pipeline-started` (*What a session hears after its merge*). Why: every other runner event names one module
(*Health and going back*). How it is checked: the seat's
fixtures.
- §333 **The runner hears the indexer's own event.** The runner's manifest `consumes` the indexer's own event
`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
(`<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
same pipeline (*A second start writes nothing*). A pipeline the runner starts from `outdated` (its start, §375, or
`runner_catch_up`) is keyed by the dependency's watched branch, the dependency's source revision `outdated` names, and
the dependent module, so it is the same pipeline the event would have started. Why: a pipeline's key is branch, source revision and module (above),
and the publication is made from a revision of a watched branch (ADR 0666 §15). How it is checked: whole (builder,
runner), a publish starts one pipeline per dependent, keyed by the publishing revision.
- §335 **What the teller says of a widening.** Each entry of `widens` names what widens (`acting-rule`, `grant`,
`credential` or `serving-part`), its name, the module and the commit. The teller says one message per
`pipeline-ended` that carries any, on every channel, once per module, watched branch and commit: it keeps in its
state `widens-said`, from the template `state`, one key per module and watched branch holding the last commit it
said, replaced by the next and deleted when `registration-changed` says the branch is no longer watched; its tool
`teller_widens_said` reads it. Why: ADR 0666 §23
says a widening in the pipeline's end, and the teller passes it on (*Who may say what*). How it is checked: whole
(teller), a `pipeline-ended` said twice with the same `widens` gives one message.
### Callers
- §336 **The forms of `callers`.** A verb's or an accepted kind's `callers` is a list whose entries are: a holder role (a
seat's name), a module, `operator`, `healers`, `persons` (any person the tool runner proves), `sessions` (an agent
session the tool runner proves), `invokers` (every module whose manifest declares the verb in `invokes`), and
`holder:<seat>@same-node` (the holder of that seat on the node of the verb's holder). A definition whose `callers`
today is text is given a list in this milestone: "any caller whose role grants it" becomes `invokers`, "a person"
`persons`, and every other text the names the design gives that verb. Why: ADR 0666 §20 makes the definition the
grants part's one source of caller limits, in a form it reads; `invokers`, which ADR 0666 §20's list does not name,
is the operator's choice for the verbs whose text said "any caller whose role grants it" (Telegram, 2026-11-11 02:13
CET: "Any declaring module"), and by the same answer, that a verb open today stays open to any module declaring it,
for the fifteen verbs of the Connectivity domain's seats that name no `callers` on main, which §68 would otherwise
close to their holders and the operator: `node-hostname`'s `entries`, `add` and `remove`;
`node-intrusion-prevention`'s `status`, `banned`, `ban` and `unban`; `node-packet-filter`'s `rules`, `reload` and
`remove`; `node-resolver`'s `routes`, `route` and `unroute`; `node-uplink`'s `resolvers` and `links`. Each of their
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
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.** 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
`input` verbs (`mesh-catalog`'s and `mesh-wants`') the holders of the build seats. Why: a step's result is taken only
from its seat holder (ADR 0666 §21), so the ask side is limited too. How it is checked: a `step` published by another
account is refused by the bus.
- §339 **The contract generator reads `callers` and makes nothing of it.** The generated contracts carry no caller check;
the grants part reads `callers` from the definition, and the generator refuses a definition whose entry is no form
above. Why: one source of caller limits (ADR 0666 §20). How it is checked: the generator's test refuses an unknown
entry.
- §340 **The reader release first.** mesh-sdk's seat-definition reader released before G's check reads `callers` as text
or as a list; G's own reader refuses text. The same release carries mesh-sdk's manifest reader of the new words of
§89 (`version`, a `*` emitter in `consumes`, an observer's finding stream), which the running checker's validator
reads before G's manifests use them. Why: readers first (ADR 0667 §4), and every definition is a list from G; it is merged ahead of G's check because the
running checker, which makes G's own check (ADR 0667 §2), must read G's definitions, the operator's choice of a
reader first (2026-11-09).
How it is checked: unit rows for both forms in the release, and for the refusal in G.
- §341 **TypeScript contracts as JavaScript.** The generator emits each seat's TypeScript contract as `index.js` with its
`index.d.ts`, so a TypeScript module imports it with no build step of its own. Why: a seat's contract is generated
from its definition (ADR 0625 §1), and a gap of the build system is built in it. How it is checked: a TypeScript
module builds against a generated contract with no `tsc` of the contract.
### The catalog, the builder and the assigner
- §342 **A module not watched on a branch.** A watched branch's catalog graph takes each module not watched on that branch
from the registration's first watched branch. `register` names a module's watched branches; with none named it takes
the repository's default branch, read through the seat `git`. Why: a placement's default is the registration's first
branch (ADR 0666 §6). How it is checked: whole (indexer), a `beta` revision holds a `main`-only module at `main`'s
head.
- §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
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
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; a put not sealed within the call's bound is dropped by the tool runner and its call
fails, naming the object. No value is split over objects. Only modules that import the object store package are
rebuilt for it. Why: one object per value (ADR 0666 §16), through the tool runner (ADR 0595 §9). How it is checked:
whole (mesh-sdk), a value of a hundred times the limit is read back whole, and a put stopped half way fails naming
the object and leaves none.
- §346 **One object store package.** The assigner's and the indexer's object store code is mesh-sdk's small object store
package, with no copy in either. Why: ADR 0595 §9. How it is checked: a search finds no second copy.
- §347 **Reading sent objects is a bus state call.** mesh-sdk lists the call that reads a sent object under its existing
bus state capability. Why: ADR 0595 §9 puts the calls in that package. How it is checked: the package's table test.
- §348 **The builder reads the catalog through its seat.** The builder reads the registrations' watched branches through
`mesh-catalog`'s `registrations` and `registration-changed`, the released versions it numbers from through
`versions` and `version-released`, keeping no list of versions of its own, and a build seat's holder from the seat list; the
manifests, the packages, each package's name in its ecosystem (the Go module path, the npm name, which the catalog
graph entry's packages carry) and a run's candidate from the catalog half of its revision view, never the indexer's
state. With no holder of a build seat, a build ends `could not run`, naming the seat, and is asked again on the seat
list's change. Why: ADR 0596 §2. How it is checked: the builder's account holds no grant to the indexer's state.
- §349 **The catalog holds released versions only, and says each.** The indexer keeps, for the seat `mesh-catalog`,
each module's released versions in its old `state` section `versions`, one key per module and version holding each
artifact's digest, the source revision built and its edge entries (§374) (issue 1961's catalog of versions). It hears
every artifact's `package-published` (packages, images, bundles and archives alike) and records a version as
released once every artifact the module's catalog entry names is published at it, then says `version-released` (the
module, the version, each artifact's digest, the source revision) on
`mesh.seat.mesh-catalog.event.version-released`. A version lower than the highest it holds released for that module
is recorded, marked `released late`, and not said: no `version-released` is lower than the module's highest, and
nothing follows one (§382). Each module keeps its newest 100 versions: at each release the indexer deletes that
module's oldest beyond them, never its highest of any `MAJOR.MINOR`. The verb `versions` answers a module's released
versions, its callers the builder, the holders of the registry seats and any person or session. A reserved version
is not in it. The builder's `version.numbered` is no longer said or heard. Why: the operator, 2026-11-10 about 21:40 to 23:00 CET (gaps items 23 and 43); the
key per version, the 100 kept and the late release are the design's choice, round 2 of the review by architectural domain, 2026-11-10. How it is checked: whole (indexer), a version with
one of two artifacts published is not released, and with both is, said once; a late older version is not said.
- §350 **A superseded build's drop is a kept call.** The runner ends a superseded build's step `superseded` at once, and
asks the build seat's `drop` as a kept call (ADR 0668 §1, §2), so it is asked again through every transport fault
and said `call-unanswered` after five minutes in the runner's findings. How it is checked: whole (runner), a drop
unanswered is asked again, and raises `call-unanswered` after five minutes.
- §351 **One rule for asking again.** mesh-sdk's kept-call form (ADR 0668 §7) asks an ask again only while it is open and
not replaced, and every asker and every hand ask-again tool uses it. How it is checked: unit (mesh-sdk), a replaced
ask is not asked again.
- §352 **The assigner's newer-revision check reads its own state.** The check adds no field to the want (*Composing waits per
node*): the assigner keeps in its old `state` section `written` one key per module and watched branch, the source
revision and end count it last wrote that module's wants from, compares a new `pipeline-ended` with it by
compare-and-set, and deletes the key when `registration-changed` says the branch is no longer watched; its tool
`assigner_written` reads it. How it is checked: whole (assigner), an older revision ending after a newer one writes
nothing, and a later end of the same revision writes.
- §353 **The assigner follows the registrations.** `mesh-delivery-contracts`' client `meshcatalogclient` carries a typed
follower of `registrations`, a first view then `registration-changed`, built on `go/views`; the assigner and the
forge module's merge switch on its first registrations (ADR 0667 §10). How it is checked: whole (assigner), it keeps
the trunk-only want until the first `registration-changed`.
- §354 **The assigner's composition step runs the composer in the sandbox.** It asks the seat `node-build-go` the ask `run`
(*What ADR 0656 leaves to this design*), its step keys under `assigner.`, and serves the ask's input by its seat
`mesh-wants`' verb `input`, as the checker's seat does today; the indexer does the same for its validation by
`mesh-catalog`'s `input`. Both are foundation parts, so each keeps its inputs in an old `state` section `inputs`, one
key per input named by its write time and its sha256, deleted when the build seat's `step-ended` of that ask is heard
or a day after its write, whichever is first, the day judged at each new ask over at most 20 of the oldest keys;
`assigner_inputs` and `indexer_inputs` read them. Both verbs' callers are the holders of the build seats. The Go build module reads no revision verb. Why: the
composition step runs on parts built from the heads, in the sandbox (ADR 0666 §17). How it is checked: whole
(assigner), a composition step's `run` is answered and its input read by digest.
- §355 **The assigner says an unanswered `manifest-reads`.** The assigner, the one caller of the node-engine's
`manifest-reads` from G, writes `call-unanswered` as its own finding and says it as an observer does (§383, §384),
so the condition keeper hears it with no change of its own, departing from ADR 0668 §3's reads by the operator's
answer of 2026-11-11. The assigner and
the runner each declare their own state `findings` in their old `state` section, both being foundation parts, read
by their own read tools alone. How it is checked: whole (assigner, runner, condition keeper), a node-engine that
never answers makes the assigner say `finding-written` for `call-unanswered` after five minutes, and the keeper
raises it from that event.
- §356 **Two acting parts in different steps make no cycle.** The assigner's cycle check counts waits within one step only
(*A wait holds only within its step*). How it is checked: the unit rows there.
- §357 **Which provider a requirement points to.** In the assignment graph, by design 27's order: the named seat's
holder, a pin, the provider on the consumer's node, the only provider; otherwise the entry is held, naming the
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
its caller. 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.
- §360 **An approved ask binds the placement.** What an approved ask binds to includes the placement's watched branch and
its `build`, beside the fingerprint. How it is checked: whole (assigner), a placement changed after the Approve asks
again.
- §361 **A placement's `build` is a held put.** A placement's `build` is written as a held put of that build, released when
the placement drops it; a put a person wrote is never released by a placement change. How it is checked: whole
(assigner), a placement's `build` holds that build on its nodes, removing it releases the put, and a person's put
stays through a placement change.
- §362 **The indexer's first-start catch-up runs apart from its source handler.** At its first start the indexer takes in
the heads the gate's source names by its catch-up path before it starts its handler of `source-moved`. How it is
checked: whole (indexer), a `source-moved` said during the catch-up is applied after it, once.
- §363 **A stale ask is dropped, backups included.** The assigner drops an ask it raised once the want it asked for is
replaced, a backup ask among them (built in mesh-delivery #200). Why: ADR 0596 and ADR 0668 §2. How it is checked:
whole (assigner), a backup ask whose want was replaced is dropped and not asked again.
### The condition keeper and the node-engine
- §364 **The node-engine claims its seat.** 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
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
catalog revision, so a module watched on any branch is a source. A condition of a module that is in no branch's
revision is cleared, said with the reason `module left the catalog`. A module's findings of a node are cleared,
said with the reason `module left the node`, when the node-engine seat's `running.<node>.<module>` says the module no
longer runs there and the newest assignment graph revision, from the keeper's first view of the assignment half
(through `meshwantsclient`, following `assignment-revision` of `mesh-wants`), no longer places it there; never for a
module no revision placed on that node. For it, `running.<node>.<module>` (§309) carries `stopped`, with no
fingerprint, once the node-engine's have no longer holds the module, on the same subject, so the stream kept last per
subject keeps it. Until its first view the keeper applies the events of the sources it keeps (§388) and says
`view-waits` after five minutes (*A part with no view says it waits*), and a missing assignment half holds only the
clears of `module left the node`. It reads no module's `findings` state (§383). Why: no part fixes
a name (§7), and the keeper hears findings and reads no other module's state (ADR 0596 §2, §4), departing from
ADR 0564 §2 by the operator's answer of 2026-11-11; the assignment half and `module left the node` are the design's
choice, and so is `stopped`. How it is checked: whole (keeper, node-engine), a fixture module declaring the finding
events is heard with no code change; a fixture module's condition on a node is cleared with `module left the node`
once the node-engine says `stopped` for it and the assignment graph no longer places it, and is not cleared on the
assignment revision alone; the catalog's validation finds no `findings` in the keeper's manifest `reads`.
### From round 1 of the review by architectural domain (2026-11-10)
- §366 **The runner's stores say their fill.** *A store filling is said before it is full* holds for the runner's
`pipelines`, `step-outputs` and `kept`: it says their fill in `show` and `kept`, `filling` past 80 % and `room` below
70 % on its seat, and `full` at the cap, when it starts no new pipeline, says each start it refused, and takes them
up on `room`; nothing is dropped to make room. Why: the runner joins the stores of *Two graphs, one source of truth*
(ADR 0666 §24, ADR 0564 §2). How it is checked: whole (runner), a full `pipelines` refuses a start, said, and
`room` starts it.
- §367 **A forge bridge message is read again on a transport fault.** The indexer reads a message of the forge
bridge's stream again when reading it met a transport fault, as main's `UnreadAgain` does (ADR 0668 §1, §4): the
message is left unacknowledged and its read asked again on the bridge's next answer, while a message the bridge
itself answered as lost stays lost, said in the indexer's journal and in `indexer_pending`. G's own change for it
(`LostAgain`, commit 106fc16b) is not built. Why: one rule for kept calls (ADR 0668), already on main. How it is
checked: the indexer's take-in table, a transport fault reads the message again and a lost answer does not.
- §368 **A manifest says its `MAJOR.MINOR`.** Each module's manifest names `version`, two numbers `MAJOR.MINOR`; the
manifest validator refuses any other form, a pre-release or custom suffix (`beta`, `alpha`) among them: no custom
version exists for now. A manifest without it is refused once G's reader runs (ADR 0667 §4: the reader is delivered
first). G's change writes `version` into every existing manifest, the `MAJOR.MINOR` of the version §372 fills for that
module, `0.1` for a module with none published, in the same group as the reader. Why: the operator, 2026-11-10 about
21:40 CET; the values are the design's choice. How it is checked: unit (manifest validator), `1.4` accepted,
`1.4-beta` and `1.4.2` refused; the catalog's validation finds no manifest without `version` at G's heads.
- §369 **The reservation is one key per module and `MAJOR.MINOR`.** The builder, a foundation part, keeps in its old
`state` section `reserved` one key per module and `MAJOR.MINOR`, the version its build reserved and each artifact's
digest once built, written by compare-and-set at the build's start; a newer build of the module takes the next after
the higher of the highest released and the one reserved, so two builds never share a version, and a superseded
build's version is never released, leaving a gap in the patches. The builder deletes the key when the catalog says
`version-released` for it, or when a version of another `MAJOR.MINOR` of the module is released (the manifest moved
on); `builder_reserved` reads the keys and `builder_reserved_drop` drops one on a person's word, written in the
hand-act log. A reserved version whose build ended with an artifact unpublished past the publish's 10-minute bound
is the builder's finding `version-unreleased`, naming the module, the version and the missing artifacts, cleared by
its `version-released`. `builder_publish_again` names the reserved version it publishes under. The builder reserves
nothing until the catalog says `versions-filled` (§372); a build waiting for it says `wait-held` naming the fill. The
seat `mesh-builds`' verb `reserved` answers a module's reserved version with each artifact's digest, its callers the
holders of the registry seats (design 51, *Publishing*). This is no list of released versions: numbering reads those
only from the catalog. Why: the operator, 2026-11-10 (a version reserved when the build starts); the key, the gap,
the finding and the verb are the design's choices. How it is checked: whole (builder), two builds close together
reserve two versions, the key goes on `version-released`, an unpublished artifact raises `version-unreleased`, and a
builder started before the fill reserves nothing until `versions-filled`.
- §370 **The version reaches every artifact's build.** The build seat's ask carries the version, and the build module
passes it to the artifact's build as the build argument `MESH_VERSION`: a Go package is tagged `vMAJOR.MINOR.PATCH`,
an npm package's version is it, an image carries it as its tag and label, and a program reads it as its own version.
A Go package's version is its tag: this departs from ADR 0573 §2, which has a Go package's version be its trunk
commit, by the operator's choice of 2026-11-10, and a later research brings that record in line. The git tag
(`go/vMAJOR.MINOR.PATCH`, design 51) is written by the builder only on the catalog's `version-released`, through the
seat `git`'s verb `tag`, whose callers are the builder alone, so a superseded build's reserved version is never
tagged. How it is checked: whole (Go and TypeScript build modules), a built package and program carry the reserved
version; whole (builder), the tag is written on `version-released` and never for a superseded build; whole (forge
module), a `tag` from any caller but the builder's account is refused by the bus.
- §371 **Only the first watched branch releases.** Only a build of the registration's first watched branch reserves and
releases a version; a build of another watched branch reserves none, publishes no version and is delivered by its
build, its fingerprint, as every want names one. Why: the operator, 2026-11-10. How it is checked: whole (builder),
a `beta` build reserves nothing and is delivered.
- §372 **The catalog's versions start from what is published.** At the new indexer's first start, `versions` is filled
once, as released, for each module from the highest `published` version over every artifact of the module that the
builder's state `versions` held (for mesh-sdk the higher of Go's `v0.1.14` and npm's `0.1.18`, so `0.1.18`), read by
`builder_versions` as a kept call (ADR 0668 §1, §2), which the indexer's manifest names in its `invokes`
(`mesh-delivery-builder.builder_versions`) until that tool goes, an unanswered call raising `call-unanswered` after five minutes;
it is not filled again. Then the indexer says `versions-filled` on its seat, once, kept on its stream with no age
limit, and the builder reserves nothing before it (§369). The builder keeps serving `builder_versions` until then: the
running builder serves it already, and the new one serves it too, so no ordering edge is needed. The new builder's
`builder_versions` also answers each reserved version with its artifacts' digests (§369), so a registry holder still
on its version before G, which asks `builder_versions`, stores a publish of a reserved version made between the
builder's delivery and its own (§381). The builder's state
`versions`, the floor and its tool `builder_floor_again`, the event `numbered`, `builder_versions`, and `tag-asks` are
removed in the first change after the first pipeline after G's delivery has delivered a change end to end, the state
through `drop` on a person's word (ADR 0595 §4). Why: numbering reads only the catalog's released versions (§348),
and without this every first version under G would start below today's and Go's `@latest` would never move (the design's choice, round 2 of the review by architectural domain, 2026-11-10).
How it is checked: whole (indexer, builder), with Go `v0.1.14` and npm `0.1.18` published before G, the first G build
of `0.1` is `0.1.19`, and a builder started before the indexer reserves nothing until `versions-filled`.
- §373 **A group's run on request publishes no version.** A run on request of a group builds each member that
depends on a package member against that member's build by its digest. The builder's build ask for such a member
carries each package member's build digest, which the builder takes from its own `build.ended` of that member's
build, its own fact (ADR 0596 §2); the build module fetches that build from the store by the digest, checks the
fetched bytes against it, and lays it in the build's workspace as the module that package names (a Go workspace's
replacement, a TypeScript local package), so nothing is fetched by a version, and no version is reserved, numbered
or published for the run. A dependent whose package member's build is not done waits, said as the builder's
`wait-held` naming the member, within building's 60-minute bound; a member's build that failed or is missing past
that bound fails the dependent's step, naming the member. The tagged versions, `tag-wanted` (the checker's, and none
of the runner's) and the builder's `tag-asks` with its tools `builder_tag`, `builder_tag_asks` and `builder_untag` go
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
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
makes the dependent stale or only orders it (§378), the manifest word it came from, the part it points to, and what
the edge depended on:
- kind 1 (an imported package) and kind 3 (the build toolchain): the dependency's version and the digest of the part,
as the build used them. The build module names each dependency it resolved, version and digest, in its step key,
and the builder carries them in each artifact's `package-published`, so the indexer reads no other part's state
(ADR 0596). Kind 3 points at the toolchain part the agreed toolchain names (ADR 0584 §1), never at the build
module's own program.
- kinds 2 and 4 (delivered after): the dependency's version and part digest of its newest `version-delivered`
when this release is itself first delivered (its own `version-delivered`, §376), one entry for the mesh.
- kind 5 (has acted for): a fact, not a version: the acting part, its step, the build that acted and that the act
was done (or released by hand), from the holder's `wait-ended` (*Each step's wait is its own part's*).
- kind 6 (runs a version that serves): a fact: the verb or manifest word used and the dependency's delivered
version that serves it, written at the same first delivery.
The entries of kinds 2, 4 and 6 read `not yet delivered` until that first delivery, are written once then, and none
is changed after; kinds 1, 3 and 5 are written at the release.
A build of a watched branch other than the registration's first releases no version (§371): its entries are kept in
the indexer's old `state` section `build-edges`, one key per module and watched branch holding the newest build's
entries, keyed by its fingerprint, replaced by the next build and deleted when `registration-changed` says the
branch is no longer watched, and they compare by the part's digest alike. The declared edge stays in the catalog
graph; an entry is the release's or the build's fact, written once. Why: the operator, 2026-11-10 about 22:30 to 23:00 CET and 22:10 CET (issue 1961), and
the lead's review of these lines. Each entry is about 0.1 KiB, about ten a version, so `versions` stays within its
64 MiB (an estimate). How it is checked: test 104.
- §375 **One query finds every edge behind, by the part's digest.** The seat `mesh-catalog`'s verb `outdated`, with
the indexer's tool `indexer_outdated`, answers every entry that is behind: for kind 1, the recorded digest differs
from the part's digest in the dependency's newest released version (for a build of another branch, its newest build
on that branch); for kind 3, it differs from the toolchain part of the builder's newest `toolchain-agreed`; for kinds
2 and 4, it differs from the dependency's newest `version-delivered`; for kind 5, the newest version of the acting
part has not acted for the dependent's current build; for kind 6, the delivered version no longer serves the verb or
word. A dependency's newer version that left the part's digest as it was makes nothing behind, so nothing is done
twice for the same inputs. Only a stale edge (§378) that is behind has an act, a rebuild; an ordering edge behind is
shown and starts nothing. Each answer names the module and its version or build, the kind, the dependency with its
watched branch and source revision, what was recorded and what is newest, and the act. It may be asked for one
dependency. Its callers are any person, an agent session, the healers and the runner. At its start the runner asks
`outdated` once, a kept call raising `call-unanswered` after five minutes, and starts the rebuilds it names; the
runner's tool `runner_catch_up`, for the operator and the healers and written in the hand-act log, does the same by
hand, for one dependency or all. A restart or a missed event so leaves nothing behind. Why: the operator, 2026-11-10
(issue 1961's "outdated"); `runner_catch_up` replaces the indexer's hand tool, which would have had to say another
part's event (the design's choice, round 2 of the review by architectural domain, 2026-11-10). How it is checked: test 105.
- §376 **A stale edge rebuilds on its event; a delivery is waited for mesh-wide.** The rebuilds start on two events
the runner consumes: kind 1 on `package-published` (§53), and kind 3 on `toolchain-agreed`, which the builder says
on its seat `mesh-builds` when the agreed toolchain of a language changes (ADR 0584 §1), naming the language, the
version and the toolchain part's digest; for each, the runner asks `outdated` for that dependency and starts a
pipeline for each dependent behind. `version-released` starts no rebuild. No other edge rebuilds or delivers anything
again. A dependent's new version that a change orders after its dependency's new version by kind 2, 4 or 6 is wanted
only once that dependency's version is delivered to every machine that places it and is present. The assigner judges
it, since placement is its own state: from its placements, the node-engine seat's `running` events and the
watchman's `machine-away` and `machine-back` of each node (§389), never its state, judged again on each of them and on each placement change, and says
`version-delivered` (the module, the version, its parts' digests and the machines away) on its seat `mesh-wants`
(`mesh.seat.mesh-wants.event.version-delivered`) once every present machine placing it runs it healthy; the catalog
hears it for its entries (§374), and the assigner writes the dependent's wants on it. The dependent's wait is the
assigner's, said `wait-held` with a 60-minute bound from the dependency's `version-released` (*Each step's wait is its
own part's*): past it, the watchman's finding `delivery-waits` names the dependency, the version and the machines not
running it, with each one's node-engine version, so a node-engine from before G that never runs it is named. A
machine away is named and catches up when it is back (ADR 0603 §7); meanwhile the dependent's status, in
`assigner_why_not` and the status view, reads "waiting for delivery of <module>", naming the machines it waits for.
When every machine that places the dependency is away, it is not delivered: its status reads "waiting for <machine>
to be back", its dependents wait, and nothing is raised for a machine simply away, the bound counting only while a
placing machine is present. `assigner_delivered_again`, for the operator and the healers and written in the
hand-act log, says `version-delivered` again for one version when it holds. Kind 5 is held by the acting parts' waits
(*Within a revision the new version acts first*). Why: the operator, 2026-11-10 about 22:30 to 23:00 CET; who judges it, the event's seat, the bound and the tools are
the design's choice, round 2 of the review by architectural domain, 2026-11-10. How it is checked: tests 104 and 106.
- §377 **The first fill.** At the indexer's first start under G, after `versions` is filled (§372), each module's
newest version gets its entries once: kinds 1 and 3 from the dependency version published latest before that
version's build was claimed, read with the times of `builder_versions`, and its part's digest; kinds 2, 4 and 6 from
the versions every machine placing it runs, present or away, read from the last `running` of each module on each node, which the
node-engine seat's stream keeps last per subject with no age limit; kind 5 as done for every acting part's running
build. Each filled entry is marked `filled`, so a person sees it was derived, not recorded. It is not filled again.
Why: so the first `outdated` does not find the whole mesh behind (the lead, 2026-11-10). How it is checked: whole
(indexer), after the first fill `outdated` answers nothing for a mesh whose modules run their newest builds.
- §378 **Each edge kind makes its dependent stale or only orders it.** An edge makes the dependent stale when the
dependent is made from the dependency: kind 1, the dependency goes into it (an imported package), and kind 3, the
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>`
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
the builder's `build.ended`, which carries each artifact's digest and, for a kept build, its keeping node, for a build
of another watched branch and for a kept build (§271); never from the builder's done key (ADR 0596 §2). It writes the
two fields into a node's want only once that node runs G's node-engine, as it holds a node's other wants for its new
node-engine (*Composing waits per node*), so the reader edge holds per node and a node away on an older node-engine
gets its node-engine's want first. Why: the operator, 2026-11-10 about 22:30 to 23:00 CET; the sources and the edge per node are the design's choice, round 2 of the review by architectural domain, 2026-11-10. How it is checked:
whole (assigner, node-engine), a want names the version and the parts' digests taken from those events, a node on an
older node-engine gets no new field, and a fetched part whose digest differs is not applied.
- §380 **`indexer_edges` answers both directions.** For any module or part, per watched branch, `indexer_edges`
answers what it depends on and what depends on it; per edge its kind, stale or order (§378), the manifest word it
came from, the part it points to, and what its newest release recorded (version and digest, or the fact for kinds 5
and 6), and whether it is behind now (§375). Its callers are any person, an agent session and the healers. Why: the operator, 2026-11-10 about 22:30 to 23:00 CET. How it is checked: whole (indexer), a fixture module's
edges are answered from both ends with their newest release's record.
- §381 **A registry holder checks a publish against the reservation.** From G's delivery the holder of a registry
seat asks the seat `mesh-builds`' verb `reserved` (§369), in place of `builder_versions`, whether the module, the
version and the artifact's digest are reserved, and refuses loudly otherwise (ADR 0605 §4); a version the catalog
already holds released for that digest is answered as held and stores nothing. Why: ADR 0605 §4 has the holder
refuse a version the builder did not give, and the builder's list goes (§372); the design's choice, round 2 of the review by architectural domain, 2026-11-10.
The order is a served-verb edge (kind 6) the delivery plan derives from the holder's new `invokes` of
`mesh-builds.reserved`: the builder runs its new version, which serves `reserved`, before each registry holder's new
version is delivered; until then the old holder asks `builder_versions`, which answers reserved versions (§372). How
it is checked: whole (registry holder, builder), a publish of an unreserved version is refused, naming it; whole
(builder, registry holder on its version before G), a publish made between the builder's delivery and the holder's
is stored; the delivery plan of G names the edge from the builder to each registry holder.
- §382 **An older version released after a newer starts nothing.** When every artifact of a version lower than the
module's highest released is published late (a retry of an older reservation), the catalog records it `released
late`, says no `version-released` for it, and the runner starts no rebuild on its `package-published` (§53). Why:
a rebuild against an older part would move a dependent back; the design's choice, round 2 of the review by architectural domain, 2026-11-10. How it is checked: test 102.
@@ -0,0 +1,167 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0636"
- "ADR 0603"
- "ADR 0596"
- "ADR 0595"
- "ADR 0564"
- "ADR 0363"
- "ADR 0289"
- "ADR 0253"
---
# 47, milestone G: the operator's answers of 2026-11-11, findings heard as events
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n". The lines
§336, §355 and §365, which these answers also change, stay in [G-7-answers](G-7-answers.md).
## The operator's answers to milestone G's design questions (2026-11-11)
*ADR 0596 §1, §2, §4, ADR 0564 §2, §4, §12, §13, ADR 0668 §1, §3, ADR 0636 §1, ADR 0666 §16, ADR 0667 §4, ADR 0363
§1, §2, §5. The operator, 2026-11-11, through lead-47: the condition keeper reads no other module's state and hears
each module's findings as events; a module states its findings from its own truth, NATS-native; the shared
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
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
next statement of §384 covers a lost one), published on `mesh.stream.<module>.event.<event>.<node>`, the node it runs
on in the subject (ADR 0363 §2; §260). `finding-written` is said when a finding is raised or what it says changes,
and only then (ADR 0289 §1, §4); `finding-cleared` when the observer no longer observes it. `finding-written`
carries `scope`, `id` and `kind` (the key `<scope>.<id>.<kind>` of ADR 0564 §2), `severity`, `subject`, `evidence`,
ADR 0253's words (`headline`, `explanation`), `since` and `at`; `finding-cleared` carries `scope`, `id`, `kind`, `at`
and `why`. The observer and the node are the subject's alone: the grants part grants each module's account publish
on its own stream's subjects of its own node's segment only, the keeper takes both from the subject, and it refuses
an event whose body names another observer or node, said in its journal. mesh-sdk's finding writer (`go/views`'
`FindingWriter`) says them for every part, after the key it writes in the part's own state `findings`, which stays
the owner's and is read only by the owner's read tool; a publish the bus does not acknowledge is sent again for one
minute, then written in the part's journal as an error naming the finding, and the next statement covers it. The
keeper takes a finding only from a source of its own list (§388) and raises or changes a condition on
`finding-written` and clears it on `finding-cleared`, save while ADR 0564 §13 holds it; a condition's looks are those
of its last event, and the observer's read tool shows its newest. What exists today goes in the same change: every
`findings` entry of the keeper's manifest `reads`, its `consumes` of the assigner's and the indexer's
`views.findings`, and its `invokes` of `assigner_read_findings` and `indexer_read_findings`; the assigner and the
indexer no longer declare `views.findings`, which no reader follows; the read tools stay for persons and sessions.
Why: only its owner reaches a module's state, and a part hears another's events (ADR 0596 §2, §4); this departs from
ADR 0564 §2, where the keeper watches each observer's bucket, by the operator's answer of 2026-11-11. The stream, its
age and cap, the subjects, the fields, the writer and its retry are the design's choice. How it is checked: unit
(mesh-sdk's writer), a finding written and one cleared each say their event with these fields after the key, and an
unacknowledged publish is sent again and then logged; whole (keeper, a fixture observer on an account of its own),
`finding-written` raises a condition and `finding-cleared` clears it, the same event published by another module's
account and by the same module's account on another node's subject are refused by the bus, and a body naming another
observer or node is refused by the keeper; the catalog's validation finds no `findings` in the keeper's `reads`.
- §384 **An observer states every finding it owns at its start and when the keeper asks; the keeper's view is
rebuilt from those events alone.** At each start, and on each ask of the keeper, an observer judges every finding it
owns anew from its own truth, the cause each kind watches (the seat list it holds, a call still unanswered, a wait
still standing), never from what one process remembers, and says `findings-stated` on
`mesh.stream.<module>.event.findings-stated.<node>`, carrying `at`, `asked` (the ask it answers, none at a start),
`findings` (every finding it observes now, with `finding-written`'s fields) and `pending`. A wait's bound counts from
the wait's own start, which the observer keeps in its own state when the wait begins (the `since` of its key in
`findings`, written before the bound passes), never from the process's start, so a wait that began before a restart
is judged against its whole age. `pending` names, with their `since` and their bound, the keys the observer cannot
judge yet since its start (a kept call it has not yet made again, a wait it has not yet looked at). The keeper sets
its view of that observer on that node to that set: it raises what is new, keeps each pending key's condition open
and unchanged, saying nothing for it, and clears each other condition of that observer and node the set does not
name, with the reason `not stated at <observer>'s start` or `not stated when asked`; a pending key is cleared only if
neither a `finding-written` nor a later set names it within its bound after the set's `at`, with the reason `not
judged within its bound`. For an observer whose catalog entry claims only seats held once for the mesh, the set
replaces its findings of every node, and the keeper takes `finding-written` and `finding-cleared` of it only from
the node the seat list names as its holder, and only with an `at` later than that holder's last `findings-stated`, so
a holder that took over leaves no finding of the one before and an old holder's late event changes nothing. A set
larger than one bus message goes in the observer's sent store (§59). The node-engine's `seat-list-unread` (ADR 0636
§1) is stated so: a node-engine started with the full list states no `seat-list-unread`, and the keeper clears it at
that start. Why: the node-engine's seat-list waiter kept only in its process that it had written its finding, so a
node-engine restarted with the full list never cleared it, and two machines said they waited for the seat list for a
day while both held it (issue 1966); this departs from ADR 0564 §2 by the operator's answer of 2026-11-11: the fix is
NATS-native, from each observer's own truth. The event, its fields, `pending`, the kept start, the reasons and the
rule for an observer held once are the design's choice. How it is checked: whole (the node-engine, and each observer
through mesh-sdk's writer), a module restarted after raising a finding whose cause has gone clears it at its start,
the node-engine's `seat-list-unread` among them, and an observer restarted with a call unanswered for two hours says
neither `condition-cleared` nor `condition-raised`; whole (keeper), a condition the set does not name is cleared with
its reason, a pending key past its bound is cleared, and in a take-over the new holder's set clears the old holder's
findings and an old holder's later `finding-written` raises nothing; unit (mesh-sdk's writer), a start states the
findings observed now and pends the rest. Test 107.
- §385 **One tool-runner account for every module of a machine stays as it is.** On a machine where one tool runner
serves every module's tools on one bus account, any tools bundle there may read any sent store (§59) that account is
granted, call `merge-pull` or the runner's `run` as a session, and say the finding events of §383 of any module that
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
`*.<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
event that a seat's definition or a contract in mesh-sdk defines, with its fields (for the finding events, mesh-sdk's
finding writer); it refuses `*` before any other event, `*.>`, and `*` in part of an emitter's name. The grants part,
from the manifests as it derives every grant (§59), makes on each declaring module's stream one durable consumer
for the consumer, named `<machine>_<module>` as ADR 0363 §5 names one, with its grants to read and acknowledge it and
its membership's word to bind it; it drops that consumer when the module no longer declares the event, and makes one
for a module that declares it later, with no change to the consumer's manifest. A `*` entry never reaches a
restricted stream (one with a `grant`, ADR 0363 §4). Why: the keeper hears modules it finds in the catalog and its
manifest names none (§7, §365); this departs from ADR 0363 §5, where an entry with a wildcard emitter never reaches
a module's stream, by the operator's answer of 2026-11-11; the validator's rule and the per-module consumer are the
design's choice. How it is checked: unit (manifest validator), `*.finding-written` accepted, `*.>`,
`*x.finding-written` and `*` before an event no contract defines refused; the grants part's table test makes a
`*.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
`<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`,
`minio` and `nfs-server` in mesh-catalog; `mesh-delivery-assigner`, `mesh-delivery-builder`, `mesh-delivery-checker` (until its
removal, ADR 0667 §12), `mesh-delivery-indexer`, `mesh-delivery-teller`, `mesh-delivery-view`, `mesh-delivery-watchman` and
`mesh-enrolment` in mesh-delivery; `mesh-host`, the node-engine; `node-tools`, the tool runner, in mesh-tools; and the modules that gain
findings in G, the runner (`mesh-delivery-pipeline`, §355) and the forge bridge (`mesh-delivery-forge-bridge`, for `branch-unprotected`, §17). The manifest
validator refuses a manifest declaring the state `findings` without that stream and that `consumes`. Why: an
observer whose stream is missing says nothing the keeper hears; the list is read from main on 2026-11-11 and the rule
is the design's choice. How it is checked: unit (manifest validator), a manifest with `findings` and no finding
stream refused; the catalog's validation at G's heads refuses none of these manifests.
- §388 **The keeper keeps its sources in its own state, and raises its own findings itself.** The keeper keeps its
list of sources (each module whose catalog entry declares the finding stream, with the nodes it heard and each
observer's last stated set and open asks) in its own state `findings-views`, in the form of its `conditions`, one key
per observer and node, deleted when the module leaves the catalog (§365) or the node (§365). At its start it applies
the events of those sources at once from its durable consumers, before its first view of the catalog half; the view
only adds and removes sources. A source new to the list is asked with `findings-asked` naming it once its durable
consumer exists (§386). The keeper's own findings (`view-waits`, `findings-unstated`, `call-unanswered`) it raises and
clears directly as the one writer of conditions, never through its own events. A missing assignment half holds only
the clears of `module left the node`. Why: a keeper that waits for its view would hear nothing after a restart while
the catalog is down; the state and its key are the design's choice. How it is checked: whole (keeper), started with
the catalog's holder stopped it raises and clears from its kept sources' events, raises `view-waits` after five
minutes itself, and clears nothing with `module left the node` until the assignment half answers.
- §389 **The watchman says a machine's presence as events.** The seat `mesh-watchdogs` gains the events `machine-away`
and `machine-back` (`mesh.seat.mesh-watchdogs.event.machine-away.<node>`, `.machine-back.<node>`, carrying the node,
`at` and since when it was last heard), said by the watchman when its presence key of a machine expires and when the
machine is heard again (ADR 0564 §4), declared in the seat's definition and kept 90 days, 16 MiB. The keeper
consumes them for `findings-unstated` (no machine away is named, and an ask is said again to a module of a machine
back), and the assigner for its judgement of `version-delivered` (§376); neither reads the watchman's state. Why:
ADR 0596 §2; the events and their fields are the design's choice. How it is checked: whole (watchman), a machine
whose presence expires says `machine-away` and one heard again `machine-back`; whole (keeper), an observer on a
machine away is not named in `findings-unstated`.
- §390 **The keeper's ask, its tools, and its hold.** The keeper asks with its seat's event `findings-asked`
(`mesh.seat.mesh-conditions.event.findings-asked`, carrying `ask`, `at`, `by` and, to ask one, `module`), declared
in the seat's definition, kept 7 days and 16 MiB; an observer hears it through mesh-sdk's writer on a consumer that
starts from the newest ask, so an old ask is never answered again. The keeper asks after its start (§388) and on
`resync` (ADR 0564 §12), which gains an optional `module` and so reads no state; its callers are the operator, the
healers and agent sessions, and each call is written in the hand-act log of the seat `mesh-hand-acts`. An observer
that states nothing within five minutes of an ask, its machine not away (§389), is the keeper's finding
`findings-unstated`, naming the module and the node, cleared by its next `findings-stated`; its conditions stand
meanwhile. `findings-unstated` is held under ADR 0564 §13's hold while its module is being delivered, and G's
delivery plan says it may show while the observers move. The keeper's tool `conditions_findings_views` answers each
observer and node of `findings-views`: its last stated set with its `at`, its pending keys and the open asks; its
callers are any person, agent sessions and the healers. Why: ADR 0564 §12 has every act of the keeper started by
hand, and the tools-first rule a read of all it keeps; the names, the callers and the bound are the design's choice.
How it is checked: whole (keeper), `findings-unstated` is cleared by a `resync` naming that one module, the call is
in the hand-act log, an observer restarted after an old ask answers only a new one, and the tool answers a fixture
observer's set.
+87
View File
@@ -0,0 +1,87 @@
---
layer: to-be
status: in-progress
design: 47
milestone: G
updated: 2026-11-11
decisions:
- "ADR 0668"
- "ADR 0667"
- "ADR 0666"
- "ADR 0664"
- "ADR 0663"
- "ADR 0662"
- "ADR 0661"
- "ADR 0660"
- "ADR 0659"
- "ADR 0656"
- "ADR 0648"
- "ADR 0646"
- "ADR 0645"
- "ADR 0644"
- "ADR 0641"
- "ADR 0639"
- "ADR 0636"
- "ADR 0635"
- "ADR 0633"
- "ADR 0625"
- "ADR 0621"
- "ADR 0619"
- "ADR 0618"
- "ADR 0615"
- "ADR 0614"
- "ADR 0603"
- "ADR 0597"
- "ADR 0596"
- "ADR 0595"
- "ADR 0593"
- "ADR 0584"
- "ADR 0573"
- "ADR 0571"
- "ADR 0567"
- "ADR 0564"
- "ADR 0563"
- "ADR 0551"
- "ADR 0548"
- "ADR 0539"
- "ADR 0534"
- "ADR 0525"
- "ADR 0505"
- "ADR 0498"
- "ADR 0493"
- "ADR 0448"
- "ADR 0443"
- "ADR 0441"
- "ADR 0437"
- "ADR 0429"
- "ADR 0421"
- "ADR 0404"
- "ADR 0398"
- "ADR 0391"
- "ADR 0363"
- "ADR 0289"
- "ADR 0283"
- "ADR 0253"
- "ADR 0240"
- "ADR 0235"
- "ADR 0233"
---
# 47, milestone G — every line milestone G builds
This is milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md): the pipelines per module of
research 086 and the two graphs of research 085, one milestone (ADR 0667 §1). The main file keeps the purpose, the
whole shape, the acceptance tests and the milestones; every line milestone G builds is in this part's files, moved whole from the main
file on 2026-11-10, and is cited as "design 47 G §n". A sentence that holds a delivered line and G's change together
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 eight 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.
- [G-3-delivery-record-and-graphs](G-3-delivery-record-and-graphs.md): the delivery record, what the gate owes the forge, the state table, the two graphs and a change's graph difference.
- [G-4-revision-view-and-steps](G-4-revision-view-and-steps.md): the revision view, what ADR 0656 leaves to this design, the choices under ADR 0656, what each step says and their tools.
- [G-5-acting-parts-check-and-kept-data](G-5-acting-parts-check-and-kept-data.md): the acting parts, the removed hold, the check on the change's own acting parts, after the merge, taken in, and kept data on the bus.
- [G-6-verbs-and-retires](G-6-verbs-and-retires.md): the verbs G adds and what retires.
- [G-7-answers](G-7-answers.md): the answers to the builders', the reviewers' and the operator's questions (§322 to §382).
- [G-8-findings-heard-as-events](G-8-findings-heard-as-events.md): the operator's answers of 2026-11-11, findings heard as events (§383 on).
@@ -6,12 +6,12 @@ code:
- mesh-catalog modules/artifact-store-tools (the store's tools), modules/distribution (its program, holding the two image seats)
- mesh-catalog modules/gitea (package retention; the two package seats' verbs in its Go bundle)
- mesh-catalog modules/mesh-build-go, modules/mesh-build-node, modules/mesh-build-image (publishing through the seats)
- mesh-delivery modules/mesh-delivery-builder (versions, tag asks; from milestone G a package numbered at its publication, the seat `mesh-builds`), the forge bridge, the indexer, the checker, the assigner, modules/mesh-delivery-pipeline (`tag-wanted` from milestone G)
- mesh-delivery modules/mesh-delivery-builder (versions, tag asks; from milestone G a module's version reserved at its build's start and released by the catalog, the seat `mesh-builds`), the forge bridge, the indexer, the checker, the assigner, modules/mesh-delivery-pipeline (no tagged version from milestone G)
- mesh-sdk (the bus helper for sending in parts)
- mesh-catalog modules/docker (image pruning)
- mesh-host internal/apply (every new image of a module pulled before any of its containers is replaced; a failed pull applied again on the status view's `settled` for the store's holder, and at the node-engine's start), internal/compose (each routed image reference written with its seat and that seat's holder)
- mesh-delivery modules/mesh-grants (what a node-engine hears: the status view's `settled`)
updated: 2026-11-10
updated: 2026-11-11
decisions:
- "ADR 0667"
- "ADR 0666"
@@ -130,7 +130,8 @@ the verb.
▼
registry seat's holder ── answers the parts it lacks
│ the build module sends those parts, in chunks, by `publish`
holder: hashes what it received, asks `builder_versions`,
holder: hashes what it received, asks `builder_versions`
(from milestone G of design 47: `mesh-builds`' `reserved`),
stores in its own store, emits `published` or `refused`
▼
builder (durable consumer: entry, done key), indexer, status view, checker
@@ -153,7 +154,9 @@ the verb.
match its digest. At 80 % of the cap it writes a finding (cleared below 80 %); at the cap it refuses a new transfer
with `refused` naming the cap. Its tool `registry_incoming` lists open transfers and the fill, and drops one by hand.
- **The holder asks the builder**, through `builder_versions`, whether the name, version and digest are recorded, and
refuses loudly otherwise (ADR 0605 §4). A rebuild of a held fingerprint answers the held version and stores nothing.
refuses loudly otherwise (ADR 0605 §4). A rebuild of a held fingerprint answers the held version and stores nothing. From the delivery of
design 47's milestone G the holder asks the seat `mesh-builds`' verb `reserved` instead, whether the module, the
reserved version and the artifact's digest are reserved, and refuses loudly otherwise (design 47 G §381).
- **How a publish ends**: the holder emits `published` or `refused`. The build module waits for one of them, matching
on its transfer, name and version, at most
10 minutes, narrating "waiting on <seat>" at least every five minutes; past the bound it writes the key refused,
@@ -266,24 +269,30 @@ the verb.
### The versions
- **From the delivery of design 47's milestone G, a package is numbered when it is published** (ADR 0666 §15). The
builder numbers a package's version at its publication, never only when its build is claimed: a package's trunk
build is claimed with no number and built by its digest, and once its build is done the builder gives it the next
count of its series, under compare-and-set, and says `numbered`, on which the build module calls `publish` with
the version and the held digest, as below for a floor not yet known. A build of an older source revision of the
same watched branch than one already numbered for that name is not numbered and publishes nothing, so a version never
names older content than the one below it. When the holder says `published`, the builder says the publication as the
event `package-published` of its seat `mesh-builds` (the name, the version, the fingerprint, the commit, the watched
branch). On it every artifact with a "published before it builds" edge to the package is rebuilt against the new
version, and the pipeline runner, which consumes the event, starts its pipeline (ADR 0666 §15; design 47, *A publish
rebuilds*);
nothing else is rebuilt, and a change to a package's tests alone publishes nothing. This removes the claim's wait
for earlier merges' numbers below, which held a group on 2026-11-09 (research 086, Findings). Why at publication: a
number given at the claim must wait for every earlier merge's number, and a build that never publishes keeps a
number nothing holds. An image, bundle or archive keeps the rule below. How it is checked: in the builder's
whole-module test a package's trunk build is numbered only once built, an older revision's build after a newer one
publishes nothing, `package-published` is said once per publication, and only the importing artifacts rebuild
(design 47, test 80).
- **From the delivery of design 47's milestone G, a module's version is reserved at its build's start and released
when every artifact is published** (the operator, 2026-11-10 about 21:40 CET; design 47 G §52, §348, §349, §368 to
§371). A module's manifest says its `version`, `MAJOR.MINOR`, and no custom version (`beta`, `alpha`) exists for now
(G §368). The patch is the next after the highest version the catalog holds released for that `MAJOR.MINOR`, 0 for
one it holds none of; the builder reserves it when the module's build starts, in its key `reserved` for the module
and `MAJOR.MINOR` (G §369), and passes it to the build of every artifact as a build argument, so code and packed
files carry it (G §370): a Go package is tagged `vMAJOR.MINOR.PATCH`, in git under its module directory's prefix
(`go/vMAJOR.MINOR.PATCH`), which departs from ADR 0573 §2 (a Go package's version is its trunk commit) by the
operator's choice, a later research bringing the record in line. Each artifact published is said as
`package-published` of the seat `mesh-builds` (ADR 0666 §15), on which every artifact with a "published before it
builds" edge to it is rebuilt against the new version and its pipeline started (design 47, *A publish rebuilds*);
nothing else is rebuilt, and a change to a package's tests alone publishes nothing. The version is released only
once every artifact of the module is published at it: the catalog, the seat `mesh-catalog`, then holds it and says
`version-released`, and its `versions` is the one list of released versions, which numbering alone reads (G §348,
§349). A failed publish leaves the version reserved, and `builder_publish_again` publishes the missing artifacts
under the same version. Only a build of the registration's first watched branch reserves and releases a version; a
build of another watched branch is delivered by its build, with none (G §371). The builder no longer numbers a
build at its claim or at its publication, says no `numbered` or `version.numbered` for a module's versions, and
keeps no list of them of its own: the bullets below on the claim's numbering, the numbering per source revision, the
builder's state `versions`, the floor and the event `numbered` hold until that delivery only. Numbering continues above what is published today, the catalog filled once from it (G §372). Why reserved before the build: a Go module zip and a TypeScript package carry their
version inside the file, so a number given at publication could not be valid for a digest built before it; this
departs from ADR 0666 §15's numbering at publication by the operator's choice. How it is checked: design 47's test
102, and in the builder's whole-module test a version is reserved at the build's start, released once both of a
module's artifacts are published, and published again under the same version after a failed publish.
- **A build that cannot read the published packages refuses**, naming the unread state, never building against a guess
(ADR 0666 §15; design 47, test 70).
- **Until that delivery, the builder numbers every trunk build** of a package, image, bundle or archive at its claim: the package series
@@ -339,13 +348,17 @@ the verb.
### Tagged versions
- **From the delivery of design 47's milestone G there are no tagged versions** (the operator, 2026-11-10 about 21:40
CET: no custom versions for now; design 47 G §373). A group's run on request builds its members against each
package member's build by its digest and publishes no version; the tagged versions, the events `tag-wanted` (the
checker's and any runner's), the builder's state `tag-asks` and its tools `builder_tag`, `builder_tag_asks` and
`builder_untag` go, and nothing is numbered below a next version. The bullets below hold until that delivery.
Tagged versions already published stay until a branch's end removes them as below, and nothing new is tagged. How
it is checked: design 47's test 103.
- **Asked by an event, never by a push** (ADR 0610 §1). The checker emits `tag-wanted` (repository, branch, head,
tag), on a family of its own, `checker.asks`, kept seven days, apart from its verdicts, so a waiter on a verdict does
not wake on it, for each head of a delivery group member that declares a package; a person asks through `builder_tag`.
From the delivery of design 47's milestone G the checker goes, and the pipeline runner says `tag-wanted` instead, on
its own seat `mesh-pipeline-runner`, for each member that declares a package of a run on request of a group not yet
merged (ADR 0666 §18): a group merged together is one source revision whose packages are published before its
dependents build, so only a run on request needs a tagged version. The
Until the delivery of design 47's milestone G (below). The
builder consumes the event on a durable consumer and keeps the ask in its own state `tag-asks`, removed when its
branch is merged or deleted, or its pull request closed. An ask the builder has not answered within five minutes is narrated in the build log
with what it waits on.
@@ -353,9 +366,8 @@ the verb.
and Go's `@latest` skips it. A package new in a group is numbered from its floor (`0.1.0-feat-x.1`).
- **A group's check builds after `published`**: Go members by the tagged version, TypeScript members with their lock
naming it and the registry's integrity; nothing is packed for a check, and a lone pull request's check publishes
nothing. No `published` within the publish's bound (10 minutes) fails the check naming the package. From the
delivery of design 47's milestone G the same holds for a group's run on request, whose pipeline steps build after
`published`, and a step that gets no `published` within the bound ends failed, naming the package.
nothing. No `published` within the publish's bound (10 minutes) fails the check naming the package. Until the
delivery of design 47's milestone G (below).
- **Removed with its branch**: on the forge bridge's `merged` event, its pull `closed`, or its branch-deleted event,
heard on a durable consumer, the builder
calls each seat's `remove`. It acknowledges the event only once every seat emitted `removed`; until then it retries