From 7e33e9a15f5bf7ed91d96b0855fee621dfaaba15 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 18:23:09 +0100 Subject: [PATCH 1/6] Design 47 follows ADR 0637 and ADR 0633: a group try, and a revision taken in by its own indexer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- 00-META/glossary.md | 9 ++ .../47-delivery-from-commit-to-delivered.md | 133 ++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/00-META/glossary.md b/00-META/glossary.md index 89186fdd..31c8ffc5 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -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 diff --git a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md index ec98bd03..c08118e2 100644 --- a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md +++ b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md @@ -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.` 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.* -- 2.54.0 From 5688a2efa1fbcd86e477221d47f548a7159ae555 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 18:31:21 +0100 Subject: [PATCH 2/6] Design 47 names the try's events, so the indexer's path to a try is a stream it consumes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checker says a whole try's start and end as try.started and try.ended on its stream checks, and the indexer recovers open tries through checker_tries; the record changing ADR 0637 §4 carries this and is cited until its number is known. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../47-delivery-from-commit-to-delivered.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md index c08118e2..abd25c87 100644 --- a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md +++ b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md @@ -631,14 +631,15 @@ pull request, and it owes the forge nothing. 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.` 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). +- **The indexer makes a try's candidate, on the checker's events.** For a whole try only, the checker emits + `try.started`, carrying the try's id and its members at their commits, and `try.ended`, carrying its id and its + state, on its stream `checks`. The indexer, the holder of the seat `mesh-catalog`, consumes them: on + `try.started` it writes `candidates.` with one pull per member, its modules and fingerprints from the + previews, validated with the seat definitions the members carry, and on `try.ended` it deletes it. It reads no + state of the checker's; at its start it recovers the tries still open through the checker's tool + `checker_tries` (ADR 0596 §1, §2; the record changing ADR 0637 §4). 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 -- 2.54.0 From 26b6dcf4f692ed0a65e13dde84a134d11afa92c0 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 18:32:49 +0100 Subject: [PATCH 3/6] Design 47 cites ADR 0638 and its recovery order, so a try's candidate survives an indexer restart ADR 0638 makes the candidate from the checker's events and decides that the indexer creates its consumer, asks checker_tries and fixes the candidates before it handles an event; the design now says so and how it is checked. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../47-delivery-from-commit-to-delivered.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md index abd25c87..63070bdf 100644 --- a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md +++ b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md @@ -4,6 +4,7 @@ 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 0638" - "ADR 0637" - "ADR 0636" - "ADR 0635" @@ -613,7 +614,7 @@ From its delivery, a check is this: ### 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.* +*ADR 0637, ADR 0638; 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. @@ -636,8 +637,14 @@ pull request, and it owes the forge nothing. state, on its stream `checks`. The indexer, the holder of the seat `mesh-catalog`, consumes them: on `try.started` it writes `candidates.` with one pull per member, its modules and fingerprints from the previews, validated with the seat definitions the members carry, and on `try.ended` it deletes it. It reads no - state of the checker's; at its start it recovers the tries still open through the checker's tool - `checker_tries` (ADR 0596 §1, §2; the record changing ADR 0637 §4). The indexer's tools read and drop a try's + state of the checker's (ADR 0638 §1, §2, which change ADR 0637 §4; ADR 0596 §1, §2). A narrowed try emits + neither event, because it makes no candidate. +- **At its start the indexer recovers the open tries before it handles the events.** It creates its consumer on + the stream `checks` first, then calls the checker's tool `checker_tries`, declared in its `invokes`, which + answers the open tries with their members and whether each is whole. It writes a candidate for each whole open + try that has none, deletes every candidate whose try is not open, and only then handles the events it holds. A + try that starts or ends between the call and the consumer is heard as an event, and each write is by try id, so + a try seen both ways is written once and deleted once (ADR 0638 §3). 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 @@ -657,7 +664,10 @@ pull request, and it owes the forge nothing. 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 + package with a kept verdict; the indexer's take-in table writes a try's candidate on its start, deletes it on its end, and recovers at its + start (an open whole try gets its candidate, a candidate of a try not open goes, a narrowed try gets none, a try + started between the call and the consumer gets one), and the checker's try test shows both events for a whole + try, neither for a narrowed one, and `checker_tries` answering the open tries (ADR 0638 §4). 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). -- 2.54.0 From 8b923e6bff5db70e692d625c06bb70133574d756 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 18:38:05 +0100 Subject: [PATCH 4/6] Design 47 takes in round 1 of the review by architectural domain The try and the take-in described seams, failure paths, live cases and the health chain less exactly than the release builds them: the reads that stay until issue 1966, the bus form of pending and the tryevent contract, the try's tools against the four lines, a checker_tries that does not answer, try events never lost, what a narrowed try keeps, the finding and the condition keeper, and both live cases. Rulings no record decides are marked as the review's. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../47-delivery-from-commit-to-delivered.md | 138 ++++++++++++++---- 1 file changed, 106 insertions(+), 32 deletions(-) diff --git a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md index 63070bdf..9f64da68 100644 --- a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md +++ b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md @@ -11,12 +11,15 @@ decisions: - "ADR 0634" - "ADR 0633" - "ADR 0632" + - "ADR 0615" - "ADR 0614" - "ADR 0603" - "ADR 0601" - "ADR 0596" + - "ADR 0595" - "ADR 0594" - "ADR 0593" + - "ADR 0590" - "ADR 0572" - "ADR 0571" - "ADR 0570" @@ -614,10 +617,12 @@ From its delivery, a check is this: ### A try: a delivery group's check at exact commits -*ADR 0637, ADR 0638; ADR 0593 §7, §9, ADR 0636 §4, ADR 0548 §5, ADR 0596 §1, §2.* +*ADR 0637, ADR 0638; ADR 0593 §7, §9, ADR 0636 §4, ADR 0548 §5, ADR 0596 §1 to §3, ADR 0615; the review of +2026-11-05 where no record decides.* 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. +pull request, and it owes the forge nothing. Where a statement below rests on the review of 2026-11-05 rather than +a record, it says so. - **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 @@ -627,26 +632,46 @@ pull request, and it owes the forge nothing. 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). + level runs only those, validates no manifest and no composition, and says so (ADR 0637 §2). A try of some tests + keeps no verdict; a try of one level keeps only that level's (the review of 2026-11-05). - **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). + composition answer, the try ends as error, never as pass, naming what it could not validate, the node by name. + 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). +- **A whole try that waits for its candidate says why.** Its try read gives the reason it waits, and the wait is + bounded by the step bound (the review of 2026-11-05). - **The indexer makes a try's candidate, on the checker's events.** For a whole try only, the checker emits `try.started`, carrying the try's id and its members at their commits, and `try.ended`, carrying its id and its - state, on its stream `checks`. The indexer, the holder of the seat `mesh-catalog`, consumes them: on + state, on its stream `checks`; a narrowed try emits neither, because it makes no candidate. The events' contract + is the package `tryevent` of mesh-delivery's sdk, read by the checker and the indexer, so no other module + rebuilds for it (ADR 0638 §1). The indexer, the holder of the seat `mesh-catalog`, consumes them: on `try.started` it writes `candidates.` with one pull per member, its modules and fingerprints from the - previews, validated with the seat definitions the members carry, and on `try.ended` it deletes it. It reads no - state of the checker's (ADR 0638 §1, §2, which change ADR 0637 §4; ADR 0596 §1, §2). A narrowed try emits - neither event, because it makes no candidate. + previews, validated with the seat definitions the members carry, and on `try.ended` it deletes it. It no longer + reads the checker's state `tries` (ADR 0638 §2, which changes ADR 0637 §4; ADR 0596 §1, §2). +- **Two reads across the seam stay until issue 1966 moves them.** The indexer reads the checker's previews, and the + checker watches the indexer's `candidates.`. Both are readers of another module's bus state that ADR 0596 + §3, as ADR 0615 changes it, lets stay until their module moves; issue 1966 tracks the move to the owner's tools + or events. The forge bridge's read of the gate's `merges` state (*Taken in by the indexer it names*, below) is + the same kind. +- **A try event is never lost.** The checker publishes each try event with retry until the stream `checks` + acknowledges it, and `try.ended` carries the state on every end, also for a try ended by a checker restart. Only + the checker's account may publish `try.*` on its stream, a row of the manifest test (the review of 2026-11-05). - **At its start the indexer recovers the open tries before it handles the events.** It creates its consumer on the stream `checks` first, then calls the checker's tool `checker_tries`, declared in its `invokes`, which - answers the open tries with their members and whether each is whole. It writes a candidate for each whole open - try that has none, deletes every candidate whose try is not open, and only then handles the events it holds. A - try that starts or ends between the call and the consumer is heard as an event, and each write is by try id, so - a try seen both ways is written once and deleted once (ADR 0638 §3). 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). + answers every open try, never only the newest, with its members and whether it is whole. It writes a candidate + for each whole open try that has none, deletes every candidate whose try is not open, and only then handles the + events it holds. A try that starts or ends between the call and the consumer is heard as an event, and each + write is by try id, so a try seen both ways is written once and deleted once (ADR 0638 §3). 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). +- **When `checker_tries` does not answer, nothing is deleted.** The indexer deletes no candidate, its take-in at + start goes on, and it asks again on the checker's next try event and by its tool `indexer_recover_tries`; the + failure is said in `indexer_try_candidates` until a recovery succeeds. In the release that brings ADR 0638, the + new indexer starts before the new checker, so the old checker does not serve `checker_tries` yet: an ordering + edge of the module graph (a served verb, ADR 0614), and the reason the failure path is needed at once (the + review of 2026-11-05). +- **How long it is kept.** The checker keeps an ended try for 7 days, readable by `checker_try_read` (the review + of 2026-11-05). How long the stream `checks` keeps its events is not decided here (*What is not decided here*). - **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 @@ -660,16 +685,31 @@ pull request, and it owes the forge nothing. 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). +- **The try's tools.** Beside the seat verb `try`, the checker's and the indexer's own tools, against the four + lines a module's tools answer: + + | Line | The checker | The indexer | + |---|---|---| + | read everything it holds | `checker_try_read` (one try: its members, steps, composition per node, failures, why it waits), `checker_tries` (every open try), `checker_kept` (the kept verdicts by digest) | `indexer_try_candidates` (each try's candidate, and a failed recovery) | + | do by hand what it does itself | `checker_try` (ask a try) | `indexer_recover_tries` (recover the open tries now) | + | recover without help | `checker_try_end` (end a try, which emits `try.ended`) | `indexer_drop_try_candidate` (drop a try's candidate), `indexer_recover_tries` | + | one seat, many tools | the seat verb `try` from the registry milestone | the seat `mesh-catalog`'s verbs, unchanged | + - **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 a try's candidate on its start, deletes it on its end, and recovers at its - start (an open whole try gets its candidate, a candidate of a try not open goes, a narrowed try gets none, a try - started between the call and the consumer gets one), and the checker's try test shows both events for a whole - try, neither for a narrowed one, and `checker_tries` answering the open tries (ADR 0638 §4). 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). + package with a kept verdict (ADR 0637 §9). The indexer's take-in table writes a try's candidate on its start, + deletes it on its end, and recovers at its start (an open whole try gets its candidate, a candidate of a try not + open goes, a narrowed try gets none, a try started between the call and the consumer gets one), and the + checker's try test shows both events for a whole try, neither for a narrowed one, and `checker_tries` answering + the open tries; both manifests are accepted by the manifest validator (ADR 0638 §4). The review's rows: a + `checker_tries` that does not answer deletes nothing and is said in `indexer_try_candidates`; a recovery of more + than 64 open tries lists them all; a narrowed try keeps only what its narrowing allows; the manifest test + refuses `try.*` from any account but the checker's. 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); during that try `indexer_try_candidates` reads its + candidate, and after `try.ended` it is gone (ADR 0638). ## The Approve and the merge @@ -739,7 +779,8 @@ pull request, and it owes the forge nothing. ### 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.* +ADR 0614 §8, ADR 0601 §3, ADR 0632 §2, ADR 0441 §3, §4, ADR 0564 §2, ADR 0595 §11, ADR 0596 §3, ADR 0615; the review +of 2026-11-05 where no record decides.* 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 @@ -749,12 +790,15 @@ timer. A group's merges arrive together. 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). + unacknowledged, so the bus delivers it again, and says so in its journal (ADR 0633 §1). It also emits + `take-in.pending` with what waits "own build unknown", so the wait's bound covers it (the review of 2026-11-05). - **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, + the merged message only once that key is written. `pending` is in the old form of bus state, the bucket the + grants part creates, not a template in `requires`; it moves when the indexer next moves its bus state (ADR 0595 + §11 as ADR 0615 changes it). 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 @@ -773,7 +817,8 @@ timer. A group's merges arrive together. 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 + from the gate's `merges` state, a read of another module's bus state that ADR 0596 §3 lets stay until issue 1966 + moves it, 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 @@ -785,14 +830,17 @@ timer. A group's merges arrive together. 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-in.taken` once it is taken in, both ordinary events of the indexer; each carries the wait's key, what + waits (a revision with the indexer build it waits for, or a group with its members so far), since when, and + `refused` (ADR 0633 §1, §5). The watchman writes the finding `take-in-waits` when no `take-in.taken` follows + within 60 minutes for a revision or 30 minutes for a group, and the condition keeper raises the condition from + it and clears it on `take-in.taken` (ADR 0564 §2 as ADR 0590 §2 changes it). At its start the watchman 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). + group's end again from the gate's `merges` state) (ADR 0633 §5). Both of the indexer's hand acts emit + `take-in.taken` for what they took in or dropped, so the condition clears (the review of 2026-11-05). - **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 @@ -807,7 +855,8 @@ timer. A group's merges arrive together. 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). + is taken in when that indexer starts, and no node-engine reads distribution's new entry before the new indexer's + hold: `node_manifest_reads` and `indexer_holds` on every node (ADR 0633, How it is checked). ## A backup before the apply @@ -1464,7 +1513,32 @@ R4d, the three image reworks (route-proxy, nats, automx; n8n left by ADR 0506), ## What is not decided here +- How long the checker's stream `checks` keeps its events (*A try*, above). +- How the old indexer takes in while it is replaced by a new one: written once the review's reading of the + build is in. +- Who is granted `checker_try` and the take-in hand tools: the operator's grants, outside this design. - A web view of deliveries beyond the pull request's comment. - Whether `mesh/delivery-group` is required on the trunks: the operator's setting, through the forge module's protection tool. - How a builder's public SSH key reaches the forge: the forge's and the SSH client's mechanism. + +## Reviewed by architectural domain + +The amendment of 2026-11-05 (ADR 0637, ADR 0638, and ADR 0633 with its insights of 2026-11-05) was reviewed by the +four reviewers by architectural domain side by side (playbook 01), and the lead ruled on every finding in round 1. +Where a ruling decides what no record does, the text says "the review of 2026-11-05". What was found, and what +answers it: + +| Domain | Found | Answered by | +|---|---|---| +| seams | the indexer said to read none of the checker's state, while it reads the checker's previews and the checker watches its candidates; the forge bridge's read of the gate's `merges` unmarked | *A try* (two reads that stay under ADR 0596 §3 until issue 1966); *Taken in by the indexer it names* (the bridge's read marked the same) | +| tools | the try's tools not listed against the four lines; the hand acts not said to clear the condition | *A try* (the table of the checker's and the indexer's tools, `indexer_recover_tries` new); *Taken in* (the hand acts emit `take-in.taken`) | +| bus forms | the form of `pending` and the try events' contract unsaid | *Taken in* (`pending` in the old form the grants part creates); *A try* (the package `tryevent` of mesh-delivery's sdk) | +| running qualities | `checker_tries` not answering; the new indexer before the new checker in this release; a lost try event; what a narrowed try keeps; a recovery cut at 64; how long tries are kept; a try waiting for its candidate | *A try* (nothing deleted, asked again, said in `indexer_try_candidates`, the ordering edge; publish with retry, `try.ended` on every end; the narrowed try's verdicts; every open try listed; 7 days; the reason in the try read, bounded by the step bound) | +| testing | ADR 0633's live case on the node-engines' reads lost; no live case for ADR 0638 | *Taken in* (`node_manifest_reads` and `indexer_holds` on every node); *A try* (the candidate read during the registry group's try, gone after `try.ended`) | +| health and events | an own build unknown left unbounded; who raises `take-in-waits`; what the two take-in events carry | *Taken in* (`take-in.pending` "own build unknown"; the watchman's finding and the condition keeper, ADR 0564 §2; the events' body) | +| security | any account could publish a try event; a node's missing composition answer | *A try* (only the checker's account publishes `try.*`, a manifest-test row; the try ends as error naming the node, keeping no verdict) | +| upgrade | the old indexer's take-in during its replacement | open: *What is not decided here*, written after the developer's reading | + +Left as notes, not in this amendment: the grants of `checker_try` and of the hand tools (the operator's), and the +test budget of the checker and the indexer. -- 2.54.0 From d61e18de41869842419bd6fb55668b489837e1c9 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 18:46:11 +0100 Subject: [PATCH 5/6] Design 47 cites ADR 0639 for the review's rulings, and fixes the indexer's build at its start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rulings of round 1 now rest on a record, clause by clause, instead of on the review; the indexer's own build is set once from the build it was started with, so a process being replaced cannot take in with its old manifest validator (ADR 0639 §7). Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../47-delivery-from-commit-to-delivered.md | 75 +++++++++++-------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md index 9f64da68..b9fa6ffe 100644 --- a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md +++ b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md @@ -4,6 +4,7 @@ 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 0639" - "ADR 0638" - "ADR 0637" - "ADR 0636" @@ -617,12 +618,11 @@ From its delivery, a check is this: ### A try: a delivery group's check at exact commits -*ADR 0637, ADR 0638; ADR 0593 §7, §9, ADR 0636 §4, ADR 0548 §5, ADR 0596 §1 to §3, ADR 0615; the review of -2026-11-05 where no record decides.* +*ADR 0637, ADR 0638; ADR 0593 §7, §9, ADR 0636 §4, ADR 0548 §5, ADR 0596 §1 to §3, ADR 0615, ADR 0639 §1 to +§6, §9.* 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. Where a statement below rests on the review of 2026-11-05 rather than -a record, it says so. +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 @@ -633,13 +633,13 @@ a record, it says so. 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 of some tests - keeps no verdict; a try of one level keeps only that level's (the review of 2026-11-05). + keeps no verdict; a try of one level keeps only that level's (ADR 0639 §4, which changes ADR 0637 §5). - **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, the node by name. 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). - **A whole try that waits for its candidate says why.** Its try read gives the reason it waits, and the wait is - bounded by the step bound (the review of 2026-11-05). + given within the checker's step bound (ADR 0639 §5). - **The indexer makes a try's candidate, on the checker's events.** For a whole try only, the checker emits `try.started`, carrying the try's id and its members at their commits, and `try.ended`, carrying its id and its state, on its stream `checks`; a narrowed try emits neither, because it makes no candidate. The events' contract @@ -654,11 +654,12 @@ a record, it says so. or events. The forge bridge's read of the gate's `merges` state (*Taken in by the indexer it names*, below) is the same kind. - **A try event is never lost.** The checker publishes each try event with retry until the stream `checks` - acknowledges it, and `try.ended` carries the state on every end, also for a try ended by a checker restart. Only - the checker's account may publish `try.*` on its stream, a row of the manifest test (the review of 2026-11-05). + acknowledges it, and `try.ended` carries the state on every end; whether it was sent is kept in the try's own + state, so an end after a checker restart still sends it. Only the checker's account may publish `try.*` on its + stream, a row of the checker's manifest test (ADR 0639 §2, which extends ADR 0638 §1). - **At its start the indexer recovers the open tries before it handles the events.** It creates its consumer on the stream `checks` first, then calls the checker's tool `checker_tries`, declared in its `invokes`, which - answers every open try, never only the newest, with its members and whether it is whole. It writes a candidate + answers every open try, with no bound on the newest, with its members and whether it is whole (ADR 0639 §3). It writes a candidate for each whole open try that has none, deletes every candidate whose try is not open, and only then handles the events it holds. A try that starts or ends between the call and the consumer is heard as an event, and each write is by try id, so a try seen both ways is written once and deleted once (ADR 0638 §3). The candidate is the @@ -666,12 +667,12 @@ a record, it says so. Approve (ADR 0637 §4). - **When `checker_tries` does not answer, nothing is deleted.** The indexer deletes no candidate, its take-in at start goes on, and it asks again on the checker's next try event and by its tool `indexer_recover_tries`; the - failure is said in `indexer_try_candidates` until a recovery succeeds. In the release that brings ADR 0638, the + failure is said in `indexer_try_candidates` (its state `try-recovery`) until a recovery succeeds. In the release that brings ADR 0638, the new indexer starts before the new checker, so the old checker does not serve `checker_tries` yet: an ordering - edge of the module graph (a served verb, ADR 0614), and the reason the failure path is needed at once (the - review of 2026-11-05). -- **How long it is kept.** The checker keeps an ended try for 7 days, readable by `checker_try_read` (the review - of 2026-11-05). How long the stream `checks` keeps its events is not decided here (*What is not decided here*). + edge of the module graph (a served verb, ADR 0614), and the reason the failure path is needed at once (ADR 0639 + §1, which changes ADR 0638 §3). +- **How long it is kept.** The checker keeps an ended try's record for 7 days, readable by `checker_try_read`, and + then removes it, so its state does not grow with every try (ADR 0639 §6). How long the stream `checks` keeps its events is not decided here (*What is not decided here*). - **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 @@ -703,10 +704,12 @@ a record, it says so. deletes it on its end, and recovers at its start (an open whole try gets its candidate, a candidate of a try not open goes, a narrowed try gets none, a try started between the call and the consumer gets one), and the checker's try test shows both events for a whole try, neither for a narrowed one, and `checker_tries` answering - the open tries; both manifests are accepted by the manifest validator (ADR 0638 §4). The review's rows: a - `checker_tries` that does not answer deletes nothing and is said in `indexer_try_candidates`; a recovery of more - than 64 open tries lists them all; a narrowed try keeps only what its narrowing allows; the manifest test - refuses `try.*` from any account but the checker's. The registry group's review finds no copy of the shared + the open tries; both manifests are accepted by the manifest validator (ADR 0638 §4). The indexer's + take-in table also has a recovery that gets no answer (no candidate deleted, the failure in `try-recovery`, the + retry on the next try event and by `indexer_recover_tries`); the checker's try test shows the retry until + acknowledgement, `try.ended` after a restart, the whole list of open tries (more than 64), the verdicts kept by a + try of some tests and of one level, the reason a waiting try gives, and an ended try's record kept 7 days and + removed after; the checker's manifest test shows that only its account publishes `try.*` (ADR 0639 §9). 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); during that try `indexer_try_candidates` reads its candidate, and after `try.ended` it is gone (ADR 0638). @@ -778,9 +781,9 @@ a record, it says so. ### 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, ADR 0564 §2, ADR 0595 §11, ADR 0596 §3, ADR 0615; the review -of 2026-11-05 where no record decides.* +*ADR 0633 with its progressive insights of 2026-11-05, which changes ADR 0603 §1, §4, §6 and §9; ADR 0639 §7, §8, +which change ADR 0633 §1; ADR 0636 §4, ADR 0614 §8, ADR 0601 §3, ADR 0632 §2, ADR 0441 §3, §4, ADR 0564 §2, ADR 0595 +§11, ADR 0596 §3, ADR 0615.* 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 @@ -788,10 +791,17 @@ 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). It also emits - `take-in.pending` with what waits "own build unknown", so the wait's bound covers it (the review of 2026-11-05). + indexer takes a revision in whole only when the revision names its own build (ADR 0633 §1). +- **The indexer fixes its own build at its start.** It sets its own build once, at its start, from the build it + was started with, and never learns it later from the node-engine seat's verb `composition`. A process the + node-engine is replacing therefore keeps its old build to the end and cannot take the new revisions in with its + old manifest validator; they wait for the new process. An indexer that learnt its build lazily and kept it + could, while being replaced, learn the new fingerprint and take in with the old validator (ADR 0639 §7, which + changes ADR 0633 §1). +- **An indexer with no own build says so on the bus.** It leaves the merged message unacknowledged, so the bus + delivers it again, says so in its journal, and sends `take-in.pending` with the reason `own build unknown` for + the revision it holds, so the watchman's bound for `take-in-waits` covers that wait (ADR 0633 §1, ADR 0639 §8, + which changes it). - **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 @@ -839,8 +849,8 @@ timer. A group's merges arrive together. (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). Both of the indexer's hand acts emit - `take-in.taken` for what they took in or dropped, so the condition clears (the review of 2026-11-05). + group's end again from the gate's `merges` state) (ADR 0633 §5). A hand take-in emits `take-in.taken` as any + take-in does, so the condition clears (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 @@ -854,7 +864,8 @@ timer. A group's merges arrive together. 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 + the same candidate); and rows for an own build fixed at start (a composition answer that differs changes + nothing) and for `take-in.pending` with `own build unknown` (ADR 0639 §9). Live, the registry group's merge shows in `indexer_pending` waiting for the new indexer and is taken in when that indexer starts, and no node-engine reads distribution's new entry before the new indexer's hold: `node_manifest_reads` and `indexer_holds` on every node (ADR 0633, How it is checked). @@ -1514,8 +1525,6 @@ R4d, the three image reworks (route-proxy, nats, automx; n8n left by ADR 0506), ## What is not decided here - How long the checker's stream `checks` keeps its events (*A try*, above). -- How the old indexer takes in while it is replaced by a new one: written once the review's reading of the - build is in. - Who is granted `checker_try` and the take-in hand tools: the operator's grants, outside this design. - A web view of deliveries beyond the pull request's comment. - Whether `mesh/delivery-group` is required on the trunks: the operator's setting, through the forge module's @@ -1526,19 +1535,19 @@ R4d, the three image reworks (route-proxy, nats, automx; n8n left by ADR 0506), The amendment of 2026-11-05 (ADR 0637, ADR 0638, and ADR 0633 with its insights of 2026-11-05) was reviewed by the four reviewers by architectural domain side by side (playbook 01), and the lead ruled on every finding in round 1. -Where a ruling decides what no record does, the text says "the review of 2026-11-05". What was found, and what +The rulings that no record decided became ADR 0639, which the text cites clause by clause. What was found, and what answers it: | Domain | Found | Answered by | |---|---|---| | seams | the indexer said to read none of the checker's state, while it reads the checker's previews and the checker watches its candidates; the forge bridge's read of the gate's `merges` unmarked | *A try* (two reads that stay under ADR 0596 §3 until issue 1966); *Taken in by the indexer it names* (the bridge's read marked the same) | -| tools | the try's tools not listed against the four lines; the hand acts not said to clear the condition | *A try* (the table of the checker's and the indexer's tools, `indexer_recover_tries` new); *Taken in* (the hand acts emit `take-in.taken`) | +| tools | the try's tools not listed against the four lines; the hand acts not said to clear the condition | *A try* (the table of the checker's and the indexer's tools, `indexer_recover_tries` new); *Taken in* (a hand take-in emits `take-in.taken`; a hand drop is open) | | bus forms | the form of `pending` and the try events' contract unsaid | *Taken in* (`pending` in the old form the grants part creates); *A try* (the package `tryevent` of mesh-delivery's sdk) | | running qualities | `checker_tries` not answering; the new indexer before the new checker in this release; a lost try event; what a narrowed try keeps; a recovery cut at 64; how long tries are kept; a try waiting for its candidate | *A try* (nothing deleted, asked again, said in `indexer_try_candidates`, the ordering edge; publish with retry, `try.ended` on every end; the narrowed try's verdicts; every open try listed; 7 days; the reason in the try read, bounded by the step bound) | | testing | ADR 0633's live case on the node-engines' reads lost; no live case for ADR 0638 | *Taken in* (`node_manifest_reads` and `indexer_holds` on every node); *A try* (the candidate read during the registry group's try, gone after `try.ended`) | | health and events | an own build unknown left unbounded; who raises `take-in-waits`; what the two take-in events carry | *Taken in* (`take-in.pending` "own build unknown"; the watchman's finding and the condition keeper, ADR 0564 §2; the events' body) | | security | any account could publish a try event; a node's missing composition answer | *A try* (only the checker's account publishes `try.*`, a manifest-test row; the try ends as error naming the node, keeping no verdict) | -| upgrade | the old indexer's take-in during its replacement | open: *What is not decided here*, written after the developer's reading | +| upgrade | the old indexer's take-in during its replacement: a build learnt lazily and kept let a process being replaced take in with its old validator | *Taken in* (the indexer fixes its own build at its start, never later from `composition`) | Left as notes, not in this amendment: the grants of `checker_try` and of the hand tools (the operator's), and the test budget of the checker and the indexer. -- 2.54.0 From b926db338dcfbb4ca1e65980b3590c95b7b1079f Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 20:26:55 +0100 Subject: [PATCH 6/6] =?UTF-8?q?Design=2047=20cites=20ADR=200639=20=C2=A79?= =?UTF-8?q?=20for=20the=20hand=20drop,=20so=20both=20hand=20acts=20are=20s?= =?UTF-8?q?aid=20to=20clear=20take-in-waits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR 0639 v9 gives the drop its own clause and moves the checks to clause 10. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../47-delivery-from-commit-to-delivered.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md index b9fa6ffe..afe743bd 100644 --- a/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md +++ b/03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md @@ -619,7 +619,7 @@ From its delivery, a check is this: ### A try: a delivery group's check at exact commits *ADR 0637, ADR 0638; ADR 0593 §7, §9, ADR 0636 §4, ADR 0548 §5, ADR 0596 §1 to §3, ADR 0615, ADR 0639 §1 to -§6, §9.* +§6, §10.* 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. @@ -709,7 +709,7 @@ pull request, and it owes the forge nothing. retry on the next try event and by `indexer_recover_tries`); the checker's try test shows the retry until acknowledgement, `try.ended` after a restart, the whole list of open tries (more than 64), the verdicts kept by a try of some tests and of one level, the reason a waiting try gives, and an ended try's record kept 7 days and - removed after; the checker's manifest test shows that only its account publishes `try.*` (ADR 0639 §9). The registry group's review finds no copy of the shared + removed after; the checker's manifest test shows that only its account publishes `try.*` (ADR 0639 §10). 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); during that try `indexer_try_candidates` reads its candidate, and after `try.ended` it is gone (ADR 0638). @@ -782,7 +782,7 @@ pull request, and it owes the forge nothing. ### 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 0639 §7, §8, -which change ADR 0633 §1; ADR 0636 §4, ADR 0614 §8, ADR 0601 §3, ADR 0632 §2, ADR 0441 §3, §4, ADR 0564 §2, ADR 0595 +which change ADR 0633 §1, and ADR 0639 §9, §10; ADR 0636 §4, ADR 0614 §8, ADR 0601 §3, ADR 0632 §2, ADR 0441 §3, §4, ADR 0564 §2, ADR 0595 §11, ADR 0596 §3, ADR 0615.* A change that brings a new manifest validator is judged by that validator: nothing else of it reaches the @@ -849,8 +849,10 @@ timer. A group's merges arrive together. (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). A hand take-in emits `take-in.taken` as any - take-in does, so the condition clears (ADR 0633 §5). + group's end again from the gate's `merges` state) (ADR 0633 §5). Both of the indexer's hand acts emit + `take-in.taken`: a hand take-in as any take-in does, and a hand drop naming the drop, for a merge whose wait was + said pending, so the watchman's `take-in-waits` clears; a drop of a merge never said pending emits none (ADR 0633 + §5, ADR 0639 §9). - **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 @@ -865,7 +867,8 @@ timer. A group's merges arrive together. 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); and rows for an own build fixed at start (a composition answer that differs changes - nothing) and for `take-in.pending` with `own build unknown` (ADR 0639 §9). Live, the registry group's merge shows in `indexer_pending` waiting for the new indexer and + nothing), for `take-in.pending` with `own build unknown`, and for a hand drop (a merge said pending emits + `take-in.taken` naming the drop, one never said pending emits none) (ADR 0639 §10). Live, the registry group's merge shows in `indexer_pending` waiting for the new indexer and is taken in when that indexer starts, and no node-engine reads distribution's new entry before the new indexer's hold: `node_manifest_reads` and `indexer_holds` on every node (ADR 0633, How it is checked). @@ -1541,7 +1544,7 @@ answers it: | Domain | Found | Answered by | |---|---|---| | seams | the indexer said to read none of the checker's state, while it reads the checker's previews and the checker watches its candidates; the forge bridge's read of the gate's `merges` unmarked | *A try* (two reads that stay under ADR 0596 §3 until issue 1966); *Taken in by the indexer it names* (the bridge's read marked the same) | -| tools | the try's tools not listed against the four lines; the hand acts not said to clear the condition | *A try* (the table of the checker's and the indexer's tools, `indexer_recover_tries` new); *Taken in* (a hand take-in emits `take-in.taken`; a hand drop is open) | +| tools | the try's tools not listed against the four lines; the hand acts not said to clear the condition | *A try* (the table of the checker's and the indexer's tools, `indexer_recover_tries` new); *Taken in* (both hand acts emit `take-in.taken`, ADR 0639 §9) | | bus forms | the form of `pending` and the try events' contract unsaid | *Taken in* (`pending` in the old form the grants part creates); *A try* (the package `tryevent` of mesh-delivery's sdk) | | running qualities | `checker_tries` not answering; the new indexer before the new checker in this release; a lost try event; what a narrowed try keeps; a recovery cut at 64; how long tries are kept; a try waiting for its candidate | *A try* (nothing deleted, asked again, said in `indexer_try_candidates`, the ordering edge; publish with retry, `try.ended` on every end; the narrowed try's verdicts; every open try listed; 7 days; the reason in the try read, bounded by the step bound) | | testing | ADR 0633's live case on the node-engines' reads lost; no live case for ADR 0638 | *Taken in* (`node_manifest_reads` and `indexer_holds` on every node); *A try* (the candidate read during the registry group's try, gone after `try.ended`) | -- 2.54.0