Merge pull request 'ADR 0279, to-be 53 §10: the issue tracker triages every new issue, and an issue is ready when what it depends on is delivered' (#285) from decisions/0279-the-issue-tracker-triages-every-new-issue into main

This commit was merged in pull request #285.
This commit is contained in:
2026-10-10 16:00:22 +00:00
4 changed files with 221 additions and 5 deletions
+4
View File
@@ -554,6 +554,10 @@ Not a domain of the mesh but of the way it is built, listed because its words me
older issue not resolved already asks; a certain duplicate is closed `wontfix` with its report kept on the older
issue ([ADR 0278](../02-DECISIONS/0278-the-issue-tracker-checks-every-new-issue-for-a-duplicate-through-a-judgement-the-agent-makes-without-tools.md)).
Its pass over the backlog is the verb `groom`.
- **triage** — the issue tracker's judgement of an opened issue, made by the agent with no tools: whether it is a
duplicate (the **duplicate check**), its **severity** and what it **needs**, a suggested owner, and the issues it
depends on or relates to; it fills only what nobody gave ([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)).
- **ready** — of an issue: every issue it depends on is wontfix, or resolved with its fix delivered.
- **hq check** — one of `00-META/checks/`, run by `merge-check.sh` as this repository's check.
## Homonyms
@@ -179,6 +179,11 @@ One pass runs at a time. A pass's findings are kept until the next pass replaces
`duplicate-check` (`on` by default; `off` stops the check on new issues, said in the log) and `judge-model`
(`sonnet` by default; an alias the judgement tool accepts). Both are the module's ordinary settings.
> **The mechanism changed — 2026-10-10, by ADR 0279.** The judgement this record decided is now one part of the
> **triage** ([ADR 0279](0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md)): its second question is always asked and also estimates severity, needs, a suggested
> owner and the issues the new one depends on or relates to. What it decided of duplicates stands; the triage's
> notes and links are recorded as by `triage`, and the notes written as `duplicate-check` are still read.
## Consequences
- An issue opened twice is closed as a duplicate within the judgement's time of being opened, with the reason
@@ -0,0 +1,175 @@
---
topic: how we work
status: accepted
date: 2026-10-10
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0278-the-issue-tracker-checks-every-new-issue-for-a-duplicate-through-a-judgement-the-agent-makes-without-tools.md
---
# 279. The issue tracker triages every new issue, and an issue is ready when what it depends on is delivered
## Context
[ADR 0278](0278-the-issue-tracker-checks-every-new-issue-for-a-duplicate-through-a-judgement-the-agent-makes-without-tools.md)
made the tracker judge every opened issue against the older ones, for a duplicate. The same afternoon the operator
asked for more from that judgement:
- "Add a severity field to an issue. The groomer should pick up new issues and add metadata, like severity, so the
issuer doesn't have to provide all the knowledge — the groomer has a better understanding of the existing issues.
Another good indication: whether an issue needs a decision or the operator's feedback."
- "Issue links matter, especially when one issue depends on another being delivered first."
Agents choose their next piece of work from `issue-tracker.list`. Today a row says a number, a status, an owner and
a title, so an agent cannot tell a contained fix from one that needs the operator, or one it can start from one that
waits on another fix not yet on the machines. A complexity hint for choosing a model was asked for and withdrawn the
same day as a question to research first; this record leaves it out.
**What the tracker holds, measured on 2026-10-10.** 42 issues at `open` and more at `diagnosing` and `located`; an
issue is one JSON value in the module's key-value state ([ADR 0201](0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md)),
not a table. Links of kind `duplicates` and `relates-to` exist; nothing records an order between two issues.
`mesh-delivery` says every change of a delivery as its `transition` event, with the repository, the pull request's
number and the state it moved to, `delivered` among them; its `checks` verb answers a pull request's delivery and
its state.
The record was searched first ("severity", "depends on", "ready", "triage"): nothing beside ADR 0278 and research
042, whose model left priorities out "each needs a decision of its own" (to-be 53, "What it leaves out").
**A word.** The glossary has none for the judgement as a whole. This record names it **triage**: the duplicate
check of ADR 0278 is one of its parts.
## Considered Options
### Who sets severity and needs
1. *The issuer, required at open.* Refused: the issuer knows the symptom and not the backlog; a required field is
filled with a guess.
2. *A person, later.* Refused as the default: nobody grooms 40 issues by hand, which is why the operator asked.
3. **The triage estimates them, the issuer may give them, a person may change them.** The triage fills only what
is empty and says why in a note; where the issuer gave a value it disagrees with, it adds a note and changes
nothing. Chosen.
### When an issue is ready
1. *When every issue it depends on is resolved.* Refused: resolved means merged and recorded, not on the machines;
the operator asked for "delivered first".
2. **When every issue it depends on is resolved, or closed as wontfix, and every pull request its fix names is
delivered** — the delivery's own state, not the merge. Chosen. A wontfix dependency will never be delivered,
and an issue must not wait for it for ever — except one closed as the duplicate of another: the work moved
there, so it lets go only when its original does.
### How the tracker learns a fix is delivered
1. *Ask `mesh-delivery.checks` for every dependency at every `list`.* Refused: a list over the backlog would make a
call per pull request, past the 30 seconds a call is answered in.
2. **The delivery's own event, kept on the issue.** The tracker consumes `mesh-delivery.transition` and records, on
each issue whose `fixed-by` names that pull request, that it is delivered. A fix delivered before its issue was
resolved is asked of `checks` once, when the tracker hears its own `moved` to resolved. Chosen.
## Decision
### 1. What an issue gains
- `severity`, one of **critical** (something the operator relies on is broken, or data is at risk), **major** (a rule
of the mesh does not hold, or people work around it) and **minor** (a contained fix in one repository; nothing the
operator sees changes). Empty until given or estimated.
- `needs`, one of **none** (an agent can fix it), **decision** (a decision record must be written first) and
**operator** (the operator's answer or feedback is required). Empty until given or estimated.
- `suggested-owner`: an owner from the registered list, suggested by the triage when `located-in` is empty. It is
not `located-in`, which says where a diagnosis found the fault.
- `delivered`: the pull requests of `fixed-by` the delivery said are delivered.
- `triage`: the triage's judgement as it was made — who judged, when, each value with its reason — kept on the issue
so a second delivery of the event applies the same judgement rather than a new one.
- A link kind **`depends-on`**: this issue waits for that one to be delivered. `link` refuses one that would close a
cycle, naming it. `get` shows both directions: what an issue depends on, and what it is **needed by**, the
issues that depend on it.
The values are optional keys of the one JSON value an issue is. An issue kept before this record reads as having no
severity, no needs and no dependency: there is no table and no schema to migrate, so no numbered migration and no
consolidated schema exist for this store; a reader that does not know a key ignores it.
### 2. Verbs
- `open` takes `severity` and `needs` optionally.
- A new verb **`set`** changes `severity`, `needs` and `suggested-owner` of an issue, as a person decides; each
change is said as the event `set`, with who set it; a value cannot be cleared, only changed.
- `list` filters on `severity`, `needs` and **`ready`**; its rows carry severity, needs and ready. An agent looking
for work asks for `severity=minor`, `needs=none`, `ready`.
- `get` answers `needed-by` and `ready` beside the issue.
### 3. Ready
An issue is **ready** when it is not resolved or wontfix and every issue it depends on is either wontfix, or
resolved with every pull request its `fixed-by` names (`<repository>#<number>`, in any case, or the forge's page of
it) delivered; an entry that names no pull request, such as a commit, is not waited for. A wontfix dependency
closed as the duplicate of another counts as its original does. Readiness is said once per spell: an issue keeps
the dependencies it was said ready for, so one delivery letting two of them go says it once. An issue with no dependency is ready. When a dependency's fix
is delivered and that makes an issue ready, the issue gains a note saying so and naming the dependency, and the
tracker says the event **`ready`** with its number, so a picker can react.
The tracker consumes `mesh-delivery.transition`; a transition to `delivered` with a pull request's number marks
every issue whose `fixed-by` names `<repository>#<number>`. On its own `moved` to `resolved`, it asks
`mesh-delivery.checks` for each pull request in `fixed-by` it has not heard delivered, at most three per handling,
and marks those whose delivery is `delivered`. An ask that fails, or more left to ask, has the event offered again,
so a delivery never asked ends in `max-deliveries` rather than in silence.
### 4. Triage
The judgement of ADR 0278 becomes the triage, on the same `opened` event and in `groom`. Its two questions stay;
the second is now always asked, with the shortlisted issues (none, when the shortlist is empty) and the registered
owners, and answers beside the duplicate verdict:
- severity, needs and, when the issue names no owner, a suggested owner — each with a one-line reason;
- the shortlisted issues the new one **depends on** (they must be delivered first) and those it **relates to**,
each with a one-line reason.
The tracker keeps the judgement on the issue first, then applies it through its own verbs, each step only when it
is not done yet:
1. one note on the new issue saying the whole judgement, a line per value with its reason; where the issuer gave a
severity or needs the triage disagrees with, the line says so and that the issuer's value stands;
2. each empty field filled; a value a person gave is never changed;
3. each `depends-on` and `relates-to` link added, unless the issue already has it, it would close a cycle, or its
target is resolved or wontfix; the older issue gains a note naming the newer one and why;
4. the duplicate verdict applied as ADR 0278 §2 says.
The triage only adds: it removes no link and changes no value a person set. What it writes is recorded as by
`triage`; the notes ADR 0278 wrote as `duplicate-check` are still read as that issue's duplicate verdict.
### 5. The backlog
`groom` triages every issue not resolved that has no triage yet, as before: a dry run kept as a pass, listing for
each issue the values it would fill, the links it would add and the duplicates it would close; then `confirm`
applies exactly that pass. An issue that ADR 0278 already checked keeps its duplicate verdict and gains the rest.
## Consequences
- An agent picks its next fix from one call — `list` with `severity=minor`, `needs=none`, `ready` — without reading
every report.
- An issue waiting on another fix says so in the tracker, and is said ready when that fix reaches the machines,
not when it merges.
- The triage's values are estimates by a model. They are filled only where nobody gave one, each carries its
reason, and a person changes one with `set`.
- The tracker now depends on `mesh-delivery`'s event for readiness; while that module is down, nothing becomes
ready, which is the safe side.
## How it is checked
| Rule | Checked by |
|---|---|
| The triage fills only what is empty, says why, and notes a disagreement without changing the issuer's value | the tracker's test with the judgement stubbed: an issue with severity given and needs empty |
| A dependency is linked, a cycle refused, a resolved target not linked; nothing a person set is removed | the tracker's tests of `link` (a cycle of two and of three) and of the triage's links |
| Ready: every dependency wontfix or resolved with every pull request of its fix delivered | the tracker's table test: no dependency, a dependency open, resolved and not delivered, resolved and delivered, wontfix |
| A delivery's transition marks the fix delivered and the waiting issue gains a note and the `ready` event, once | the tracker's test: the event twice, one note, one `ready` |
| A fix delivered before its issue was resolved is found at the move | the tracker's test with `checks` stubbed |
| `list` filters on severity, needs and ready; `get` says needed-by | the tracker's tests |
| `set` changes the three values, refuses another, and says the event | the tracker's test |
| The backlog's dry run changes nothing; confirm fills only what is empty | the tracker's test |
| Live | an agent opens an issue: within a few minutes `get` shows its severity, needs and the triage's note; after the backlog's pass is applied, `list` with `severity=minor`, `needs=none`, `ready` answers the issues an agent can take |
## References
- [ADR 0278](0278-the-issue-tracker-checks-every-new-issue-for-a-duplicate-through-a-judgement-the-agent-makes-without-tools.md) — the judgement this record widens
- [ADR 0275](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) — the tracker
- [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
+37 -5
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/0279-the-issue-tracker-triages-every-new-issue-and-an-issue-is-ready-when-what-it-depends-on-is-delivered.md
---
# 53 — The issue tracker
@@ -38,10 +39,11 @@ each with what the operator sees when it is done.
├ bucket counters one key per sequence; slice 1 has `issue` only
├ bucket grooming the backlog pass of the duplicate check (§ 9)
├ setting owners the registered list `located-in` may name
├ verbs open, get, list, move, note, link, import, groom,
├ verbs open, get, list, move, note, link, import, groom, set,
│ served by the control-node's tool runner
├ events opened, moved, noted, linked, imported
└ consumes its own `opened`: the duplicate check (§ 9)
├ events opened, moved, noted, linked, imported, set, triaged, ready
└ consumes its own `opened` and `moved`: the triage (§ 9);
mesh-delivery's `transition`: when a fix is delivered (§ 10)
claude-code (on the same node)
└ claude_code_judge one question answered by the agent with no tools (§ 9)
@@ -69,6 +71,7 @@ agent finds them with `mesh_search` ("open an issue") and calls them as `issue-t
| `move` | a number, a status, and what that status needs | changes the status under the rules of § 5 |
| `note` | a number and a text | appends to the issue's notes; a note is never edited |
| `link` | a number, a kind and a target, or `remove` | adds or removes a typed link |
| `set` | a number and any of `severity`, `needs`, `suggested-owner` | changes them as a person decides (§ 10) |
| `groom` | optionally `confirm` and a pass's id | the duplicate check over the backlog (§ 9): without `confirm`, starts a dry run in the background and answers its id; with the id alone, its progress and findings; with `confirm` and the id, applies exactly that pass's findings, each checked again |
| `import` (slice 3) | optionally `dry-run`, a commit | reads every issue folder of hq through the records module and writes each as an issue at its own number; creates what is absent, fills a status the tracker holds empty, never overwrites, never advances the counter; run again, changes nothing and says so |
@@ -86,7 +89,10 @@ caller; the module records that identity as who acted. No verb takes an actor.
- `notes`: appended, each with who wrote it, when, and the text;
- `links`, typed: to another issue (`duplicates`, `relates-to`), a pull request, a decision record, a design, a
condition key or a delivery;
- `record`: the path of the issue's folder in hq, when it has one.
- `record`: the path of the issue's folder in hq, when it has one;
- `severity` (critical, major, minor), `needs` (none, decision, operator), `suggested-owner`, `delivered` (the
pull requests of `fixed-by` the delivery said are delivered) and `triage` (the triage's judgement as made), and the link
kind `depends-on` (§ 10).
**The registered list of owners** is a setting of the module, changed through the controller's settings verb like any
other module setting. Slice 1 starts it with the repositories in hq's `00-META/repos.md` and the modules the catalogue
@@ -164,6 +170,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 |
| 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 |
@@ -204,7 +211,32 @@ was checked** ([ADR 0278](../../02-DECISIONS/0278-the-issue-tracker-checks-every
`grooming`, then applies that pass's findings on `confirm`.
- Settings: `duplicate-check` (`on`) and `judge-model` (`sonnet`).
## 10. Triage, severity and readiness
**An agent opening an issue need not know the backlog: the triage estimates its severity and what it needs, links
what it depends on, and the issue is said ready when every fix it waits for is delivered**
([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)).
- **Triage** is the judgement of § 9 widened: its second question is always asked and also answers severity,
needs, a suggested owner when the issue names none, and the shortlisted issues it depends on or relates to, each
with a one-line reason. The judgement is kept on the issue first, then applied through the tracker's own verbs:
one note saying it all, each empty field filled, each link added (never one that closes a cycle, never to a
resolved or wontfix issue), the duplicate verdict last. It fills only what is empty; a value a person gave
stands, and a disagreement is a line in the note. Recorded as by `triage`.
- **Severity**: critical (something the operator relies on is broken, or data is at risk), major (a rule of the
mesh does not hold, or people work around it), minor (a contained fix; nothing the operator sees changes).
**Needs**: none, decision (a decision record first), operator (the operator's answer is required).
- **`depends-on`**: refused when it would close a cycle; `get` shows what an issue depends on and what it is
needed by.
- **Ready**: not resolved or wontfix, and every dependency wontfix (one closed as a duplicate counts as its
original), or resolved with every pull request its `fixed-by` names delivered; said once per spell. The tracker consumes `mesh-delivery.transition` and, at its own `moved` to resolved, asks
`mesh-delivery.checks` for what it has not heard; an issue made ready gains a note and the event `ready`.
- `open` takes severity and needs; `set` changes severity, needs and suggested owner; `list` filters on severity,
needs and ready. `groom` triages the backlog, dry run first.
- 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.
## What it leaves out
Priorities, claims, notifications, 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` event, 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.