hq #294 (issue 386) and #295 (issue 399) both merged a record numbered 0283; #294 landed first and keeps it. The branch that lands last renumbers.
118 lines
8.2 KiB
Markdown
118 lines
8.2 KiB
Markdown
# 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 in the knowledge base |
|
|
|---|---|
|
|
| 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 |
|
|
|
|
The knowledge base already holds the operational record and is indexed on symptoms. 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](../../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),
|
|
[to-be 53](../../03-DESIGN/01-to-be/53-the-issue-tracker.md)). 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:
|
|
|
|
```yaml
|
|
---
|
|
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 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](../../02-DECISIONS/0284-an-agent-moves-an-issue-to-fixing-under-its-own-name-and-an-issue-is-resolved-only-from-fixing.md),
|
|
[ADR 0281](../../02-DECISIONS/0281-an-issue-knows-whether-its-fix-is-delivered-and-a-fix-stuck-after-its-merge-is-loud.md)).
|
|
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](02-graduation.md) 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](../../02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md));
|
|
`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 be written to the knowledge base, so
|
|
the next person searching a symptom 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.
|