ADR 0281, to-be 53 §11: an issue knows whether its fix is delivered, and a fix stuck after its merge is loud
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 approved measuring, from the delivery's own events, whether each fix
an issue names reached the machines; delivered is not resolved, and a merged fix
that does not arrive must be said rather than found.
This commit is contained in:
jochen
2026-10-10 18:08:38 +02:00
parent d836092329
commit 5ef7d4dc36
2 changed files with 144 additions and 1 deletions
@@ -0,0 +1,124 @@
---
topic: how we work
status: accepted
date: 2026-10-10
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md
---
# 281. An issue knows whether its fix is delivered, and a fix stuck after its merge is loud
## Context
[ADR 0279](0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md)
made the tracker hear when a pull request in an issue's `fixed-by` is delivered, for one purpose: an issue that
depends on it becomes ready. On 2026-10-10 the operator approved a wider use of the same fact: an issue knows
whether its fix is delivered, every step of the way — merged, held and why, covered by a later merge, delivered —
"a measured fact, never set by hand"; delivered is not resolved; and "a merged-but-stuck fix is loud".
**What the delivery says, measured on 2026-10-10.** `mesh-delivery` says every change of a delivery as its event
`transition`, with the repository, the pull request's number, the state it moved to (proposed, checked, ready,
published, delivering, held, delivered, failed, superseded, stopped, retired, covered), why, and the trunk commit it
merged as. Its verb `checks` answers a pull request's delivery, its state and what it waits for. No new event is
needed. Today an agent resolves an issue when its pull request merges, and nothing says whether the fix reached the
machines; a merge held for a person's push waits until somebody looks.
The record was searched first ("delivered", "held", "stuck", "ready to verify"): ADR 0239 and ADR 0270 (the
delivery and its covered state), and ADR 0279.
**What the mesh offers a module to say something is wrong, measured.** A module raises a condition only through
its health ([ADR 0256](0256-a-machines-hardware-condition-is-read-by-one-module-on-every-machine-and-a-fault-is-said-through-its-health.md)),
and a module that runs only its bundle declares none
([ADR 0240](0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md) rule 8). The tracker runs only its
bundle. What it can do is tell the operator's channel (`operator-channel.notify`), which deduplicates, caps and
routes a message like the controller's conditions and clears it when told it is over.
## Considered Options
### Where the fact comes from
1. *A link from the issue to its delivery plan, set by the agent.* Refused by the operator: a link set by hand goes
stale, and the plan is the delivery's, not the issue's.
2. **The delivery's own events, for the pull requests the issue's `fixed-by` names.** Chosen: measured, and kept up
to date by the module that owns the delivery.
### How a stuck fix is said
1. *A condition of the controller's, raised by the tracker.* Not possible as the mesh stands: a module raises a
condition only through its health, and the tracker has none to declare. Giving it a long-running process for
that alone would be a health check the module runs on itself, which ADR 0240 refuses as a judge.
2. **A note on the issue, and a warning on the operator's channel, cleared when the fix is delivered.** Chosen. The
channel routes it like a condition; the gap — a bundle-only module cannot raise a condition of its own — is named
here and is a question for its own decision.
## Decision
### 1. Each fix is measured
For each pull request an issue's `fixed-by` names (`<repository>#<number>`, read in any case, or the forge's page of
it), the tracker keeps what the delivery last said: its state, why, whether it is merged and when. It learns it from
`mesh-delivery.transition` for every state, and, for a pull request it has not heard of when the issue names it,
from `mesh-delivery.checks` when it hears its own `moved` (at most three asks per handling; an ask that fails has the
event offered again). No verb sets it.
### 2. One line says it
`get` and every row of `list` carry **delivery**, derived from those states, worst first:
- "fix not delivered: …" — a delivery failed or was stopped, with why;
- "held: …" — with why, as the delivery says it;
- "fix merged, not delivered: …";
- "fix not merged: …";
- "nothing heard from the delivery of …";
- "delivered to every machine it should reach", or "… (by a later merge that contains it)" for one covered.
An issue whose `fixed-by` names no pull request has no line.
### 3. Delivered is not resolved
`resolved` still means the acceptance was proven live. When every fix of an issue that is not resolved or wontfix is
delivered, the issue gains a note — "Ready to verify: every fix of this issue is delivered … prove the acceptance
live, then resolve it" — and the tracker says the event **`ready-to-verify`**, once for that set of fixes. Agents
therefore name a fix in `fixed-by` when it merges, with the issue still `located`, and resolve it after the proof:
`move` to the status an issue already has is taken when it changes what the issue holds, such as its `fixed-by`, and
refused when it changes nothing. `move` still requires `fixed-by` to resolve.
### 4. A stuck fix is loud
A fix merged and not delivered after **two hours** (the module's setting `stuck-after`) is said once: a note on the
issue, and a warning on the operator's channel under the key `issue-tracker.fix-stuck.<number>`, cleared there when
every fix of the issue is delivered. Two hours is past every ordinary delivery — a walk takes minutes, a first-node
gate ten at most — and still the same working day; a hold for a person is what it catches. The tracker looks every
five minutes. One that the channel did not take is said at the next look.
### 5. Ready, as before
An issue is ready when everything it depends on is wontfix, or resolved with every fix delivered (ADR 0279 §3): the
same measured fact.
## Consequences
- An agent sees from `list` which fixes are merged and not on the machines, and which wait for a person and why.
- A fix held for the operator's push says so to the operator within two hours of its merge, instead of waiting for
somebody to look.
- Resolving moves later: after the delivery and the proof. An issue can sit at `located` with its fix delivered;
the `ready-to-verify` note and event say so.
- The stuck warning is a message on the operator's channel, not a controller condition; `mesh-controller.conditions`
does not list it.
## How it is checked
| Rule | Checked by |
|---|---|
| The line follows the delivery's states: not merged, merged, held with why, delivered, covered | the tracker's test feeding transitions |
| Ready to verify once, only for an issue not resolved; it stays unresolved | the tracker's test: the delivered transition twice, one note, one event; a resolved issue gains none |
| A stuck fix is said once on the issue and the channel, cleared when delivered; under the bound nothing; the bound is the setting | the tracker's test with the channel stubbed, including one that fails and is said at the next look |
| A pull request named before the tracker heard it is asked once, failure loud | the tracker's tests of `moved` with `checks` stubbed |
| Live | an issue at `located` names a merged pull request; `get` says "fix merged, not delivered", then "delivered to every machine it should reach", and the issue gains the ready-to-verify note |
## References
- [ADR 0279](0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md) — readiness, which this record widens
- [ADR 0239](0239-a-delivery-is-owned-by-the-mesh-delivery-module-and-runs-from-commit-to-delivered.md) — the delivery and its states
- [To-be 53](../03-DESIGN/01-to-be/53-the-issue-tracker.md) — the design this record amends
+20 -1
View File
@@ -6,6 +6,7 @@ updated: 2026-10-10
decisions:
- 02-DECISIONS/0275-the-mesh-numbers-and-tracks-its-issues-in-a-module-of-its-own-and-hq-keeps-only-the-narrative-of-a-design-or-governance-issue.md
- 02-DECISIONS/0278-the-issue-tracker-checks-every-new-issue-for-a-duplicate-through-a-judgement-the-agent-makes-without-tools.md
- 02-DECISIONS/0281-an-issue-knows-whether-its-fix-is-delivered-and-a-fix-stuck-after-its-merge-is-loud.md
- 02-DECISIONS/0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md
---
@@ -41,7 +42,7 @@ each with what the operator sees when it is done.
├ setting owners the registered list `located-in` may name
├ verbs open, get, list, move, note, link, import, groom, set,
│ served by the control-node's tool runner
├ events opened, moved, noted, linked, imported, set, triaged, ready
├ events opened, moved, noted, linked, imported, set, triaged, ready, ready-to-verify
└ consumes its own `opened` and `moved`: the triage (§ 9);
mesh-delivery's `transition`: when a fix is delivered (§ 10)
@@ -170,6 +171,7 @@ in one call, from any session, and the responder on a channel answers from it on
| The cycle's rules hold at the write | the module's table test over § 5, one case per row, each refused case changing nothing |
| The module creates no bucket | the runtime refuses a module that tries (ADR 0201 §3); the controller's raise asserts both buckets |
| The caller is who acted | the module's test that the recorded actor is the call's identity, whatever the arguments say |
| An issue's delivery line follows the delivery; ready to verify once; a stuck fix said once and cleared | the rows of [ADR 0281](../../02-DECISIONS/0281-an-issue-knows-whether-its-fix-is-delivered-and-a-fix-stuck-after-its-merge-is-loud.md)'s "How it is checked" |
| The triage fills only what is empty, links no cycle, and an issue is ready only when its dependencies' fixes are delivered | the rows of [ADR 0279](../../02-DECISIONS/0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md)'s "How it is checked" |
| The duplicate check acts only on a clear duplicate, never on an older or a resolved issue, once per issue, loudly when it cannot judge | the rows of [ADR 0278](../../02-DECISIONS/0278-the-issue-tracker-checks-every-new-issue-for-a-duplicate-through-a-judgement-the-agent-makes-without-tools.md)'s "How it is checked" |
| Live | on the mesh, an agent calls `issue-tracker.open` and gets the next number; two sessions on two nodes open at the same moment and get two different numbers; `list` shows both |
@@ -236,6 +238,23 @@ what it depends on, and the issue is said ready when every fix it waits for is d
- The values are optional keys of an issue's one JSON value: an issue kept before reads as having none, and the
key-value store has no schema to migrate.
## 11. Whether the fix is delivered
**An issue knows whether its fix is on the machines, as the delivery says it; delivered is not resolved, and a fix
stuck after its merge is said** ([ADR 0281](../../02-DECISIONS/0281-an-issue-knows-whether-its-fix-is-delivered-and-a-fix-stuck-after-its-merge-is-loud.md)).
- For each pull request `fixed-by` names, the tracker keeps the state `mesh-delivery.transition` last said — merged,
held and why, covered, delivered — or asks `mesh-delivery.checks` once for one it has not heard of. Nothing sets it
by hand.
- `get` and each row of `list` carry **delivery**, one line: "fix not delivered: …", "held: …", "fix merged, not
delivered", "fix not merged", "nothing heard …", "delivered to every machine it should reach".
- When every fix of an issue not yet resolved is delivered, it gains a note and the event `ready-to-verify`, once;
it is resolved after the acceptance is proven live. A fix is named while the issue stays `located`: `move` to the
same status changes what the issue holds.
- A fix merged and not delivered after `stuck-after` (two hours) is said once: a note on the issue and a warning on
the operator's channel, cleared when it is delivered. It is not a controller condition: a module that runs only
its bundle cannot raise one.
## What it leaves out
Priorities beyond severity, claims, notifications beyond the `ready` event, full-text or semantic search, the forge's own issue lists, and an index of any kind: