Knowledge base: progress says how far a design, milestone, issue or record has come (issue 1947)

A lead's five-minute report and 'is design 47 nearing its end' were joined by hand from
the design file, the tracker and the deliveries. One verb now works the state out
(ADR 0424 §1), minimal for every five minutes, detailed for the report's tree.
Milestones are read from the design's table until the tracker's milestone records
(issue 1781) are delivered.
This commit is contained in:
2026-10-26 16:37:54 +01:00
parent 5e516317e9
commit 8da33e43d4
17 changed files with 3366 additions and 8 deletions
@@ -0,0 +1,7 @@
Captured for the progress verb's tests (novox/hq issue 1947), never written by hand:
- `hq/`: three designs of hq at the commit in `hq/COMMIT`, copied whole.
- `tracker-rows.json`: rows of `issue-tracker.list` with `numbers`, as the live tracker answered them on
2026-10-26 16:2x (before rows said `moved`).
- `deliveries.json`, `groups.json`, `show-car-hunter-2.json`: parts of `mesh-delivery.deliveries`, `.groups` and
`.show` as the live delivery seat answered them on 2026-10-26 16:2x, cut to the members named here.
@@ -0,0 +1,5 @@
{"deliveries": [
{"for": "17m26s", "id": "jschoubben/car-hunter@cc4c8ce12901", "number": 2, "since": "2026-10-26T15:05:01Z", "state": "ready", "waits": "its merge"},
{"for": "34m7s", "id": "novox/mesh-catalog@8c5f27627b67", "number": 502, "since": "2026-10-26T14:48:20Z", "state": "merged"},
{"for": "19h50m33s", "group": "fix/1945-credential-when-row-without-seal", "id": "novox/mesh-delivery@61196c99e6a4", "number": 46, "since": "2026-10-25T19:31:54Z", "state": "held", "waits": "a person: merged without a passing check: only a person decides what follows"}
]}
@@ -0,0 +1,3 @@
[
{"composed": "asked as mesh/check 3fad1982742a", "composed_gate": true, "id": "fix/1945-credential-when-row-without-seal", "members": ["novox/mesh-controller@fbf14b68dbab (held)", "novox/mesh-delivery@61196c99e6a4 (held)"], "state": "held", "why": "a member is held: novox/mesh-controller@fbf14b68dbab (merged without a passing check: only a person decides what follows); novox/mesh-delivery@61196c99e6a4 (merged without a passing check: only a person decides what follows)"}
]
@@ -0,0 +1,319 @@
---
layer: to-be
status: in-progress
code: [mesh-catalog modules/mesh-issues, mesh-catalog modules/claude-code, mesh-catalog modules/messenger, mesh-sdk]
updated: 2026-10-11
decisions:
- "ADR 0275"
- "ADR 0278"
- "ADR 0279"
- "ADR 0281"
- "ADR 0284"
- "ADR 0285"
- "ADR 0291"
- "ADR 0294"
- "ADR 0300"
---
# 53 — The issue tracker
**The mesh keeps its issues in a module of its own, `mesh-issues`, one instance on the control-node, holding the seat
`issue-tracker`. It hands out every
issue's number from one counter that two callers can never both advance, holds each issue's status and links, refuses
a change the development cycle would refuse, and announces every change on the bus. An issue lives whole in the tracker, its report, diagnosis and
resolution included; hq keeps no issue content.**
(ADR 0275,
from research 042;
ADR 0291 for an issue whole in the tracker.)
An **issue** is a question about the mesh that someone must answer. The **issue tracker** is the module that holds
issues, named `mesh-issues`; the seat it holds is `issue-tracker`. The glossary holds both words.
**Acceptance.** An agent opens an issue and gets the next number; two agents opening at the same moment never get the
same one.
This document designs slice 1 in detail. The slices after it are listed in [§ 8](#8-the-slices-after-the-first),
each with what the operator sees when it is done.
## 1. The parts
```
control-node
mesh-issues (seat issue-tracker, held once for the mesh)
├ its process, launched by the node runtime
├ bucket issues one key per issue, by number; a short history per key
├ 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, set, tracking,
│ served by the control-node's tool runner
├ 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)
claude-code (on the same node)
└ claude_code_judge one question answered by the agent with no tools (§ 9)
any agent session, on any node
mesh MCP server ── mesh_search, mesh_describe, mesh_call ──▶ the verbs, over the bus
```
The module is written in Go against the SDK. It is built on the model of hits, a headless issue tracker on NATS, and
carries none of its code; its README says so.
## 2. The seat and its verbs
The module declares the seat `issue-tracker` at mesh scope and claims it exclusively
(ADR 0126): there is one tracker per mesh. Its verbs are
the seat's tools, served through the tool runner and announced on the bus
(ADR 0197), so an
agent finds them with `mesh_search` ("open an issue") and calls them as `issue-tracker.<verb>`.
| Verb | Takes | Does |
|---|---|---|
| `open` | a title, the report as text, optionally `located-in` and a folder path | checks the issue, then takes the next number and writes it; returns the number. Refuses an empty title or report, and an owner not on the registered list |
| `get` | a number | the issue whole: fields, notes, links |
| `list` | optionally a status, an owner, a phrase | compact rows, newest first, short enough to read in a chat: number, status, owner, title, and the worker with since and whether it is stale |
| `move` | a number, a status, and what that status needs; `by` for `diagnosing` and `fixing`, and `take-over` with a reason to take an issue over from another worker | 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) |
| `tracking` | optionally condition keys | for every condition key an issue links, the issues that link it and where each stands: status, needs, worker, fix (§ 12) |
| `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 |
Each verb's description says in plain words what it does and what it refuses, without the mesh's internal names, so
an agent picks the right one from `mesh_search` alone. A refusal says what was wrong and what would be accepted.
**The caller is the bus credential, never an argument.** The runtime hands the module the identity of each call's
caller; the module records that identity as who acted. No verb takes an actor. `by` on `move` is not one: it names
the issue's worker, who works on it, as the worker states it
(ADR 0284).
## 3. What one issue holds
- `number` (its key), `title`, `status` (`open`, `diagnosing`, `located`, `fixing`, `resolved`, `wontfix`), `opened`,
`closed`;
- `worker`, while it is `diagnosing` or `fixing`: who works on it as who@node, `since` when, and `active`, the last
time a caller did anything on it (§ 5);
- `located-in`: entries of the registered list of owners (repositories and modules), never free text;
- `fixed-by` (pull requests or commits), `amended-design`, `replay` or `replay-none`, and a reason for `wontfix`;
- `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 (the issue **tracks** that condition, § 12) or a delivery;
- `documents`: the report, and a diagnosis and a resolution when written, each with its author and date, revised in
place with its history kept (ADR 0291);
- `record`: the path of the issue's folder in hq, while one exists; none is opened any more, and the folders are
imported into `documents` and removed (§ 8, slice 6);
- `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
registers.
**An issue that has a folder in hq** kept its status in that folder's frontmatter until the import of slice 3 ran;
until then the tracker held its number, title and `record`, took its notes and links, and `move` refused it with the
reason: its status lives in its folder. Since the import, every issue is whole in the tracker, folder or not, and a
report in hq carries no status key. No status is written in two places for one issue.
## 4. State, the counter and events
**Buckets the module declares, the controller creates**
(ADR 0201).
The manifest's `state` names two buckets, `issues` and `counters`, with their options: `issues` keeps a short history
per key, `counters` one value per key, neither expires. The module never creates a bucket. One issue is one value; at
the 256 KiB a value may be, a note that would push an issue past it is refused with that reason (the largest issue
folder in hq is 25 KiB).
**The counter is advanced by compare-and-set.** Beside `get`, `put`, `delete`, `keys` and `watch`, the runtime's state
verbs hold two writes that are refused when another writer was first, in the runtime and the SDK's state interface
together (ADR 0259):
- `create` writes a key only if it does not exist;
- `update` writes a key only if its revision is still the one the caller read, and returns the new revision.
The SDK's `Increment` reads a counter and writes the next number with `create` or `update` at the revision it read,
trying again a bounded number of times when another writer was first. The counter is started once, by `create` at
the number before the first, so it never starts twice. `open` then runs in this order:
1. validate the issue; a refused issue spends no number;
2. read `counters/issue` and its revision;
3. `update` it to the next number at that revision; if another caller moved it first, the write is refused, and
`open` reads again and retries, a bounded number of times;
4. `create` the issue's key; an existing key is a fault the module reports loudly and never overwrites.
So the counter does not rely on there being one instance: two instances, or two calls racing in one, still never hand
out one number twice. **The counter starts** above the highest issue number on `main` and on every open branch of hq
at the moment the module is switched on. Numbers already skipped stay unused, and a number never moves.
**Every change is an event the module declares** under `emits`
(ADR 0041): `opened`, `moved`, `noted`, `linked`. Each carries
the number, what changed, and who changed it, so the events are the audit trail, and anything watching the bucket or
the events sees a change as it happens. `moved` also carries the worker a move names, as `by`, with `since`, and who
acted as `caller`; on a take-over `took-over-from` and the reason, and `released` when a move ends the work. No
event carries a secret, and an issue holds none.
## 5. The cycle's rules, at the write
`move` refuses what `cycle.py` refuses at merge today, and the rules playbook 03 states:
| The issue moves to | Needs |
|---|---|
| any status | one of the six |
| `diagnosing` or `fixing` | `by`, the worker as who@node; refused while another worker is current, naming them, unless `take-over` gives a reason, which is noted; a worker with nothing done on the issue for `worker-stale-after` (24 hours) is stale and may be taken over from without one |
| any other status | no `by` and no `take-over`; the move ends the work |
| `located` | `located-in` names at least one registered owner |
| `resolved` | the issue is at `fixing` (or already `resolved`, to correct what it holds); `located-in`, and `fixed-by` saying what fixed it |
| `resolved`, for 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) | `replay` (an id from mesh-lab's replays register) or `replay-none` saying why none is possible (ADR 0237) |
| `wontfix` | a reason |
A refused move changes nothing and says which rule refused it. The rows for `diagnosing`, `fixing` and `resolved` are
ADR 0284's:
resolved only from `fixing` is the check that an agent said it started the fix. `cycle.py` checked the folders that carried status until
slice 3; since then it refuses a status key in any report, and judges no issue's status.
## 6. How the operator and agents use it
Through the mesh's tools first. An agent opening an issue calls `issue-tracker.open`, gets the number, and, for a design
or governance issue, writes `04-ISSUES/NNN-slug/` under it with the number in the report. Playbook 03's first step
changes to that the day slice 1 is live; nothing else in playbook 03 changes in slice 1. `list` answers "what is open"
in one call, from any session, and the responder on a channel answers from it once the seat exists.
**An agent says when it starts.** Before it starts work on an issue, an agent moves it to `diagnosing` (to find where
it lives) or `fixing` (to fix it) with `by`, its own name and node. `list` then shows who works on what, and a second
agent is refused before it starts the same work. An agent that keeps working notes what it finds, which keeps it the
worker; one that stops moves the issue on, or is stale a day later.
## 7. How it is checked
| Rule | Checked by |
|---|---|
| Two opens at once never share a number | the module's test over a real bus: many concurrent `open` calls, from two instances, yield distinct consecutive numbers; the runtime's test that `update` at a stale revision is refused |
| A refused open spends no number | the module's test: an invalid open, then a valid one, gets the next number |
| An issue's key is never overwritten | the module's test: a `create` on an existing key is refused and reported |
| 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 |
| A worker is named on a move to diagnosing or fixing, another is refused while it is current, a take-over is noted, a stale one may be taken over from, and resolved is reached only from fixing | the rows of ADR 0284's "How it is checked" |
| An issue's delivery line follows the delivery; ready to verify once; a stuck fix said once and cleared | the rows of ADR 0281's "How it is checked" |
| A linked pull request is a fix; a closed unmerged one is withdrawn; delivered and not resolved is said once; an abandoned issue is flagged | the rows of ADR 0294'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'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's "How it is checked" |
| An issue's documents are kept whole, with author, date and history, and the import of hq's folders loses nothing (ADR 0291) | the module's tests of `documents` and of the import, one case per document kind; the comparison of every issue with its folder before the folders are removed, written on issue 427. Tracked by issue 427 |
| 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 |
## 8. The slices after the first
| Slice | Delivers | Done when the operator sees |
|---|---|---|
| 1 | The `mesh-issues` module: `open`, `get`, `list`, `move`, `note`, `link`; a counter by compare-and-set in the SDK; playbook 03 takes its numbers from it | an agent opens an issue and gets the next number; two agents opening at the same moment never get the same one |
| 2 | Decision and research numbers from the tracker, each its own key in `counters`; a number is reserved when a branch starts; the knowledge base's `reserve` asks for them ([to-be 55](55-the-knowledge-base.md) § 5). Tracked by issue 424 | two agents writing a decision at once get two numbers, and no hq pull request is renumbered |
| 3 | `import`: every issue folder in hq read through the records module and written as an issue with its own number, idempotent and showing what it would change before it does (built and run 2026-10-10; one pull request changed playbook 03 and `cycle.py` and added dated notes to ADR 0019, 0080 and 0275, and once every folder was checked against the tracker a second removed the status keys from every report, as § 3 says); then the four consolidation passes of research 042 (fixed, duplicates, owners, grouping; the duplicates pass is the duplicate check's `groom`, § 9, judged rather than by shared words), each a list confirmed before it is applied through `move`, `link` and `note`, and an audit of `fixed-by` against the forge | the not-closed list comes from one call and shrinks by what was already fixed or duplicated, each with its reason |
| 4 | Conditions and pull requests linked: an issue links a condition by its key, and every message about the condition says the issue and where it stands (§ 12; the controller does not show the link, ADR 0285); a merged pull request notes every issue it names; after a week of that, the controller opens one issue per `healer-wanted` condition key by itself | a condition's message names its issue, and a merge notes the issues it names |
| 5 | A read-only view onto the `issues` bucket over the bus | the operator reads issues in a browser without an agent |
| 6 | An issue whole (ADR 0291): the `documents` of § 3; an import of the 361 folders of hq into them, repeatable, keeping each issue's number and dates; once every issue is compared with its folder, one pull request rewrites the 765 path mentions in hq to "issue N", removes the folders, makes `records.py` refuse a new path into `04-ISSUES/`, and changes playbook 03; `open` loses `record`. Tracked by issue 427 | `issue-tracker.get` answers an issue's report and diagnosis, and hq has no `04-ISSUES/` |
Once slice 1 runs, each slice after it is opened as an issue in the tracker, so the tracker carries its own work.
## 9. The duplicate check
**When an agent opens an issue that repeats one not resolved, the tracker closes the new one as its duplicate
within a few minutes, keeping its report on the older one; an issue that repeats nothing gains a note that it
was checked** (ADR 0278).
- **Started by the tracker's own `opened` event**, which the module consumes. Only an issue still at `open` is
checked, against every older issue at `open`, `diagnosing`, `located` or `fixing`; resolved and wontfix issues are never
offered or changed.
- **Judged in two questions** to the agent module's `claude_code_judge` on the tracker's node: a shortlist of at
most five by titles, then a verdict on the reports (`duplicate`, `related` or `none`, whether it is certain,
and why). The judging session runs as the node's agent account under its licence, with no tools, in safe
mode, and answers within 25 seconds.
- **A certain duplicate**: the new issue is linked `duplicates`, its title and report become a note on the older
one, its verdict a note on itself, and it moves to `wontfix`, "duplicate of N". **Related, or not certain**: a
`relates-to` link and a note on both. **None**: one note. The older issue never moves. Each step goes through
the tracker's own `note`, `link` and `move`, and is recorded as by `duplicate-check`.
- **Once per issue.** The verdict note is written first, in a fixed first line; an event delivered again does
only the steps not yet done.
- **Loud when it cannot judge.** One note on the new issue, and the event is not taken: after five deliveries
the controller raises `max-deliveries` on the tracker's consumer.
- **The backlog**: `groom` runs the same judgement over every issue not resolved, as a dry run kept in the state
`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).
- **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.
## 10a. The record is a log of operations
**The tracker's record is its log of operations, and its bucket is that log's fold** (ADR 0300, resting on ADR 0297):
every write is one operation appended under the issue's number before the bucket is written; the bucket is rebuilt
from the log on start; the events announce and the log is the audit trail; `list` answers from memory with a ranked
search and a `before` cursor. Built under issues 501 and 502.
## 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). Since ADR 0294, an issue's fixes are the pull requests `fixed-by`
names and every pull request linked to it; a pull request closed without merging is withdrawn; an issue left in
fixing an hour after its last fix is delivered is said once, "fixed and delivered, not resolved"; and a stale worker
with no pull request open is **abandoned**.
- 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 `fixing`: `move` to the
same status, with `by`, 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.
## 12. Which issue tracks a condition
**Every message about a condition says which issue tracks it and where that issue stands, or that none does**
(ADR 0285).
- **An issue tracks a condition** through a link of kind `condition` naming the condition's key exactly; set by
whoever opens the issue or works on it, never by the triage or the controller. An agent that opens an issue for a
condition links its key at once, and one that silences a condition because an issue tracks it links that issue
first.
- **`tracking`** answers, for every key an issue links or only the keys asked, the issues that link it, those not
closed first, at most 10 per key: number, status, severity, needs, the worker with since, active and stale, and
**fix** (`not-merged`, `merged`, `held`, `failed`, `unheard`, `delivered`, or empty), from the facts of § 11. It
carries no title, report or note, and changes nothing.
- **The router** (to-be 45 § 5) asks it over the bus, outside its work, at most once a minute, and says from it the
**tracking line** in every condition's message; the words and the verdict it changes are its own (to-be 45 § 5).
The controller learns nothing of issues.
## What it leaves out
Priorities beyond severity, 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. Who works on an issue, listed here until
2026-10-10, is settled by ADR 0284.
@@ -0,0 +1,363 @@
---
layer: to-be
status: in-progress
code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog]
updated: 2026-10-17
decisions:
- "ADR 0348"
- "ADR 0349"
- "ADR 0201"
- "ADR 0113"
- "ADR 0158"
- "ADR 0360"
- "ADR 0362"
---
# 57. Building config and provisioning
**The work of ADR 0348 (config is a domain of its own) and ADR 0349 (provisioning), broken into
packages small enough that each ends at something that can be seen to work, in the order their
dependencies allow.** The two decision records are the authority on *what* is built; this document holds
only the packages, their order, their sizes and their proofs, and is wrong the moment it disagrees with
them. It is the shape [design 38](38-building-the-operators-machine.md) gave the operator's machine.
## How this is built
**Each package is one issue in the issue tracker, and one pull request per repository it touches.** The
issue carries the package's number here in its title, links the packages it waits for with `depends-on`,
and links ADR 0348 or ADR 0349. At most five packages are worked at once, each by its own agent in its
own worktree; a package starts once every package it depends on is merged. Every pull request is
reviewed independently at its head and merged only with its checks green.
**It ships as one release.** The operator's decision of 2026-10-14: one delivery, not one per package.
Each of mesh-controller, `mesh-host`, mesh-sdk, mesh-catalog and hq has the branch
`feat/57-config-and-provisioning`, and every package's pull request merges into that branch, never into
main. When the last package is merged, the whole is rehearsed in the lab: the rollout, the moves of data in
their order, and every proof that is run live. Then each repository's branch reaches main in one pull
request, and the five, sharing their branch's name, are one delivery group (ADR 0239 §2), judged as one
future state. The five are merged within one merge window, so they are one batch and one rollout (ADR 0276
§1, §2), in an order declared with `after:` lines in each pull request (ADR 0239 §11): mesh-sdk, then
`mesh-host`, then mesh-controller, then mesh-catalog, then hq. The release holds every package but T6, R1 and R2:
the media catalogue and the applications live in repositories of their own and move after it, and only
then can the old forms be refused (R1) and the record say what runs (R2).
**The moves of data run after the delivery, in this order, each a verb that says what it did:**
placement out of the settings layers (F2), the settings layers into mesh-config (K5), the secrets into
the vault, nothing rotated (V3), then the providers one by one (B2, B3). Each is rehearsed in the lab
first, and each is checked by reading back through the module that owns the data before anything
points at it.
**What a release costs.** A proof that is run live (dunst on a workstation, a secret reaching its file, a
database consumer connecting) is first seen in the lab rehearsal, and a fault found then holds the
release, not one package. A merge's builds go to every machine at once (ADR 0360), so the release has no
first node to hold it: after the pull requests' checks, the lab rehearsal is the only live check before it
reaches every node. A machine that fails to apply it, or whose provisioner reports a module's files failed
(ADR 0362), is named in the rollout's end and raises apply-failing, and is fixed forward with a new pull
request into main; one that says nothing within fifteen minutes is named silent and gets its builds from
the backlog rollout once it is heard from (ADR 0360 §10).
**Nothing breaks on the way.** ADR 0349 clause 17: each file is filled the old way or the new way, never
both, by the form of its manifest, and the old forms stay accepted until the last module has moved.
Inside the release this still holds: the media catalogue and the applications kept in their own
repositories move after it, so the old forms stay accepted until they have (R1).
**Where it stands, 2026-10-17** (the Milestones section below). The packages' branches were merged into
main of mesh-controller, the node-engine (`mesh-host`) and mesh-catalog on 2026-10-16. T6 moved after them, as
planned above, and is resolved. B3 was left out as well, so B3, R1 and R2 are what is left.
## What exists already, measured
Counted 2026-10-14 at mesh-controller `1601cb1`, the node-engine (`mesh-host`) `1a26778`, mesh-sdk `a2d2af0`, mesh-catalog
`ebf7f37`.
| Piece | Today | Becomes |
|---|---|---|
| the controller's composer | `catalogue.Resolution.compose` fills every namespace but `${secret:}` in one loop (`declaration.go` ~1000–1085); the namespaces and their fields are tabled in `fillers` | fills placement only, for a module that has moved |
| settings | tables `settings`, `settings_history`; the `settings` verb (`modules.go` 407–760); proposals and asks (`proposals.go`, 975 lines); trusted files; `takes-settings` merging; about 4 100 lines and 2 500 of tests | the `mesh-config` module and its buckets; the verb `config` |
| placement kept as settings | `places`, `accesses` and the kept networks are read from settings layers | facts of an assignment, the controller's own |
| secrets | the controller mints, seals and accepts every secret (`internal/secrets`, `inventory/secrets.go` 1 125 lines; tables `secret`, `module_secret`, `secret_ask`, `operator_key`); the vault (`mesh-vault`, TypeScript) is read-only and never asked | the vault, in Go, holding and minting; handing out on request and reply |
| bindings | grant files and binding files composed by the controller (`declaration.go` 750–818); `${bound:}` 117 uses | each provider answers its consumers; the vault mints the pair's credential |
| contributions | `${contribution:}` (4 collectors), `${shell:}` (3), `${environment:}` (1); 22 contributors | collections, filled by the provisioner client |
| file text | 225 file resources in 88 modules, all inline (about 193 KB); 12 modules keep a second copy in 34 files with a test that the two agree (ADR 0349's context counted eleven; the twelfth is lemurs) | template files under `files/`, packed by the module's build |
| packing a directory | the builder's `archive` artifact packs a tree and publishes it by digest; the node-engine fetches and unpacks it | the same mechanism carries a module's templates |
| the node-engine's file writing | `applyFile`, `applyBlock`, `applyInto`, create-once, drift written back, `Keep` backups, restart-on and reload-on, container reads | the same code, reached also through a narrow local write verb |
| the node-engine's local verbs | one socket (`cli.sock`), peer-checked by uid, forwarding to the controller; no local write | a second, narrow socket for the provisioner client |
| bus identities | one principal per node for the tool runner, one per module with a process of its own | the provisioner client gets one of its own |
| the SDK | KV, watches, asks, caller proof; no sealing helper; the TypeScript `provisioner` is a database-provider harness | a seal box, provider helpers, and readers for config, bound values and secrets |
**Two measurements decide the order.** The builder can already pack a module's directory, and the
controller already fills every placeholder at registration time from text; so template files can ship
first, the controller reading the text back from the packed `files/`, and nothing on a node changes. And
the node-engine's writing code needs no change in kind, only a second way in; so the provisioner client
reuses it rather than writing as root itself.
## The order the work allows
```
package waits for package waits for
F1 — P1 —
F2 — P2 P1
F3 — P3 F3
F4 — P4 P3
T1 — P5 P2, P4, T1, K3
T2 T1 P6 P5
T3 T4 T5 T2 P7 P6
T6 T1 V1 F1
K1 T1 (same file) V2 V1
K2 K1 V3 V2
K3 F3, K2 V4 V3, P5
K4 K3 V5 V3, V4
K5 F2, K4 B1 P5, V5
K6 K1 B2 B1
K7 K5, P6 B3 B2
L1 P6 L2 L1
S1 P6 R1 T5, T6, K6, K7, P7, V4, B3, L2, S1
R2 R1
```
The issue tracker carries these as `depends-on` links (thirteen issues also link what they wait for
through another package), so the packages ready to start are those whose linked packages are all merged
into the release's branches. An issue's "Waits for: … delivered" means merged into the release's
branches: nothing is delivered before the release. The first wave touches different repositories and different files:
**T1, F1, F3, P1 and F4**. K1 waits for T1 because both change `internal/catalogue/manifest.go`; P3
waits for F3 because both change `internal/broker`; F2 joins as soon as a slot is free.
## 0 — Foundations
**F1 (issue 650) — A seal box in the SDK.** *mesh-sdk. Half a day.* The ADR 0206 box (X25519, HKDF-SHA256,
AES-256-GCM) moves from the licence manager into `mesh-sdk/go/seal`, with a TypeScript twin, and the
licence manager uses it. **Proof:** a byte-for-byte test against the licence manager's existing sealed
values; the licence manager's tests pass unchanged.
**F2 (issue 651) — Placement leaves the settings layers.** *mesh-controller. Two days.* `places`, `accesses` and the
kept networks become facts of an assignment, held by the controller beside the assignment, with a
migration moving each existing layer's keys; `Settle` stops letting a setting replace a provider's
served keys. **Proof:** composition of every node before and after the migration is byte-identical;
the settings layers hold no placement key afterwards.
**F3 (issue 652) — The assignments and the modules' config declarations on the bus.** *mesh-controller. Two days.*
The controller keeps the bucket of assignments design 54 decides (which module on which node), and a
bucket of each registered module's declared config values, written when they change, with one event per
write. mesh-config needs both to resolve. **Proof:** a test against a real bus: an assignment and a
registration each change exactly one key and raise one event.
**F4 (issue 653) — The words.** *hq. Half a day.* The glossary gains *config value*, *predefined key*, *provision*
and *provisioner client*, *collection*, and the ownership words *owned*, *seed* and *managed*. The word "setting" goes on *config
value*'s *Not:* line only in R2, once the 67 documents still using it are reworded; until then the
glossary says it is the older word. **Proof:** `words.py` passes.
## 1 — Template files (ADR 0349 clause 2)
**T1 (issue 654) — A file resource may name a template.** *mesh-controller. Three days; the largest package of the
stage.* A file resource takes `"template": "files/<name>"` in place of `content`; the module's build packs
`files/` into an archive artifact of its own (the existing `archive` packing); registration and `Resolve`
read each template's text back from the packed tree into `content`, so composition and the node-engine
are unchanged; `module check` and the composition check read templates from the repository tree beside the
manifest; a file naming both `template` and `content` is refused. **Proof:** composition tests: a module
with its text in `files/` composes byte-identically to the same module with it inline; the composition check
judges a change to a template alone.
**T2 (issue 655) — The twelve modules with copies.** *mesh-catalog. A day.* adwaita, clipmenu, dunst, feh,
gnome-keyring, i3, i3status-rust, lemurs, picom, rofi, screen-lock and xdg move to their templates; the
34 inline copies and the `sameAsSource` tests go. **Proof:** each module's composed declaration on its
nodes is unchanged (the composition check's plan says no file changes).
**T3 (issue 656) — The desktop modules.** *mesh-catalog. A day.* Every remaining module assigned only to
workstations. **Proof:** as T2.
**T4 (issue 657) — The servers' modules.** *mesh-catalog. Two days.* Every remaining module with a build. **Proof:**
as T2.
**T5 (issue 658) — The ten modules without a build.** *mesh-catalog. A day.* ca-trust, de-spiegel, invoicing,
local-model-consumer, matrix, node-env, only-office, sshd, step-ca and supabase gain an archive build for
their templates. **Proof:** as T2; afterwards no manifest in the catalogue carries file text.
**T6 (issue 659) — The media catalogue and the applications.** *mesh-media-catalog and each application's
repository. Two days.* **Proof:** as T2, per repository.
## 2 — Config (ADR 0348)
**K1 (issue 660) — A module declares its config.** *mesh-controller. Two days.* The manifest takes `config` (type,
description, validation, trusted, `predefined`, `default` or `required`), `takes-config` and
`${config:<key>}`, with `settings`, `takes-settings` and `${setting:}` read as aliases; registration
refuses everything ADR 0348's "How it is checked" lists for registration: a value without type or
description; a value with neither a default nor `required`; a default failing its validation; an
object or list not trusted; a predefined key not on the list or of another type (with K2); an untrusted
value falling back to a trusted key; a value neither in a template nor read at runtime; a placeholder
naming an undeclared value; a `reload-on` or `restart-on` naming an undeclared file; a moved module's
mergeable file not naming its keys; a mesh word or `puid`/`pgid` in `takes-config`. **Proof:** the registration refusal tests, one per
rule; every manifest in the catalogue still registers.
**K2 (issue 661) — The list of predefined keys.** *mesh-controller and mesh-catalog. A day.* `predefined.json` at the
catalogue's root, with `font-size` and `agent-managed-settings` first; the build validates it and every
manifest naming a key; the merge check refuses removing a key still named or holding a value; the newest
trunk build's list is published. **Proof:** the merge check refuses a planted removal; an older build's
list does not replace a newer one.
**K3 (issue 662) — The mesh-config module.** *mesh-catalog. Four days.* A Go module holding the seat `mesh-config`:
the buckets `mesh-config_values`, `_resolved`, `_predefined`, `_proposals`; the four sources resolved from
F3's assignments and declarations; the verb `config` (set, clear, list, history, narrowed by module and
node); validation; the hand-act log; `config-changed`; only its account writes its buckets. Terminal-only
for trusted values until K4. **Proof:** its tests resolve each source in turn; a test against a real bus
refuses a put from another account.
**K4 (issue 663) — Trusted values are proposed and approved.** *mesh-catalog. Two days.* mesh-config asks under its
own name, with its own grant and cap of open asks, binds each option to the level, key and value, records
the approver beside the value, and checks the proposal on Approve (ADR 0277's rules, ADR 0348 clause 8).
**Proof:** a proposal test end to end with a stand-in channel; an Approve against a changed value is
refused.
**K5 (issue 664) — The values move, and `settings` forwards.** *mesh-controller and mesh-catalog. Two days.* Each
existing layer moves into mesh-config (a node layer to `module.<node>.<module>`; a whole-mesh layer to
mesh config, a manifest default or each node, as ADR 0348's consequences say; claude-code's managed
settings to `agent-managed-settings`), recorded with why; for a module not yet handed to the provisioner
the controller reads `mesh-config_resolved` where it read its layers (ADR 0349 clause 17's old way,
an exception to ADR 0348 §9 that R1 removes), and `settings` answers by forwarding to `config`. **Proof:** composition of
every node before and after is byte-identical; `settings` and `config` list the same values.
**K6 (issue 665) — The catalogue says config.** *mesh-catalog. A day.* The 15 modules using `${setting:}`, the 9
declaring settings and the 4 with `takes-settings` move to the new names and the full declaration.
**Proof:** the catalogue's check finds no old name in a moved module.
**K7 (issue 666) — The operator's acceptance on dunst.** *live. An hour.* With every level cleared the first
workstation's dunstrc says 10; a mesh font size, then that node's font size, then dunst's own value on
that node each appear in its dunstrc within the call's answer, and the history shows each change with who and why. Needs P6.
## 3 — The provisioner (ADR 0349 clauses 3–5, 11, 13)
**P1 (issue 667) — The node-engine knows who owns a placed file.** *`mesh-host`. A day.* `declaration.File` gains
`owner-at-runtime` (`owned`, `seed`, `managed`), mapped onto today's paths (drift written back,
create-once, `into`), and the declaration gains the list of files the provisioner fills. **Proof:** the
apply tests: each ownership behaves as its old form did.
**P2 (issue 668) — The narrow local write verb.** *`mesh-host`. Two days.* A second unix socket accepts a write only from
the provisioner client's account (`SO_PEERCRED`) and only for a path the declaration names as filled,
writes through `applyFile`, `applyBlock` or `applyInto` with `Keep`, and fires restart-on, reload-on and
container reads as an apply does. **Proof:** tests: an undeclared path and another uid are refused; a
write under `reload-on` reloads its program once.
**P3 (issue 669) — The provisioner client's own bus account.** *mesh-controller. A day.* A principal of its own per
node for `node-provisioner`, granted to read its node's keys in each domain's bucket, publish
`prov.applied.<node>.>` and ask the vault; nothing else. **Proof:** the grants test, and a test against a
real bus refusing a read of another node's keys.
**P4 (issue 670) — The provisioner's server.** *mesh-catalog. Two days; after P3.* A Go module holding `mesh-provisioner` once:
the registry of which domain answers which kind, the buckets `_registry`, `_needs`, `_applied` and
`_collect`. **Proof:** its tests; the buckets listed live.
**P5 (issue 671) — The provisioner's client.** *mesh-catalog. Four days.* A Go service on every node, outside the tool
runner: fetches a module's templates by digest, records needs, watches each owner's keys for its node,
fills only when every value is there, writes through P2, reports `prov.applied`, writes an `owned` file
back on drift, removes a module's files and needs when it leaves, fills from current values when its node
comes back. Its first owners are placement (from the declaration) and config (from
`mesh-config_resolved`). **Proof:** the client's test list in ADR 0349, against a real bus.
**P6 (issue 672) — The controller hands a module to the provisioner, and the rollout waits for it.** *mesh-controller.
Two days.* For a module marked moved, the declaration carries placement, the templates' digest and the
filled-files list, and the controller runs no other filling pass for it; a rollout counts the module applied
on a machine only after the node-engine's report and that node's `prov.applied` for the new build, and a
failed `prov.applied` is a failed apply (ADR 0362). dunst moves first. **Proof:** composition and rollout
tests; dunst on the first workstation filled by the client, live.
**P7 (issue 673) — The node-engine stops writing placed files from the declaration.** *`mesh-host`. A day.* For a moved
module the engine writes its filled files only through P2, and removes as an orphan only what the verb
placed. **Proof:** the apply tests; no placed file written twice in a day's journal on the
first workstation.
## 4 — Secrets (ADR 0349 clauses 6–11, 16; issue 647)
**V1 (issue 674) — The vault in Go, holding secrets.** *mesh-catalog. Four days.* `mesh-vault` is rewritten in Go:
its own store, a copy of each secret sealed to the operator key, `get` on request and reply sealed to the
asker's key (F1's box; the node-engine's key format is settled here and written down), `secret-changed`
without a value, no secret in any bucket. **Proof:** the vault's test list in ADR 0349.
**V2 (issue 675) — The vault's verbs.** *mesh-catalog and mesh-controller. Three days.* `secret accept`, `secret-ask`
(with ADR 0277 §24–§27's record and bounds in the vault's store), `issued` (ADR 0305, the provider's tool
answering the vault alone) and `rotate` become the vault's; the controller's verbs forward. **Proof:** the
`secret-ask` and issuing provider tests in ADR 0349.
**V3 (issue 676) — The secrets move to the vault.** *mesh-controller and mesh-catalog. Two days.* Every row of
`secret`, `module_secret`, `secret_ask` and the operator key's public half are copied into the vault as they are,
nothing rotated, and read back through the vault before anything points at it. **Proof:** for every
secret, the vault's sealed answer opens to the same value the controller's does.
**V4 (issue 647) — A node's secrets come from the vault.** *mesh-catalog, mesh-controller, `mesh-host`.
Three days.* The client fills `${secret:}` from the vault's `get`; for a moved module the controller seals
nothing; the core's credentials travel sealed by the vault to their node and the node-engine opens them.
**Proof:** a secret rotated in the vault reaches its file without a new declaration, live.
**V5 (issue 677) — The vault mints pair credentials.** *mesh-catalog. Two days.* One value per provider assignment and
provision, remade for all holders when the vault sees a consumer's need recorded or deleted (ADR 0158
§4–§6). A need recorded for a bind the vault already holds from V3 adopts that value and remakes
nothing, so the move itself rotates no credential. **Proof:** the provider test in ADR 0349, with a stand-in provider.
## 5 — Bindings (ADR 0349 clause 12)
**B1 (issue 678) — A provider's helper in the SDK.** *mesh-sdk. Two days.* Go: watch the needs for a kind, write one
entry per consumer, refuse a derived value for a multi-holder consumer, answer from the vault's pair
credential. The TypeScript `provisioner` harness is renamed so the word means one thing. **Proof:** its
tests with a stand-in bus.
**B2 (issue 679) — PostgreSQL answers its consumers.** *mesh-catalog and mesh-controller. Three days.* The first
provider moves; its grant files and binding files are retired. **Proof:** every database consumer on
every node connects after the move with the credential it had, adopted by the vault as V5 says, live.
**B3 (issue 680) — The other providers.** *mesh-catalog. A week, one pull request per provider.* **Proof:** as B2, per
provider; afterwards the controller composes no grant file.
## 6 — Collections (ADR 0349 clause 13)
**L1 (issue 684) — A collection is declared and filled by the client.** *mesh-controller and mesh-catalog. Three days.*
A collector requires a collection; a contributor provides an item from a template; the client fills the
collector's template from `_collect`. `${contribution:}`, `${shell:}` and `${environment:}` read as
collections. **Proof:** zsh's file holds every placed contributor's item and loses one whose contributor
leaves.
**L2 (issue 681) — The collectors and contributors move.** *mesh-catalog. Two days.* zsh first, then the bar, the
display session, the hotkeys, xorg, power and node-env, with their 22 contributors. **Proof:** each
collector's composed file is unchanged at the move.
## 7 — A module's code asks at runtime (ADR 0349 clause 14)
**S1 (issue 685) — The SDK's readers.** *mesh-sdk and mesh-catalog. Two days.* `config`, `bound` and their watches,
answered by the node's client for what the manifest declares. `secrets` refuses until the module runs
under a bus account of its own, which waits on issue 569 and its decision. **Proof:** the SDK's test in
ADR 0349; claude-code reads its managed settings through it.
## 8 — The old forms go (ADR 0349 clause 17)
**R1 (issue 682) — Refused and removed.** *mesh-controller, `mesh-host`, mesh-catalog. Three days.* Once every module of
every catalogue has moved: the catalogue's check refuses inline text, `settings`, `${setting:}` and the
old contribution forms; the controller's filling passes but placement, its settings, secret and binding
code and tables (a new migration) go; the node-engine keeps opening sealed values for the core's files
only; the controller stops reading `mesh-config_resolved`. **Proof:** the controller's test that its
code opens no secret outside genesis and reads no config value; the absence of the
removed code.
**R2 (issue 683) — The record says what runs.** *hq. Two days.* The as-is designs are written, designs 27 and 32 are
amended, the 67 documents that still say "setting" in running prose are reworded so the glossary can
retire the word (F4 left it off the *Not:* line for them), design 49 lists the Config domain, ADR 0348's and 0349's built status is said, and this document's status becomes `implemented`.
## Milestones
The packages above were built as one delivery, not milestone by milestone; the milestones group them by what
each makes true. Whether a milestone's issues are delivered is read from the issue tracker (playbook 07).
| # | Outcome | Packages | Issues |
|---|---|---|---|
| 1 | Config, provisioning, secrets, bindings, collections and runtime asks run the new way; the old forms are still accepted | every package but T6, B3, R1, R2 | 647, 650 to 679 but 659, 681, 684, 685 |
| 2 | The media catalogue and the applications carry their file text as templates | T6 | 659 |
| 3 | The other providers answer their consumers; the old forms are refused; the record says what runs | B3, R1, R2 | 680, 682, 683 |
## What is deliberately not here
- **Secrets in a module's own code** wait on issue 569 (a module's tools run as the tool runner's
account) and its decision; S1 ships without them.
- **Release separated from delivery** stays issue 644's, and **the bus's replicas** issue 645's.
- **Renaming "walk"** is issue 646's.
## How this list is kept true
Each package's proof that runs in its tests is run when it is merged into the release's branch, and its
line here gains the date and the commit; a proof that runs live is run in the lab rehearsal and again
after the release's delivery. A package whose proof fails is not reworded; the failure is recorded under it and the package
stays open. The issue tracker says which packages are worked now; this document says only the order.
@@ -0,0 +1 @@
3a5c4c7c4d9c4143044f9659d4b58cb1d531bcd7
@@ -0,0 +1 @@
{"delivery": {"base": "main", "branch": "feat/current-mesh", "commit": "cc4c8ce12901ba4f4035bef7e660db049a2561f5", "id": "jschoubben/car-hunter@cc4c8ce12901", "issues": [1945], "number": 2, "repository": "jschoubben/car-hunter", "state": "ready", "title": "car-hunter on today's mesh: health check, templates, a network of its own, rebuildable data seeded at start"}, "waits": "its merge"}
@@ -0,0 +1,13 @@
[
{"delivery": "delivered to every machine it should reach", "needs": "none", "number": 1942, "owner": "mesh-controller cmd/mesh-controller/delivery_edges.go", "ready": false, "severity": "major", "status": "resolved", "title": "Today's planner cannot put the controller and the node-engine in one tier, which release 6's switch needs"},
{"needs": "none", "number": 1943, "owner": "mesh-sdk go/delivery, go/catalogue, go/cmd/mesh-hand-compose", "ready": false, "severity": "major", "status": "resolved", "title": "mesh-sdk has no layout for a module's composition on a node, no node want, no composition digest in a want, and still lists the plan verbs"},
{"needs": "none", "number": 1944, "owner": "mesh-host cmd/mesh-host, internal/agent", "ready": false, "severity": "major", "status": "resolved", "title": "The node-engine applies only whole declarations the controller sends, so no node can follow its wants"},
{"needs": "none", "number": 1945, "owner": "mesh-controller cmd/mesh-controller", "ready": true, "severity": "major", "since": "2026-10-24T14:14:53Z", "status": "fixing", "title": "The controller still plans deliveries and pushes whole declarations, so nothing can deliver by wants", "worker": "dev-r6-interface@shanks"},
{"delivery": "fix merged, not delivered: mesh-catalog#475", "needs": "none", "number": 1946, "owner": "mesh-catalog modules/mesh-delivery-gate", "ready": false, "severity": "major", "status": "resolved", "title": "The gate keeps a delivery's states, machine steps and plan calls that no longer exist once plans go"},
{"delivery": "held: mesh-delivery#39: a person: merged without a passing check: only a person decides what follows", "needs": "none", "number": 1906, "owner": "mesh-delivery", "ready": false, "severity": "minor", "status": "resolved", "title": "Release 6 must bring what R5's assigner leaves out: wants mirrored as pulses, the restore ask for a data module and the condition for a held fingerprint"},
{"needs": "none", "number": 1130, "owner": "hq 03-DESIGN/01-to-be/47, 03-DESIGN/00-as-is/04-delivery, 03-DESIGN/00-as-is/13-the-console", "ready": false, "severity": "minor", "status": "resolved", "title": "Design 47's sections still describe delivery plans, batches and the merge window, which ADR 0429 replaces release by release"},
{"abandoned": true, "delivered-not-resolved": true, "delivery": "delivered to every machine it should reach", "needs": "none", "number": 1049, "owner": "mesh-catalog modules/gitea", "ready": true, "severity": "critical", "since": "2026-10-18T02:26:22Z", "stale": true, "status": "fixing", "title": "A group that moves a module delivers its first member before the second merges, so the plan builds the moved module from the old repository and fails", "worker": "fix-chain-cat@shanks"},
{"needs": "decision", "number": 1126, "owner": "mesh-controller", "ready": false, "severity": "minor", "status": "open", "title": "About 49,800 controller code lines stay after the delivery model, against a first limit of 25,000"},
{"needs": "none", "number": 774, "owner": "mesh-controller; mesh-catalog modules/mesh-vault", "ready": true, "severity": "minor", "status": "open", "title": "ADR 0113 clause 6 is not built: the controller, not the vault, mints the bus accounts of modules"},
{"needs": "none", "number": 1021, "owner": "mesh-catalog modules/mesh-delivery", "ready": false, "severity": "minor", "status": "wontfix", "title": "An after: line added by editing a pull request's description is never read, though mesh-delivery's refusal says to add it there"}
]