ADR 0282 (proposed): a leaf fix runs healthy on every node within five minutes of its merge, a core module within ten
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

The operator asked that a bug fix land on every node in under five minutes. 51
walks measured from the controller's own records put a leaf walk at a median of
6.0 minutes (7 of 20 within five) and say where the time goes: waiting behind
another walk, the first-node judgement, the merge window. The record weighs each
lever by what it would have saved, keeps three readings for core modules (ten
minutes, the measured worst being 8.8), and proposes the measure and condition
that check it. To-be 47 gains the section, the owners and five slices; the
glossary gains delivery time, delivery budget, core and leaf module.
This commit is contained in:
jochen
2026-10-10 19:51:07 +02:00
parent 0cabe5d391
commit 9414855fbe
3 changed files with 289 additions and 0 deletions
+7
View File
@@ -366,6 +366,13 @@ planner, the walk). **Downstream of** Module, Core, Health and repair and Data.
*Not:* ~~rollout~~ (hq)
- **first-node gate** — the judgement on a delivery's first node: healthy three times, no witness put it
back, no condition raised since the send (ADR 0236 §2). Not *the gate* bare (see [Homonyms](#homonyms)).
- **core module** and **leaf module** — a core module is one on the controller's own path (the controller,
the node-engine, the tool runner, the bus, mesh-delivery) or the mesh's resolver, whose judgement protects
the mesh's ability to reach a node at all; a leaf module is every other module. Proposed by
[ADR 0282](../02-DECISIONS/0282-a-fix-to-a-leaf-module-runs-healthy-on-every-node-within-five-minutes-of-its-merge-and-a-core-module-within-ten.md).
- **delivery time** — from a delivery's merge to every node of its deploy plan that is heard from running its
modules' builds and reporting them healthy. Its **delivery budget** is what it is held to: five minutes for
a leaf module, ten for a core module (proposed by ADR 0282). Not the walk's time, which ends at the send.
- **release** — a person's word that a held delivery goes on; the verb `mesh-delivery.release`. Used in
this sense only.
- **rollback** — putting a node back on the build it ran before, by something other than the build being
@@ -0,0 +1,231 @@
---
topic: the mesh
status: proposed
date: 2026-10-10
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0276-merges-are-assembled-in-a-rolling-window-and-each-batch-is-delivered-by-one-walk-with-one-commit-per-repository.md
---
# 282. A fix to a leaf module runs healthy on every node within five minutes of its merge, and a core module within ten
## Context
On 2026-10-10 the operator asked, in their words: "we definitely need to speed up our bug fixing process so a
bugfix can land deployed and rolled out in less than 5 minutes". Issue 382 in the issue tracker (a small fix took
45 minutes to reach a node) names the causes it saw: a 90-second merge window, one walk at a time, about four to
five minutes per tier, and three judgement passes for every module. Issue 338 (a module built from a shared
repository moves on every merge to it) names a fourth: every merge of the controller walks two catalogue modules
in two more tiers.
### What was measured
Every walk the controller keeps (`delivery-walks`) records, per module, when its build was asked and finished,
when its first node was sent it, when the first-node gate judged it and when the rest were sent. Read on
2026-10-10 for the 51 walks that finished between 2026-10-09 13:55 and 2026-10-10 17:32 (UTC), each walk's time
from its merge to its builds being sent to every node it targets splits into these phases. A walk is a **core**
walk when it moves a module of the controller's own path (the controller, the node-engine, the tool runner, the
bus, mesh-delivery) or the mesh's resolver, and a **leaf** walk otherwise. Nine walks that failed or were folded
into another are not counted.
| Phase, in minutes | Leaf: median | Leaf: worst | Leaf: share | Core: median | Core: worst | Core: share |
|---|---|---|---|---|---|---|
| the merge window: merge to walk opened | 0.5 | 2.1 | 9 % | 0.4 | 2.1 | 7 % |
| waiting behind another walk | 0.3 | 59.1 | 47 % | 0.0 | 1.8 | 1 % |
| build | 0.4 | 1.6 | 5 % | 1.2 | 4.1 | 14 % |
| built to sent to the first node | 0.3 | 0.6 | 3 % | 0.5 | 3.0 | 5 % |
| first-node judgement, the longest of each tier | 2.6 | 6.3 | 27 % | 7.2 | 14.9 | 63 % |
| first nodes of one tier judged one after another | 0.0 | 4.2 | 8 % | 0.0 | 0.0 | 0 % |
| judged to sent to the rest | 0.0 | 0.9 | 2 % | 0.5 | 5.1 | 6 % |
| between tiers | 0.0 | 0.6 | 0 % | 0.0 | 1.0 | 3 % |
| **merge to sent to every node** | **6.0** | **66.0** | | **9.7** | **20.5** | |
- **Leaf walks** (20): median 6.0 minutes, 90th percentile 14.8, worst 66.0; **7 of 20 within five minutes**.
The worst is the incident of issue 362 (58 minutes waiting for a person's word), which ADR 0276 already
repaired; without it the worst wait behind another walk is 13.3 minutes. Since the merge window went live on
2026-10-10 at about 12:40, a lone merge waits 1.6 to 2.1 minutes for its window (median 1.8), and **4 of the 9
leaf walks since then waited behind another walk**, for 1.8, 4.9, 9.5 and 13.3 minutes, each behind a walk of
the controller's own path.
- **Core walks** (31): median 9.7, 90th percentile 17.0, worst 20.5; 4 of 31 within five minutes. They are two
populations. **13 core walks had one tier**: median 5.6, 90th percentile 7.2, worst 8.8. **18 walks
were controller merges that also walked the build agent and the route proxy** in tiers of their own (issue
338): median 14.2, worst 20.5, none within ten minutes.
- **The first-node judgement** is three healthy readings, at least forty seconds apart and two minutes after
the send (ADR 0236 decision 2). It took a median of 2.4 minutes for a core module other than the controller (10
judgements), 3.6 minutes for the controller (19 judgements, worst 4.3, its readiness includes its
self-check), and 2.6 minutes per tier of a leaf walk. **Five of the 20 leaf walks judged two first nodes one
after the other** within one tier (a module first on one node, another first on a second), adding 3.0 to 4.2
minutes each.
- **The build is small**: a median of 0.4 minutes for a leaf walk and 0.6 minutes for a core walk of one tier.
- **The rest are sent at once already**: a leaf walk sends the rest a median of 0.0 minutes after its judgement.
The walk ends when the rest are *sent*, not when they run the build. Applying a send takes a median of 10 to
17 seconds per node (the controller's `durations`, seven days, every node), so the measured times are short of
"healthy on every node" by about a quarter of a minute.
- **The merge gate does not build the module.** Its record for a controller pull request on 2026-10-10 shows it
clones the repositories, composes every node's declaration with the change, compiles the controller only to
judge the change with itself (3.2 seconds), and runs the repository's `merge-check.sh` (6 minutes 13 seconds,
before the merge). No artifact is built that the merge could reuse; the merge's build is the first.
Three limits of the measurement: a walk opens only when the walk before it ends, so time beyond the window's
own 1.6 to 2.1 minutes is counted as waiting; the walk keeps only the last judgement pass, so the time of a
first pass is estimated below, not measured; and the check before the merge is not counted, because the
operator's target starts at the merge.
**Checked against GENESIS.** *Anything requiring a human to notice it will be noticed late*: a fix that takes
fifteen minutes to land is noticed by the operator waiting for it. *Failure must be loud*: a delivery over its
budget raises a condition. *Evidence over assertion*: every lever below is weighed by what it would have saved
in the walks above. Nothing here conflicts with GENESIS.
**Words.** The glossary has no word for the time from a merge to its modules running healthy everywhere, nor for
the time it is held to, nor for the two kinds of module. This record proposes **delivery time**, **delivery
budget**, **core module** and **leaf module** for the Change and delivery domain, with this record.
## Considered Options
Each lever is weighed by what it saves in the 20 leaf walks measured, against what it costs.
1. **No merge window for a lone merge** (chosen). Saves the window's 1.8 minutes on every lone merge. The window
exists to put the parts of one change, merged together across repositories, into one walk (ADR 0276). A
merge whose delivery group has no member still unmerged has no part to wait for.
2. **Independent walks in parallel** (chosen). Saves the wait behind another walk: 1.8 to 13.3 minutes in 4 of
the 9 leaf walks since the window went live, 34.7 minutes over the 19 leaf walks outside the issue 362
incident. Its cost is two builds judged on one node at once, which the rules in decision 3 keep apart.
3. **Judgement passes by the module's class** (chosen). One healthy reading takes an estimated minute after the
send (a send applies in 10 to 17 seconds, and the controller acted on a build in a median of 0.3 minutes),
against 2.6 minutes for three. Saves about 1.5 minutes per tier of a leaf walk. The cost is a crash that shows
only after the first reading, which a second reading forty seconds later catches for a module with a
long-running resource. A module whose every resource is a tools bundle has no process of its own to crash: the
tool runner serves its tools or does not.
4. **The first nodes of one tier judged at once** (chosen; not in issue 382, found by the measurement). Saves
3.0 to 4.2 minutes in 5 of the 20 leaf walks.
5. **Reuse the merge gate's build of the pull request's head** when the merged tree equals the checked one.
Rejected: the merge gate builds no artifact today (see Context), so there is nothing to reuse. Building every
head's artifacts at its check, to save the merge's build, would save at most the build's median 0.4 minutes
for a leaf walk, cost a build on every push to a pull request, and need ADR 0238 decision 1 ("anything built to
check it is never registrable") reopened.
6. **A build cache.** Rejected as a lever of this record: the build is 5 % of a leaf walk's time, and the
artifact that did not change is already not rebuilt (ADR 0267's build source).
7. **Sending the rest at once after the first passes.** Already so: the rest are sent a median of 0.0 minutes
after the judgement. Nothing to gain.
8. **The plan says when it is expected to land** (chosen). Saves no time. It tells the operator, while waiting,
when to look, from the same measurements.
9. **One budget for every module, five minutes.** Rejected: a core module's judgement is what protects the
mesh's ability to reach a node at all. A broken node-engine, tool runner, bus or resolver cannot be put back
from outside the node, and the controller judges every other build. The controller's judgement alone takes 3.6
minutes; its three readings are kept.
With options 1 to 4 applied to the 20 measured leaf walks (no window, no wait, one judgement of about a minute
per tier with its first nodes at once, plus a quarter of a minute for the rest to apply), the median would be 2.1
minutes and the worst 5.3; **19 of 20 within five minutes**, against 7 of 20 measured. Keeping three readings
and applying only options 1, 2 and 4 gives a median of 3.9 and a worst of 9.6 (18 of 20).
## Decision
1. **The target, and how it is measured.** A delivery's **delivery time** runs from its merge to the moment
every node of its deploy plan that is heard from runs its modules' builds and reports them healthy. A node not
heard from (a laptop asleep) is listed and not waited for, as ADR 0236 leaves it. **A leaf module's delivery
budget is five minutes. A core module's is ten.** A delivery held for a person (a module that records, a hold,
a bus step) is shown and not held to a budget: its wait is the person's, and S16 and D14 already speak for it.
- A **core module** is one on the controller's own path (the controller, the node-engine, the tool runner, the
bus, mesh-delivery) or the mesh's resolver. A **leaf module** is every other module. A delivery's class is
that of the slowest-budgeted module it moves.
- The ten minutes for a core module are the measurement's: the 13 core walks of one tier took a median of
5.6 minutes and at worst 8.8. A controller merge that also walks the build agent and the route proxy (median
14.2) meets it once issue 338's path precision is built: that work is tracked by issue 338 and is the
planner's, not this record's.
2. **No merge window for a lone merge.** When a merge arrives, no batch is assembling, and its delivery belongs to
no delivery group with a member not yet merged, the controller cuts its batch at once. A merge of a group still
waiting for members, or a merge arriving while a batch assembles, waits for the window as ADR 0276 decided.
3. **Independent walks run at once.** A batch becomes a walk without waiting for an open walk when it shares with
every open walk no repository, no module, and no edge of the module graph between their modules. Where they
meet, they wait for each other:
- **one first-node judgement per node at a time**: a walk's first node is chosen among its module's nodes
where no other walk is judging; a send to a node where another walk's build is being judged waits for that
verdict, so ADR 0236 decision 4a (no build reaches a node without a judgement) holds as written;
- **a core walk keeps its ground**: no leaf build is sent to a node where a core build is being judged, and
while a new controller is judged, no leaf judgement counts a reading; those resume when it passes or is put
back;
- two walks of one repository never run at once (issue 335's rule stands), and a merge into a repository with
an open walk joins that repository's next batch.
This replaces ADR 0276 decision 4's "one walk at a time"; its rule that a walk which started is never
superseded stands.
4. **Judgement by class.** The first-node gate's readings, each at least forty seconds after the one before:
- a **core module**: three, the first at least two minutes after the send, as ADR 0236 decision 2 decided;
- a **leaf module with a long-running resource** (a service, a container, a process the node-engine runs):
two, the first after the node-engine states every such resource healthy (ADR 0240);
- a **leaf module whose every resource is a tools bundle**: one, once the tool runner serves its tools.
Everything else ADR 0236 decided of the first-node gate stands: no witness put the build back, no condition
raised since the send, ten minutes at the most, and a rollback when it fails.
5. **One tier's first nodes are judged at once**, each by its own readings, and the tier passes when every one
has passed.
6. **Every walk keeps its phases, and every delivery its delivery time.**
- The controller records in the walk, and says in `plan-moved`, each phase of the table above with its start
and end, each judgement reading with its time, and the first healthy report of each node of the rest. Its
`durations` gains the kind `walk-phase`, per class.
- mesh-delivery records each delivery's delivery time, its class and its phases from the walk. A new verb,
`times`, answers each class's budget, the last seven days' median and worst delivery time, and every
delivery over its budget with where its time went; `deliveries` heads its list with one line of the same,
and `show` gives a delivery's phases.
- The controller's `status` shows that line, read from `times` by the self-check, beside what is wrong.
7. **A delivery over its budget is loud.** The self-check's probe reads `times` and raises the warning
`delivery.<class>.over-budget` when a delivery's time exceeds its class's budget, naming the delivery and its
longest phase. It clears when the next delivery of that class lands within its budget.
8. **The plan says when it will land.** `plans` and mesh-delivery's `show` give an open batch and an open walk an
expected landing time: now plus the remaining phases at the last seven days' median for its class, and once a
walk is judging, plus the readings still owed.
## Consequences
- **A leaf fix lands within five minutes when nothing else is in its way**, by the measurement's own estimate:
build 0.4, send 0.3, one or two readings at about a minute each, the rest a quarter of a minute.
- **A burst of unrelated merges no longer queues**; it walks in parallel, and one node judges one walk at a time.
The controller holds several open walks, and every reader of a walk (`plans`, S3, S16, `plan-moved`,
mesh-delivery) reads a list where it read one.
- **A leaf module is judged on less evidence.** A build that fails after its last reading reaches the rest. That
is bounded: the rest's first healthy report is recorded, a failure there is a condition on that node, and the
build that broke it is put back the ordinary way. The trade is the operator's stated choice of speed for leaf
modules; core modules keep every reading.
- **A core walk still holds its ground.** A leaf delivery whose node is judging a core build waits up to that
judgement (3.6 minutes for the controller) and may exceed its budget; the condition says so, and the week's
numbers show how often.
- **The core budget depends on issue 338.** Until the planner moves only the modules whose build source a merge
touched, a controller merge exceeds ten minutes and raises the condition each time. That is intended: the
condition counts what issue 338 costs.
- **What got harder:** the walk's record grows by the phases and readings, kept in the controller's store as a
numbered migration; the scheduler of walks gains the independence test and the per-node judgement rule, each a
place where two walks can disagree, and each tested.
- **On acceptance**, ADR 0236 (decision 2, three readings for every module) and ADR 0276 (decision 4, one walk at
a time; decision 1, the window for every merge) each gain a note saying the mechanism moved here, and to-be 47
lists this record among its decisions.
## How it is checked
| Rule | Checked by |
|---|---|
| a lone merge is cut at once; a group's member and a merge during assembly wait | the controller's tests: a lone merge asks its build with no window; a merge whose group has an unmerged member waits for the window; a second merge within the window joins the batch |
| independent walks run at once, dependent ones wait | the controller's tests: two catalogue merges of unrelated modules walk at once; two of one repository, two sharing a module, and two joined by a graph edge do not; two walks never judge on one node at once; a send to a node under another walk's judgement waits for its verdict; no leaf reading counts while a new controller is judged |
| judgement by class | the controller's tests: a tools-only leaf passes on one reading, a leaf with a service on two forty seconds apart, a core module on three two minutes after the send; each fails and is put back as before |
| one tier's first nodes at once | the controller's test: a tier whose modules are first on two nodes judges both at once and passes when both pass |
| every phase and the delivery time are recorded | the controller's test of the walk record and `plan-moved`; mesh-delivery's tests of the delivery time, its class, `times` and `show` |
| a delivery over budget is loud | the probe's test: a leaf delivery of 5 minutes 10 seconds raises `delivery.leaf.over-budget` naming its longest phase; the next within budget clears it |
| the target holds on the live mesh | `mesh-delivery.times`: the week's leaf median within five minutes and the condition raised by no more than one leaf delivery in ten; the same measurement as this record's Context, repeated a week after the last part is built |
## References
- Issue 382 (a small fix takes 45 minutes to reach a node) and issue 338 (a module built from a shared repository
moves on every merge to it), in the issue tracker.
- [ADR 0236](0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md):
the first-node gate's three readings, kept for core modules.
- [ADR 0238](0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md):
a check's build is never registrable, which option 5 would have reopened.
- [ADR 0239](0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md):
mesh-delivery owns the delivery, and so its delivery time.
- [ADR 0267](0267-an-artifact-is-rebuilt-only-when-its-build-source-changes-and-a-module-moves-only-along-build-dependencies-on-artifacts.md):
an unchanged artifact is not rebuilt.
- [ADR 0276](0276-merges-are-assembled-in-a-rolling-window-and-each-batch-is-delivered-by-one-walk-with-one-commit-per-repository.md):
the merge window and the walk this record changes in part.
- [Research 044](../01-RESEARCH/044-what-leaves-the-controller-next/00-overview.md), whose question 6 asks for
the time from merge to a node running the fix as a baseline; the table above is that baseline.
- To-be [47](../03-DESIGN/01-to-be/47-delivery-from-commit-to-delivered.md), amended with this record.
@@ -158,6 +158,57 @@ controller's `plan-moved` event, never inferred from time.
answers within ten minutes is held with the true reason (the open walks of its repository and that none
names its merge, or that the controller holds none) and listed by `stalled` at once.
## The delivery budget
*Proposed 2026-10-10 by [ADR 0282](../../02-DECISIONS/0282-a-fix-to-a-leaf-module-runs-healthy-on-every-node-within-five-minutes-of-its-merge-and-a-core-module-within-ten.md),
which awaits the operator's decision. Nothing in this section is in force until that record is accepted; then it
joins this document's decisions and replaces, where they differ, the window for every merge and the one walk at a
time above.*
**The target.** A delivery's **delivery time** runs from its merge to every node of its deploy plan that is heard
from running its modules' builds and reporting them healthy. A **leaf module's** delivery budget is five minutes;
a **core module's** (the controller's own path and the mesh's resolver) is ten. A delivery held for a person is
shown and held to no budget. Measured on 51 walks on 2026-10-09 and 10, a leaf walk took a median of 6.0 minutes
(7 of 20 within five), a core walk of one tier 5.6 (worst 8.8), and a controller walk dragging two catalogue tiers
14.2 (issue 338).
**What changes in the walk.**
- **A lone merge is cut at once**: no batch assembling, and no member of its delivery group still unmerged.
- **Independent walks run at once**: no shared repository, module or module-graph edge with any open walk. One
node judges one walk at a time; a send to a node under another walk's judgement waits for its verdict; no leaf
build goes to a node where a core build is judged, and no leaf reading counts while a new controller is judged.
- **The first-node gate reads by class**: three readings for a core module, as ADR 0236 decided; two for a leaf
module with a long-running resource; one for a leaf module whose every resource is a tools bundle.
- **A tier's first nodes are judged at once.**
- **Every phase is kept**: the window, the wait, the build, the send, each reading, the rest's first healthy
report; the plan shows an expected landing time from the last seven days' medians.
**Who builds what.**
| Part | Owner | What it adds |
|---|---|---|
| the walk's phases and readings | mesh-controller | each phase's start and end and each reading in the walk and in `plan-moved`, the rest's first healthy report per node, `durations` of kind `walk-phase`; a numbered migration |
| the delivery time | mesh-delivery | each delivery's delivery time, class and phases; the verb `times` (budgets, the week's median and worst per class, the deliveries over budget and their longest phase); a line atop `deliveries`; phases in `show` |
| the condition | mesh-controller | a self-check probe reading `times`, raising `delivery.<class>.over-budget` (a warning) and clearing it at the next delivery of that class within budget; the line in `status` |
| the lone merge | mesh-controller | the cut at once when nothing waits to join |
| walks at once | mesh-controller | the independence test, one judgement per node, a core walk's ground, a repository's next batch |
| judgement by class | mesh-controller | the readings per class in the first-node gate; a tier's first nodes judged at once |
| the landing time | mesh-controller, mesh-delivery | an expected landing time in `plans`, in `plan-moved`, and in `show` |
**Slices**, each one pull request per repository, each with its acceptance sentence:
| Slice | Delivers | Done when |
|---|---|---|
| 1 — the measure | the walk's phases and readings (controller); the delivery time, `times` and the line in `deliveries` (mesh-delivery); the probe and its condition (controller) | `mesh-delivery.times` shows the week's leaf and core median and worst delivery time, and a leaf delivery over five minutes raises `delivery.leaf.over-budget` naming its longest phase |
| 2 — no window for a lone merge | the cut at once | a lone leaf merge's build is asked within fifteen seconds of its merge, read from its walk's phases |
| 3 — judgement by class | the readings by class, a tier's first nodes at once | a tools-only leaf module passes its first-node gate on one reading, read from its walk |
| 4 — walks at once | the independence test and the per-node rule | two unrelated catalogue merges a minute apart are both delivered within five minutes, neither waiting for the other |
| 5 — the landing time | the expected landing time | `plans` shows it for an open walk, and the week's deliveries land within a minute of it at the median |
Issue 338's path precision is the planner's, tracked by issue 338; the core budget is met by controller merges
only once it is built.
## A delivery group
- **Membership**: the pull request's head branch name, across the mesh's repositories, among open pull