Files
hq/00-META/process/03-issues.md
T
jschoubben c9d7d03ab8
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 delivered
Keep playbook 03's rule as it was, in plain words, and say how the record is searched (review of #318)
2026-10-11 21:07:32 +02:00

8.1 KiB

Playbook 03 — Issues

Trigger. Something is wrong at the level of the mesh's design or governance — a rule that turns out to be unenforced, a stated behaviour that does not happen, a silent failure the design permits.

Who runs it. Anyone may open an issue. No localisation is required to report one.

What belongs here, and what does not

Belongs in 04-ISSUES Belongs with the fix's own lesson, not here
The design permits a failure to be silent How to fix one occurrence of it
A documented rule is enforced by nothing A command that works around it
A stated invariant is false in practice A node-specific quirk
The owning component is unknown and finding it needs the whole mesh in view Symptom → fix, once the answer is known

A fix's lesson (symptom → fix) is written where a search by the symptom's literal words finds it (records.records_search). This folder is not a second copy of it. An issue here is a question HQ must answer, not an incident someone must clear.

The issue tracker holds the number and the status

Every issue is opened in the issue tracker, the module mesh-issues holding the seat issue-tracker on the control-node (ADR 0275, to-be 53). Its number comes from one counter advanced by compare-and-set, so two authors opening at the same moment never get the same one. A number is never taken by reading main or the open pull requests — that is how numbers collided (issue 155: two records sharing a number reached main with every check passing). A session that cannot reach the tracker takes no number and says so.

The tracker is the truth for every issue's status, owner, fix and links, whether or not the issue has a folder here. issue-tracker.get NNN says where an issue stands; issue-tracker.list says what is open. Nothing here says it: a report's frontmatter carries no status.

An issue takes one of two shapes:

  • With a folder here — a design or governance issue, whose narrative belongs in this public record and is reviewed in a pull request. The folder holds the report and the diagnosis; the tracker holds everything else.
  • Wholly in the tracker — every other issue. Nothing is written here.

04-ISSUES/ stays, for the narrative only. It keeps the public narrative of a design or governance issue: the symptom as observed, the diagnosis, what was ruled out, and why. A plain bug — a defect whose fix needs no design question answered — needs no folder; it is whole in the tracker.

The reports written before the tracker (the folders numbered 001 to 364) carried status:, located-in:, fixed-by:, amended-design:, replay: and replay-none: in their frontmatter until the import of slice 3 had taken them into the tracker and been checked against it, field by field; then one pull request removed them, so that no status is held in two places. What they said that day is in this repository's history at the commit the import read, which every imported issue names (imported.commit in issue-tracker.get).

Steps

  1. Open the issue in the tracker, through the mesh MCP server: mesh_call issue-tracker.open with title, report (the symptom as observed, in plain terms, with the evidence that it happened), optionally located-in (entries of the tracker's registered list of owners), and, for an issue that gets a folder here, record — the folder's name without its number, short-name. The tracker answers with the number NNN; a refused open spends none.

    An issue with a folder then creates 04-ISSUES/NNN-short-name/00-report.md under exactly that number, on a branch:

    ---
    opened: YYYY-MM-DD
    ---
    

    Then # NNN. The title, the title as the tracker holds it, and the symptom as observed, as in the tracker's report. No status key: cycle.py refuses status:, located-in:, fixed-by:, amended-design:, replay: or replay-none: in any report, because the tracker holds them and two places holding one fact drift.

    An issue about a condition links the condition's key at once: issue-tracker.link with kind condition and the key mesh-controller.conditions gives. Every message about that condition then names the issue and where it stands; one no issue links says "No issue tracks this yet." An agent that silences a condition because an issue tracks it links that issue first (ADR 0285).

    An issue wholly in the tracker is done at this step.

  2. Investigate. Before you start, move the issue to diagnosing with by: who works on it and its node, as who@node (fix-399@laptop, operator@workstation). The tracker keeps you as the issue's worker, and get and list show it with since when. The trail goes in issue-tracker.note, and, for a folder-backed issue, in 01-diagnosis.md in the same folder as well — dated, including what was ruled out. A note keeps you the worker; a worker with nothing done on the issue for a day is stale. Then move it to located with located-in, which the tracker refuses without a registered owner; that ends your work on it.

  3. Fix. Before you start the fix, move the issue to fixing with by, even when you diagnosed it yourself. The tracker refuses the move while another worker is current, naming them: ask them, or take it over with take-over saying why, which is noted on the issue. A stale worker may be taken over from without a reason. Name the fix in fixed-by when it merges, with a move to fixing that gives by again; the issue stays fixing until the acceptance is proven live (ADR 0284, ADR 0281).

  4. Resolve. issue-tracker.move to resolved, which the tracker takes only from fixing, with fixed-by, and, if the root cause was a design gap, run playbook 02 and give amended-design. A core issue — its located-in names the controller, the node-engine, the tool runner, the SDK, or the catalogue's bus, forge or build agent — resolves with replay, the id of its replay in mesh-lab's replays register, proved to fail on the commit before the fix and pass on it, or with replay-none saying why none is possible (ADR 0237); issue-tracker.move refuses the move without it. wontfix needs a reason.

Rules

  • Closed issues are never deleted — they are the mesh's symptom-to-component memory.
  • fixed-by names something that will still exist: a commit or a pull request, never a branch. A branch is deleted when it merges, so a branch name there is a pointer that resolves to nothing by the time anybody follows it.
  • A fix that turns out to have broken something else is written back into the record that asked for it, pointing at the new issue. Somebody arriving at a record to learn why the code is the way it is must not have to already know there was a sequel.
  • A number never moves. A folder carries the number the tracker gave; a folder whose number collides with another (one numbered before the tracker, or by hand) is a fault, and cycle.py refuses two records sharing a number.
  • An issue whose answer is a general lesson should also have that lesson written where a search by the symptom's literal words finds it, so the next person searching it finds it. Both, not either.
  • wontfix is legitimate and requires a sentence saying why; the tracker refuses it without one.
  • An agent says when it starts work on an issue, by moving it to diagnosing or fixing with by. Two agents never start the same work without knowing: the second is refused, naming the first.
  • A rule states how it is checked. The cycle's rules on an issue — a status one of the six, diagnosing and fixing with a worker and no other current one, located with an owner, resolved only from fixing and with a fix, a core issue with its replay, wontfix with a reason — are refused at the write by issue-tracker.move (mesh-catalog modules/mesh-issues, its table test over the rules). cycle.py checks what this repository still holds: no two folders share a number, every report has frontmatter with the day it was opened:, and no report carries a status key.