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.