Design 47 follows ADR 0637 and ADR 0633: a group try, and a revision taken in by its own indexer

A try now checks a delivery group at exact commits with the check's own code, and a
merge's take-in waits for the indexer its revision names, so design 47 says both where
its checks and its source revision are described. The indexer hears a try on the
checker's events rather than reading its state (ADR 0596); the record changing
ADR 0637 §4 to say so is to follow. The glossary gains try and pending revision.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 18:23:09 +01:00
parent 674f88f9ab
commit 7e33e9a15f
2 changed files with 142 additions and 0 deletions
+9
View File
@@ -543,6 +543,11 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
move changed, keeping the composition while no composed fingerprint changed; the verdict names the commits it
was carried from (ADR 0548 §5, §6). A head git refuses onto the new commit is not carried: its next check is
whole (ADR 0548 §7).
- **try** — the checker's run of a delivery group's check on one or more members, each a repository at an exact
commit, before or without a pull request: the same examiner as the group's check, stricter in that a missing
candidate or a node's missing composition answer ends it as error, and owing the forge no status and no Approve.
It keeps the verdicts of the packages it passed for the check to take (ADR 0637). Asked only when the operator
asks for one (ADR 0637 §6).
- **trunk commit** — the commit of the trunk a merge check's verdict was judged against: the trunk's head when
the build module cloned it. The verdict holds for that commit only, goes out of date when the trunk moves, and
is then carried to the new commit (ADR 0323, ADR 0548).
@@ -703,6 +708,10 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
merged head and the group that moved it. A group reaches the mesh as one source revision (ADR 0429 §11); a
source revision is **delivered** when every want equals its module's fingerprint and every have equals its
want and is healthy, derived from the keys and recorded by no part (§1).
- **pending revision** — a source revision the running indexer has not taken in, because it names another build of
the indexer or follows one that does: only the named indexer's own keys are written, the rest kept in the
indexer's state `pending` until that indexer starts and takes it in (ADR 0633 §1 to §3). A group's members wait
there too until the group's end, as members still gathered (§4).
- **canary** — the first node of a module's nodes, ordered by ring then name: a new fingerprint reaches it
first and the module's other nodes only once it runs healthy there (ADR 0429 §6). Replaces sending to every
machine at once from release 6 (§15); a fingerprint that fails there is held and the canary set back, or for
@@ -4,10 +4,18 @@ status: in-progress
code: [mesh-controller, mesh-catalog modules/mesh-delivery-gate, mesh-delivery, mesh-host, mesh-sdk, mesh-vault, hq 00-META/checks]
updated: 2026-11-05
decisions:
- "ADR 0637"
- "ADR 0636"
- "ADR 0635"
- "ADR 0634"
- "ADR 0633"
- "ADR 0632"
- "ADR 0614"
- "ADR 0603"
- "ADR 0601"
- "ADR 0596"
- "ADR 0594"
- "ADR 0593"
- "ADR 0572"
- "ADR 0571"
- "ADR 0570"
@@ -60,6 +68,7 @@ decisions:
- "ADR 0446"
- "ADR 0445"
- "ADR 0444"
- "ADR 0441"
- "ADR 0437"
- "ADR 0435"
- "ADR 0434"
@@ -602,6 +611,55 @@ From its delivery, a check is this:
The assigner writes the verdict under the checker's hash of the heads it judged, in its own bucket, and the checker reads it into the
composition of its verdict, which says "not run" until the assigner's module exists (ADR 0528 §5).
### A try: a delivery group's check at exact commits
*ADR 0637; ADR 0593 §7, §9, ADR 0636 §4, ADR 0548 §5, ADR 0596 §1, §2.*
A try says beforehand whether a check will pass. It is the check's own code run on commits nobody pushed to a
pull request, and it owes the forge nothing.
- **A try is a delivery group at exact commits.** The checker's try takes one or more members, each a repository
and the exact commit asked, never a branch's head, with an optional trunk commit. One repository at one commit
is a try of one member, and the same members in any order are one try (ADR 0637 §1). Its steps are check asks
on the build seats' streams, as above.
- **A whole try runs the group's check.** It builds the case a group's pull-request check builds and runs the same
examiner: each member merged onto its trunk, the unit and integration levels on the build modules, the manifest
validator with the seat definitions of the members' commits (ADR 0636 §4), the module graph, and the
composition on every placed node (ADR 0593 §7, §9). It names every failure. A try narrowed to some tests or one
level runs only those, validates no manifest and no composition, and says so (ADR 0637 §2).
- **A try is stricter than the check, on purpose.** When no candidate exists for its members, or a node gave no
composition answer, the try ends as error, never as pass, naming what it could not validate. A check passes such
a node with a warning (ADR 0593 §9); a try cannot, because it would then not say whether the check will pass. A
try that ended as error keeps no verdict (ADR 0637 §3).
- **The indexer makes a try's candidate, on the checker's events.** The checker emits the checker's try events: one
when a whole try starts, carrying its members at their commits, and one when it ends. The indexer, the holder of
the seat `mesh-catalog`, consumes them and writes `candidates.<try id>` with one pull per member, its modules and
fingerprints from the previews, validated with the seat definitions the members carry, and deletes it at the
try's end. It reads no state of the checker's; at its start it recovers the tries still open through the
checker's tool that reads a try (ADR 0596 §1, §2, which a record changing ADR 0637 §4 is to carry). The
indexer's tools read and drop a try's candidate. The candidate is the try's alone: the check, the gate and the
forge bridge leave it alone, and the forge is owed no status and no Approve (ADR 0637 §4).
- **A try and a check share kept verdicts.** The try computes each package's digest as the check does and keeps
the verdict of each package it passed under that digest (ADR 0548 §5, ADR 0536 §1). The pull-request check of a
tried head runs only the packages with no kept verdict for their digest, those a moved trunk or a new head
changed, so a group tried before its push costs about one check's worth of build capacity (ADR 0637 §5, ADR 0482
§1, ADR 0548 §1).
- **No try in the normal flow.** A session asks no try; it asks a group try only when the operator asks for one,
for instance to see a group's composition before its push. The review reports a try asked without the
operator's ask (ADR 0637 §6).
- **Asked through the seat verb from the registry milestone.** The seat verb `try` takes `members` once its seat
definition in mesh-delivery-contracts gives them, at the registry milestone (issue 1966); until then a group try
is asked through the checker's own tool `checker_try`. The names a try shares between the checker and the
indexer, its group name and the two reasons a candidate waits, are defined once in mesh-delivery-sdk from that
milestone, and no copy stays in either module (ADR 0637 §7, §8).
- **How it is checked.** The checker's try test asks two members at exact commits and the same group in any
order, refuses five malformed asks, ends as error without a candidate, keeps no verdict from an error, and fails
naming the node on a refused composition; the examiner's table validates a try's manifests as the check does;
the whole-module test starts a placed engine and a candidate and shows that the check of a tried head runs no
package with a kept verdict; the indexer's take-in table writes and deletes a try's candidate. The registry
group's review finds no copy of the shared names. Live, the registry group is tried whole before its push, and
the checker's try read names each member, the composition per node and every failure (ADR 0637 §9).
## The Approve and the merge
*ADR 0421, ADR 0398 §3, ADR 0406 §1.*
@@ -646,6 +704,8 @@ From its delivery, a check is this:
revision of `source.main` in one write (ADR 0429 §11). There is no merge window, no batch and no plan: the
indexer writes each module's fingerprint at the merge, the builder claims each new fingerprint once, and the
assigner writes each want when its six rules hold. A merge is never refused, held or dropped once it is made.
What of it is taken in waits only for the indexer its source revision names, or for the rest of its group
(*Taken in by the indexer it names*, below; ADR 0633).
- **Two merges close together are two revisions.** The second's fingerprints replace the first's in the
indexer's modules; a node gets the newer fingerprint's want once its rules hold, and a fingerprint nobody wants
any more is never applied. Nothing waits for a window to close.
@@ -665,6 +725,79 @@ From its delivery, a check is this:
the move; its tools `assigner_wants`, `assigner_want`, `assigner_differences` and `node_haves` read the same
keys, and its events `delivered`, `moving`, `settled` and `stuck` say them on the bus.
### Taken in by the indexer it names
*ADR 0633 with its progressive insights of 2026-11-05, which changes ADR 0603 §1, §4, §6 and §9; ADR 0636 §4,
ADR 0614 §8, ADR 0601 §3, ADR 0632 §2, ADR 0441 §3, §4.*
A change that brings a new manifest validator is judged by that validator: nothing else of it reaches the
machines until the indexer it builds runs, and the merges behind it then move on the bus's own event, never on a
timer. A group's merges arrive together.
- **A source revision names its indexer.** The indexer is the module that claims the seat `mesh-catalog`; a source
revision names the build of it whose fingerprint its module has once the revision is applied. The running
indexer learns its own build from the node-engine seat's verb `composition`, and takes a revision in whole only
when the revision names that build. When it cannot learn its own build, it leaves the merged message
unacknowledged, so the bus delivers it again, and says so in its journal (ADR 0633 §1).
- **A revision that names another indexer moves only that indexer.** The running indexer writes only what
building and moving the named indexer needs: the indexer's own module entry, its manifest record, its verdict
(ADR 0614 §8), its key of the module graph and the packages the revision publishes. It keeps the rest in its own
state `pending`, one key per merge carrying the merged message whole, written by compare-and-set, and acknowledges
the merged message only once that key is written. Nothing else of the revision reaches the module, verdict,
graph, taken, predefined, candidate or config-candidate state, so no node-engine, assigner, grants part or builder
reads it (ADR 0633 §1, which changes ADR 0603 §4).
- **Validated with the seat definitions its merges carry.** The running indexer validates the revision's
manifests, the named indexer's own among them, with the seat definitions read from the merges' trees, never a
built-in seat list. When it refuses the named indexer's manifest, the revision stays pending, and the event
`take-in.pending` and the tool `indexer_pending` say so in their field `refused` (ADR 0633 §1, its insight of
2026-11-05, ADR 0636 §4).
- **The named indexer takes it in at its start.** An indexer that starts as the build a pending revision names
takes the pending revisions in, oldest first, as one take-in judged by its own manifest validator, before it
starts its consumer on the forge bridge's stream; then it removes them from `pending`. It takes in only whole
revisions, and its validation and holds act on them as on any merge (ADR 0633 §2, ADR 0601 §3, ADR 0632 §2).
- **A merge behind a pending revision waits too, and the newest governs.** While a revision is pending, every later
merge joins the pending revisions in merge order, unrelated modules included, because its trees build on theirs;
which indexer they name is worked out over all of them. A later merge that brings the indexer's fingerprint back
to the running build, as a revert does, lets the running indexer take them all in; a pending revision that names
yet another build is taken in by the next indexer only as far as the first bullet allows (ADR 0633 §3, which
changes ADR 0603 §1 and §9).
- **A group's merges are taken in together.** The forge bridge sets the group on each member's merged message,
from the gate's `merges` state, and once every approved member is merged it publishes `forge.group.merged` on
its own stream `forge`, after the last member's merged message, naming the members and their merge commits. The
indexer hears both through its one consumer, keeps each member's merged message in `pending` until the group's
end, and then takes the members in as one source revision under the first bullet. A refused member is no
group's end: the group stays gathered until it merges on, said by the wait below and the urgent condition of a
held group (ADR 0441 §3, §4); the gate passes the group's end by without counting it a refusal. A later merge in
a member's repository waits behind the group; a merge in another repository is taken in alone, unless a revision
is pending for its indexer (ADR 0633 §4, which builds ADR 0429 §11 for take-in).
- **A wait is bounded, said and recoverable.** The indexer's tool `indexer_pending` reads the pending revisions and
the members still gathered, each with its merges, the indexer build it waits for, since when and `refused`; the
view names a module waiting for its revision's indexer through that tool (ADR 0633 §5, which changes ADR 0603
§6). The indexer emits `take-in.pending` when a revision becomes pending or a group's first member is held, and
`take-in.taken` once it is taken in. The watchman raises the condition `take-in-waits` when no `take-in.taken`
follows within 60 minutes for a revision or 30 minutes for a group, clears it on `take-in.taken`, and at its
start seeds its open waits from `indexer_pending`; the bounds only raise the condition, and nothing is taken in
on a timer. The hand acts, each said in the journal and the audit, are the indexer's `indexer_take_in_pending`
(take the pending revisions, or a group gathered so far, in with the running indexer, when the named indexer can
never run or the group's end never comes) and `indexer_drop_pending` (drop a pending merge a later one replaced),
and the forge bridge's `bridge_groups` (read the groups it holds open) and `bridge_group_end_again` (publish a
group's end again from the gate's `merges` state) (ADR 0633 §5).
- **The check refuses an indexer the running parts could not move.** For a change to the indexer's own module, the
check validates the new indexer's manifest, found as the manifest that claims the seat `mesh-catalog`, with the
trunk's manifest validator in its sandbox and the seat definitions of the members' heads, and refuses the change
when that validator would refuse or hold it: such an indexer would never run, and every revision behind it would
wait for ever (ADR 0633 §6, ADR 0636 §4).
- **How it is checked.** One integration test starts the indexer whole against the bus: a group's two merged
messages and its end are taken in as one revision, nothing written before the end; a revision changing the
indexer writes only the indexer's own keys and shows in `indexer_pending`; restarted as the named build, the
indexer takes every pending revision in, oldest first, with no timer, and a group still gathered stays pending.
Unit tables cover a later merge joining, a revert, another named build, a refused member, a merge in a member's
and in another repository, an unknown own build, a compare-and-set conflict, both events, the watchman's two
bounds and its seeding, the view, the gate passing the end by, and each hand act; three cases cover the seat
definitions of the revision (accepted without a built-in seat list, a refusal in `refused`, the check accepting
the same candidate). Live, the registry group's merge shows in `indexer_pending` waiting for the new indexer and
is taken in when that indexer starts (ADR 0633, How it is checked).
## A backup before the apply
*ADR 0214 §7, ADR 0233, ADR 0404, ADR 0429 §14, ADR 0539 §16.*