Merge pull request 'ADR 0281, to-be 53 §11: an issue knows whether its fix is delivered, and a fix stuck after its merge is loud' (#286) from decisions/0281-an-issue-knows-whether-its-fix-is-delivered into main
This commit was merged in pull request #286.
This commit is contained in:
+124
@@ -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
|
||||
@@ -7,6 +7,7 @@ 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/0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md
|
||||
- 02-DECISIONS/0281-an-issue-knows-whether-its-fix-is-delivered-and-a-fix-stuck-after-its-merge-is-loud.md
|
||||
---
|
||||
|
||||
# 53 — The issue tracker
|
||||
@@ -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,7 +238,24 @@ 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:
|
||||
Priorities beyond severity, claims, notifications beyond the `ready` and `ready-to-verify` events and the stuck-fix warning, full-text or semantic search, the forge's own issue lists, and an index of any kind:
|
||||
none of them is in any slice above, and each needs a decision of its own.
|
||||
|
||||
Reference in New Issue
Block a user