Author SHA1 Message Date
mesh-tools 1552b0e0a3 Playbooks 07 and 08: remove everything a feature or fix made after its delivery
mesh/delivery checking
mesh/merge-gate out of date: judged against trunk 4e437eb1488f, the trunk is now a8b951f3b488; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk 4e437eb1488f, the trunk is now a8b951f3b488; judged again on a recheck or when it is merged
Feature 57 left over a hundred worktrees and per-issue branches in a
session scratchpad, outside the .work/<slug> the playbook named; a small
fix had no clean-up step at all. Content is checked on main, not hashes,
because gitea_commit lands commits with new hashes.
2026-10-16 15:37:55 +00:00
28 changed files with 727 additions and 1597 deletions
-44
View File
@@ -1,44 +0,0 @@
---
name: hq-cleanup
description: Use when the operator asks to clean up the forge and the working copies — stale or merged branches, old pull requests, finished worktrees, leftover folders, stashes. Triggers on "cleanup", "clean up our repos", "stale branches", "old worktrees", "tidy the forge". Reads first, deletes only what the operator approves, and reports what it cleaned, kept and left.
---
# hq-cleanup
Runs playbook [09 — Cleanup](../../../00-META/process/09-cleanup.md); the playbook is authoritative. This skill
adds the mechanics.
## 1. Take stock (one read-only agent, smaller model)
Brief it to change nothing and to write one report to the session's scratchpad:
- **Forge.** `gitea.gitea_list_repos`; per repository its default branch and **every branch from the git refs**
(`gitea.gitea_api` GET `/repos/{owner}/{repo}/git/refs/heads`; the list tool misses branches, issue 949);
`gitea.gitea_branch_protection_get`; open pull requests (`gitea.gitea_open_pulls`, titles and update times
from `gitea.gitea_api` `/repos/{owner}/{repo}/pulls?state=open`). Per branch: contained in the default branch
(`gitea.gitea_contains`, with the default branch's sha), patch-equal (`git cherry` in a local clone), tip date.
- **Local.** Under the projects folder: each clone's `git worktree list --porcelain`, `git branch -vv`,
`git branch --merged origin/<default>`, `git stash list`, `git status --porcelain` per worktree; folders and
loose files beside the clones with `du -sh` and newest file. A worktree under another session's scratchpad is
Kept.
- A **Missing tools** section: each read it did without a mesh tool.
## 2. Classify and ask
Classes as playbook 09 step 2: Safe, Likely safe, Kept, Unknown. Tell the operator the counts and the report's
place, and ask one question: delete the Safe list? Likely-safe and Unknown items are asked separately.
## 3. Delete the approved list only
- Forge branch: `gitea.gitea_delete_branch`.
- Worktree: `git worktree remove <path>` (never `--force`), then `git worktree prune`.
- Local branch: `git branch -d <name>` (never `-D`).
Once design 58 milestone 7 is built (issue 884), each deletion is a call held as an ask: the operator approves
the exact list on Telegram and the mesh carries it out on the warrant. Until then a session's permission check
refuses these; hand the operator the approved list as a file, with the commands, and do not work around the
refusal.
## 4. Report
Four lists: **cleaned** (with its evidence), **kept** (why), **unknown** (what it looks like), **the operator
kept** (their word). Open each missing tool with `issue-tracker.open`, or note it on an existing issue.
+28 -60
View File
@@ -98,9 +98,6 @@ and repair).
*Not:* ~~tools-sdk~~
- **invokes** — the manifest word for the tools a module calls, `<module>.<tool>` each or `*` for every
one. A grant on the publish side and nothing else; a module that declares none calls nothing.
- **run for** — an assignment, or a start of a module, that serves one agent identity with that identity's machine
account, home and bus password. Only a module whose manifest declares it may be run for an identity; running for
the operator is the default run, as the operator account, with no setting (ADR 0412).
- **upgrade policy** — how a module's new builds reach its nodes: `roll` (one node first, judged, then
the rest), `together`, or `record` (register the build, send it nowhere)
(ADR 0236).
@@ -222,11 +219,10 @@ every other domain has something to run on. **Recorded by** the controller and t
(ADR 0229).
- **record mirror** — a key-value bucket on the bus that the controller writes from one kind of its
records (nodes, settings, declared config, predefined keys), after each change commits in its store and only
records (nodes, assignments, rollouts, builds, settings), after each change commits in its store and only
when the value differs, with one event per write. The store stays the record; the self-check compares
the two and raises `mirror-drifted` on any difference
(ADR 0289, ADR 0356). Builds, rollouts and assignments are no mirrors: they are
the chain, kept on the bus as their only source (ADR 0408). Shortened to *mirror* where the
(ADR 0289). Shortened to *mirror* where the
text is about the controller's records; the builder's *mirrored* `upstream` image (Change and delivery) is
a different thing, a copy of an outside image in the artifact store.
@@ -234,7 +230,7 @@ every other domain has something to run on. **Recorded by** the controller and t
**Purpose.** To decide, send and apply what each node runs, and to say why.
**Recorded by** the controller's `inventory` database. **Upstream of** Provisioning, Connectivity, Data
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261, 0411.
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261.
**Uses:** node, machine, module, seat, manifest (Module), config value (Config).
- **assignment** — a module put on a node. Its config values on that node (module-assignment config,
@@ -267,15 +263,15 @@ and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 02
other things (see [Homonyms](#homonyms)).
- **apply** — the node-engine making its node match its declaration; **reconcile** is doing so again
until nothing differs.
- **operator account** — the machine account of the operator's person identity on a node, read from the
identity and no longer stated on the node record; empty for a node nobody logs into. Everything the mesh
places under a person's home is resolved against this account's home and owned by it
(ADR 0181, ADR 0411).
- **operator account** — the login name of the person who works on a node, stated on the node record;
empty for a node nobody logs into. Everything the mesh places under a person's home is resolved
against this account's home and owned by it
(ADR 0181).
Not a name a manifest carries.
- **agent account** — the machine account of the agent identity the identity seat marks as a node's agent account,
on the operator's Approve, never by an assignment or a config value. Where none is marked, agents run as the
operator account. It cannot
become root without a person, and the node-engine judges that on every look (ADR 0266, ADR 0411).
- **agent account** — the login every coding agent on a node runs as, when the node names one of its own;
stated on the node record at the controller's terminal, never by a verb or a config value. Empty means agents
run as the operator account. It cannot become root without a person, and the node-engine judges that on
every look (ADR 0266).
- **account group** — a group of the machine's group database that a module puts an account in, by
declaring the account with that group and nothing else of it. Several modules may each add one to the
same account; the mesh takes back only an account group it added
@@ -344,7 +340,7 @@ and vault (Identity and access).
**Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents
and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault.
**Upstream of** Provisioning, Change and delivery, and Operator and conversation.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388.
**Uses:** module, node, seat, tool (Module), operator, consumer and provision (Provisioning), agent and ask (Operator and conversation).
- **credential** — anything that proves an identity: a password, a key, a token. A **pair credential**
@@ -358,13 +354,8 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
- **vault** — the module that owns every secret (`mesh-vault`,
[24 — The secrets vault](../03-DESIGN/01-to-be/24-the-secrets-vault.md)).
- **identity** — a person, an agent or a module as the mesh knows it: a name, a kind, a role, and whether it
is switched on; a person or an agent also has a login name, the machines it is present on and, for a person,
the login kept on a machine where it differs. Every identity and role is held by the identity seat, in the
bus's key-value store, and changes only through its verbs (ADR 0386, ADR 0411). Each person and agent
identity has a bus account of its own, carrying its role (ADR 0413).
- **machine account** — an identity's login on one machine, made by the mesh from the identity: the
controller composes it and the node-engine applies it, adopting a login that exists and never renaming or
deleting one (ADR 0411).
is switched on. Every identity and role is held by the identity seat, in the bus's key-value store, and
changes only through its verbs (ADR 0386).
- **role** — what an identity may call, as a set of grants (ADR 0386). Not a seat: a bare *role* is a
homonym (see [Homonyms](#homonyms)).
- **grant** — one line of a role: an effect everywhere, an effect on one seat, or one named tool, and may be
@@ -402,7 +393,7 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
**Purpose.** To take one commit from its pull request to every node that should run it, and to put it
back when it fails. **Recorded by** the `mesh-delivery` module (deliveries) and the controller (the
planner, the rollout). **Downstream of** Module, Core, Health and repair and Data.
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323, 0401–0408.
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323.
**Uses:** module, node, declaration and send (Placement), health (Health and repair), person.
- **merge check** — the two statuses a pull request carries before it may merge: the **merge gate**
@@ -417,10 +408,7 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
**builder**. Any node may hold the seat, so there is no build node by nature
(ADR 0237).
*Not:* ~~build machine~~
- **build queue** — the builds waiting to run, kept by the bus as three work queues a builder takes in order:
delivery builds, then checks of pull requests whose Approve is asked or given (ADR 0421 §10), then every other check; each check queue keeps one
request per pull request, the newest (ADR 0406); one entry in it is a
**build request**.
- **build queue** — the controller's queue of builds to run; one entry in it is a **build request**.
The queue's verbs still say *ask* for an entry; the conversation's ask is another thing
(see [Homonyms](#homonyms)).
- **package** — what code resolves when it is **compiled**: an npm/cargo/pypi dependency, by **version**.
@@ -462,14 +450,9 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
- **delivery group** — two or more deliveries sharing a pull request head branch name across the mesh's
repositories, delivered as one unit in an order declared (`after:`) or inferred by the planner. One
level: a group holds deliveries, never groups. Its state is derived from its members, never set.
- **hotfix** — one pull request in one repository, delivered at its merge (ADR 0401).
- **feature** — one branch name across its repositories: in two or more, its pull requests are one delivery group,
merged together and delivered member by member in the group's order; in one, a single pull request delivered as a
lone delivery (ADR 0401). Work is never held back on a branch other than a trunk: the trunk is what runs.
*Not:* ~~release branch~~ (hq)
- **toolchain** — a language's compiler and tools, installed by that language's build module on every node holding
the build seat, pinned to one version that is the same on every builder; its version is part of a build's reuse
key (ADR 0405). No longer an image.
- **release branch** — a branch other than `main`, shared by name across repositories, that collects a feature's
merged work and is brought into `main` as one delivery (design 57's `feat/57-config-and-provisioning`). While it
is in flight, a change that touches its files waits for it (playbook 07, *Is the coast clear?*).
- **delivery plan** — what a delivery does to the mesh: its **build plan** (the modules it moves and
their dependents, in tiers), its **deploy plan** (per node, what it receives and what waits for a
person) and its verdict (the composed nodes, the replays). Computed by the controller's planner from a
@@ -477,17 +460,15 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
*Not:* ~~change plan~~, ~~release plan~~
- **merge window** — the rolling wait in which the controller collects merges before it plans them: each
merge restarts it, and it closes when none arrived for its length (90 seconds by default) or at the latest
10 minutes after its first merge (ADR 0276). Kept as two keys with a time to live on the bus; the first to
expire closes the batch (ADR 0407).
10 minutes after its first merge (ADR 0276).
- **batch** — the merges one merge window collected, planned once as one rollout. For a repository merged more
than once in it, the rollout takes the latest commit, and the earlier merges are answered by it. Not *group*,
which is a delivery group.
- **mesh-delivery** — the module that owns deliveries and delivery groups, holding the mesh-scoped seat
of the same name. It records and decides; the controller sends, judges and rolls back when it is asked.
- **rollout** — the controller's sending of one batch's builds across nodes, at most one trunk commit per
repository, tier by tier, to every node that runs each module at once (ADR 0276, ADR 0360). Rollouts that share a
repository, a module or a graph edge run one after another; others run side by side (ADR 0407). It names every
merge it answers, and its record is one key of the bus's rollouts bucket, a link of the chain (ADR 0408). A primitive the delivering stage
repository, tier by tier, one node first and judged at the first-node gate, then the rest (ADR 0236,
ADR 0276). One rollout is open at a time; it names every merge it answers. A primitive the delivering stage
asks for, not an object anyone manages. A **tier** is one step of a rollout: a module, then the modules
built against it. The controller's verb `plans` lists the rollouts. A module is rolled out by its `roll`
policy.
@@ -626,8 +607,8 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03
line as the **controller's terminal** or as an ordinary call
(ADR 0272).
- **nox** — `mesh-cli`'s alias, in interactive zsh only. A script, `sudo` and a document meant to work everywhere
say `mesh-cli`. The operator also calls the mesh itself *nox mesh*. *nox* is also the agent (ADR 0414 §7): see
[Homonyms](#homonyms), and say which is meant. It is the one command the operator types: every line the mesh addresses to the operator — a
say `mesh-cli`. The operator also calls the mesh itself *nox mesh*; on this page and in the records *nox* is the
alias alone. It is the one command the operator types: every line the mesh addresses to the operator — a
condition's words, a verb's answer, a README, a design — names a `nox` line, never a daemon's (the controller's
or the node-engine's binary followed by a command), which `words.py` and the catalogue's check refuse
(ADR 0272,
@@ -732,7 +713,7 @@ it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Dec
Not a domain of the mesh but of the way it is built, listed because its words meet the mesh's.
**Recorded in** this repository, the issue tracker and the knowledge base. **Decided by** ADR 0019, 0080, 0244, 0275,
0284, 0290, 0291, 0423, 0424.
0284, 0290, 0291.
- **research effort**, **graduation**, **hand-off**, **playbook** — an investigation, held whole in the
knowledge base and named "research NNN";
@@ -746,23 +727,11 @@ Not a domain of the mesh but of the way it is built, listed because its words me
(ADR 0290). Not a *rule*, which is a line of
`00-META/how-we-build.md`.
- **knowledge base** — what holds the decision records and research efforts whole (number, status, topic,
typed links, clauses and reasoning, with every version), and, once they are moved, the designs and the glossary
entries too (ADR 0423); takes them as proposals, has them reviewed and accepted,
typed links, clauses and reasoning, with every version), takes them as proposals, has them reviewed and accepted,
and answers what holds: the seat `knowledge-base`, held by the module `mesh-knowledge`
([55 — The knowledge base](../03-DESIGN/01-to-be/55-the-knowledge-base.md)). Not **the record**, the literal reader
of this repository.
- **design** — a record of the knowledge base, named "design NN", with a **to-be** section (what is being built)
and an **as-is** section (what runs), its owning repositories and its links to its decisions and other designs
(ADR 0423). Until the move, a document in `03-DESIGN/`, in `01-to-be/` or `00-as-is/`.
- **design state** — of a design: proposed, designed, in progress or implemented, worked out by the knowledge base
from the issues with a design link to it and whether their deliveries are done; never written by anyone
(ADR 0424).
- **glossary entry** — one entry of the glossary as a record of the knowledge base: its head word or words, its
domain, its meaning, its *Not:* line and its identifier line (ADR 0423). Until the move, an entry of this page.
- **milestone** — a group of a design's issues delivered together, as one delivery group, or one pull request when
it touches one repository; named by what the operator or the mesh can do once it is delivered (playbook 04,
ADR 0401).
*Not:* ~~epic~~ (hq)
- **design** — a document in `03-DESIGN/`: **as-is** (what runs) or **to-be** (what is being built).
- **issue** — a question about the mesh that someone must answer, numbered and tracked by the issue tracker, which
holds it whole: its report, diagnosis and resolution included (ADR 0291);
the folders in `04-ISSUES/` are imported there and removed.
@@ -815,6 +784,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| release | a person letting a held delivery go on | **release** | Change and delivery |
| | taking a consumer off a licence | **unbind** (verb `release`, to rename) | Identity and access |
| | a copy of a work an engine can fetch | **media release** | Media |
| | a branch brought into `main` as one delivery | **release branch** | Change and delivery |
| gate | the judgement on a delivery's first node | **first-node gate** | Change and delivery |
| | the pull request status | **merge gate** | Change and delivery |
| | a failed step stopping its module's later steps (ADR 0136) | *a failed step holds its module* | Placement |
@@ -834,8 +804,6 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| record | a numbered decision | **decision record** | The record |
| | the literal reader of this repository, searched first for a symptom | **the record** (the `records` module) | Operator and conversation |
| | the upgrade policy that sends nowhere | `record` | Module |
| nox | the mesh's one agent identity, its logins and accounts (ADR 0414) | **`nox`, the agent** | Identity and access |
| | the operator's terminal command, `mesh-cli` | **the `nox` command** | Operator and conversation |
| knowledge base | the module holding the decision records and research efforts whole | **knowledge base** | The record |
| | the predecessor's operational memory, in older playbooks (issue 430) | none: it no longer exists | — |
| check | a pull request's two statuses | **merge check**, **merge gate**, **repository check** | Change and delivery |
-1
View File
@@ -58,7 +58,6 @@ the expensive half.
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery |
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one review, one push, one delivery |
| [09](09-cleanup.md) | Cleanup | Merged branches, stale pull requests, finished worktrees and folders: read first, delete only what the operator approves, report the rest |
## The cycle is checked
+1 -4
View File
@@ -33,10 +33,7 @@ about an hour, and said to the operator if it runs past.
- **Pre-check each record before proposing it**, as for research (playbook [01](01-research.md), step
5): every record, issue or design it names is in `links`; each deferral names its issue; each figure
is given as measured; no word on a glossary *Not:* line.
3. **Write the design.** **One design per thing** (the operator, 2026-10-16): when a to-be design already
covers what the decisions change, they rewrite that design, as *Amending an existing design* below says,
and no second design is written beside it that refers to it. Only for something no design covers, create
the document under `03-DESIGN/01-to-be/` with frontmatter:
3. **Write the design.** Create the document under `03-DESIGN/01-to-be/` with frontmatter:
```yaml
---
+6 -13
View File
@@ -12,12 +12,8 @@ A design becomes **milestones**, and each milestone becomes **issues**:
- **A milestone is usable on its own.** Once its delivery plan is done, the operator or the mesh can do
something new, said in one sentence ("a Claude Code session is mirrored into a thread on the bus").
A milestone that changes nothing anyone can use is not a milestone; fold it into the next.
- **A milestone is one release** of playbook [07](07-feature-branches.md): prepared, developed whole, reviewed,
checked and delivered as one, never cut for its size or for time (the operator, 2026-10-17).
- **Milestones are cut by release order.** A milestone ends where one repository's change must be released before
another can use it (a new manifest word before the catalogue reads it, an SDK change before its users, until
design 47's milestones 3 and 6 lift those orders); the lead checks this when cutting, so no milestone needs a
second release.
- **A milestone is one feature step** of playbook [07](07-feature-branches.md): prepared, developed in one to
two hours, reviewed, checked and delivered as one. A milestone larger than that is split.
- **An issue is one repository's part of a milestone,** sized so a fixing agent finishes it inside the
milestone's development time. Issues that must land in order are linked `depends-on`.
@@ -34,8 +30,7 @@ A design becomes **milestones**, and each milestone becomes **issues**:
4. **Break it into milestones.** Write them into the design, in a section *Milestones*: for each, its number,
its one-sentence outcome, the acceptance tests of the design it proves, the repositories it touches, and
what it depends on. Order them so each is usable as soon as it is delivered, the smallest useful one
first. A milestone is named by what it lets someone do; how it ships is shaped by the plan its changes would
have, asked through `mesh-delivery.what-if`: the modules it moves and their fan-out (ADR 0401 §6).
first.
5. **Ask the operator to confirm the milestones**, in a short table: number, outcome, the acceptance tests it
proves, repositories, the parts each repository builds. Change what they change.
6. **Open the issues.** For each confirmed milestone, `issue-tracker.open` one issue per repository part, its
@@ -44,10 +39,8 @@ A design becomes **milestones**, and each milestone becomes **issues**:
entry in the design.
7. **Flip the status.** `status: in-progress`, `updated:` today; the design change goes through playbook
[08](08-small-fixes.md) (one hq pull request).
8. **Hand each milestone to playbook 07, one at a time.** The milestone is one release of playbook 07; its issues
are the release's issues; the next milestone starts when the previous one's delivery plan is done. A design waits
while another design's milestones are being built: one design is built at a time (playbook
[07](07-feature-branches.md)), so this step starts only when the design before it is delivered.
8. **Hand each milestone to playbook 07, one at a time.** The milestone is the feature step; its issues are the
step's issues; the next milestone starts when the previous one's delivery plan is done.
9. **On completion of the last milestone**, run the "when something ships" section of playbook
[02](02-graduation.md).
@@ -58,7 +51,7 @@ A design becomes **milestones**, and each milestone becomes **issues**:
owns what is not. The knowledge base's rule `deferral` checks the decision record when it is proposed
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md)).
- HQ is not a code repository and never carries implementation.
- How the building is done — worktrees, agents, review, checks, the Approve and the delivery — is playbook
- How the building is done — worktrees, agents, review, checks, the ready mark and the delivery — is playbook
07's, not this one's.
## How it is checked
+110 -225
View File
@@ -28,60 +28,45 @@ A small fix (one repository, one issue, about an hour of work) follows
hours of local work, met its first check on 2026-10-16 and failed on 29 problems, TypeScript errors among
them, that a unit suite run continuously while working finds at once.
- **A green pull request nobody marks.** An approved, green pull request that was not marked ready waited
12 h 17 min for someone to merge it (mesh-catalog #300, 2026-10-14). There is no ready mark since ADR 0421:
the operator's Approve is asked as soon as the checks pass.
12 h 17 min for someone to merge it (mesh-catalog #300, 2026-10-14).
- **A module proven only after it reached the machines.** The mail modules were delivered with unit tests
only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.
## The shape
One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**,
opened **once**, after the review. It runs in five steps, and its time is bounded. A milestone has no phases
(the operator, 2026-10-17): these are its steps, and a report names the step it is in:
opened **once**, after the review. It runs in five phases, and its time is bounded:
| Step | What happens | Bound |
| Phase | What happens | Bound |
|---|---|---|
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes |
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit | the milestone's issues, 30 minutes each |
| 1. Develop | one fixing agent per repository builds every issue locally, unit tests only | 1 to 2 hours |
| 2. Review | one reviewer reads all of it; reports go both ways; at most 3 rounds | 15 minutes |
| 3. Check | push; the forge's merge checks run the integration tests; no further review | one check run |
| 4. Deliver | merge; the delivery plan builds, publishes and sends the whole feature | its delivery plan |
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, reviewed once,
pushed once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
merge check, an Approve and a delivery plan of waiting, so the fewest cuts is the fastest. Milestones are cut by
release order when the design is broken up (playbook [04](04-build-handoff.md)); a release-order dependency found
inside a milestone is a fault of that cut, said to the operator, never a silent extra step.
A feature that cannot be developed in about two hours is split into **steps**. Each step runs all five
phases on its own; the next step starts only when the previous step's delivery plan is done. A step is the
unit that ships, so nothing finished waits for work that is not.
**One design is built at a time** (the operator, 2026-10-17). While the milestones of one design are being
built, no milestone of another design starts: its issues stay `open`, and nobody moves them to `diagnosing` or
`fixing`. The next design starts when the last milestone of the one being built has its delivery plan done. A
lead who finds another design's milestone already in `fixing` asks its owner before starting its own. A small
fix ([playbook 08](08-small-fixes.md)) and an issue that is in no design's milestones are not held by this rule.
**Scope is fixed at the prepare step.** An issue found during a feature goes to the next milestone or the next feature,
**Scope is fixed at phase 0.** An issue found during a feature goes to the next step or the next feature,
never into the running one, unless it blocks the running one.
## Step 0 — Prepare (the lead, larger model)
## Phase 0 — Prepare (the lead, larger model)
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches (but see the
exception for a new manifest word in phase 3: a release that must merge first has a name of its own). The
branch is never protected. **One milestone is one branch per repository** (the operator, 2026-10-17), with one
folder `.work/<slug>/`; never a second name for part of it (`-2`, `-step2`).
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches. The
branch is never protected.
2. **Make the worktrees:** one per touched repository under `.work/<slug>/<repo>`, branched off `main`
(`git worktree add .work/<slug>/<repo> -b feat/<slug> origin/main`). The lab beds find the other
repositories by sibling path, so add the untouched siblings the lab reads as detached worktrees on
`main` (`git worktree add --detach .work/<slug>/mesh-tools main`), never symlinks. **A repository's own
clone stays on `main`** (the operator, 2026-10-17): no work is done in it and no branch is checked out
there, so a session that opens in it reads what holds now; fetch and fast-forward it before reading.
`main` (`git worktree add --detach .work/<slug>/mesh-tools main`), never symlinks.
3. **Settle every open question** in the feature's issues before any agent starts: on the issue, through
the knowledge base, or by asking the operator. A fixing agent that meets an undecided design question
stops, and its run is spent again.
4. **Write one context file per repository**, `.work/<slug>/<repo>.context.md`. It is what a fixing agent
reads instead of rediscovering the code, and it is reused by every agent and every round:
- the issues it builds, in order, each with: the files and functions it changes, the decisions it builds
with (quoted, with where each was decided), and its acceptance criteria (item 5), each with the test that
proves it;
with (quoted, with where each was decided), the test to write first, done-when in one sentence;
- the repository's map for this feature: the packages and files involved, with one line each on what they
hold, and the patterns to copy (file:line);
- what not to touch, and what lands beside it in other repositories;
@@ -90,55 +75,25 @@ never into the running one, unless it blocks the running one.
integration tests, which the forge's checks run).
When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with
file:line, and the lead writes the file from its answers.
5. **Write the findings into the issues** (the operator, 2026-10-17: the better the content of the issues, the
better work the agents do, and the less review is needed). Before any agent starts, each issue carries, with
`issue-tracker.note`: every file and test that touches the change, found by search and not from memory
(file:line); how the change is checked and delivered end to end (which test proves its done-when, which
modules its delivery plan moves, to which machines); and for a group, its merge order. The context file
points to these notes; an issue reread by another session or a later agent then holds what was found.
When the reading is large, it starts with a read-only survey agent on the smaller model.
**Each issue carries its acceptance criteria, written by the lead** (the operator, 2026-10-18: the larger
model writes them, and they guide every test). Each criterion is one sentence, given, when, then, about what
the module or the operator observes, never about how the code does it, and names the level of the test that
proves it: unit, integration (a bus, a store or a process started) or end to end (none exist yet, so such a
criterion is proven live after the delivery and said so). Together they cover the issue's done-when and the
cases its life passes through: on 2026-10-18 mesh-delivery's group life (a group of three, a member's own
check, a close in a group, a second pull request at the same commit, a stop and a resend) had no criterion,
and each case was found by a failure on the live mesh. Each criterion is numbered in its note and cited as
`<issue>/AC<n>` (`1118/AC2`). They are an `issue-tracker.note` headed `Acceptance criteria`, a revision a new note that replaces the older one, until the tracker holds them as a
field (issue 1118). A criterion the reviewer adds in step 2 is written onto the issue the same way.
**The survey sizes each issue, and an issue sized over an hour is split before any agent starts** (the operator,
2026-10-17), into issues of about 30 minutes each, one change each, in the order they land: the bound of a
fixing agent's issue is then a real bound. On 2026-10-17 one issue held four changes sized at four to six
hours; it ran past its 30-minute bound for over an hour, and the reports could only say "still on part D".
6. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
5. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
## Step 1 — Develop (one fixing agent per repository, smaller model)
## Phase 1 — Develop (one fixing agent per repository, smaller model)
- **One agent per repository, kept for the whole feature.** It reads its context file once and builds the
issues one after another, committing each locally with a message naming the issue ("Keep the assignments
on the bus (issue 652)"). It is not replaced per issue: the lead continues the same agent (by its id) with
the next instruction, so its context is paid for once.
- **Unit tests only, each well under a second** (how-we-build §5, ADR 0391). **The whole unit suite runs before
every commit** (the operator, 2026-10-17), not only the tests of what changed: it takes under a second, and a
change breaks tests in packages it never touched. Each issue's first test fails before its change and passes
after; `gofmt`, `go vet` and the whole unit suite stay green. **The tests are written from the issue's
acceptance criteria**: one test or more per criterion at the level it names, each test's comment citing
the criterion it proves by its label (`// 1118/AC2`), and no criterion left without its test; an integration criterion is a test
that compiles while you work and runs in the one merge check before the push.
- **Nothing is pushed** and no pull request is opened in this step. Never land on a red check: run the
- **Unit tests only, of what changed, each well under a second** (how-we-build §5, ADR 0391). Each issue's
first test fails before its change and passes after; `gofmt`, `go vet`
and the repository's unit tests stay green. The integration tests (`merge-check.sh` with the store and the
bus) wait for phase 3.
- **Nothing is pushed** and no pull request is opened in this phase. Never land on a red check: run the
checks, read the result, then land in a separate step.
- **The fixing agent's brief** (the lead gives it with the context file): name your issue in every step and
message ("issue 805: landing in mesh-catalog"); reach the mesh only through the mesh MCP server, never ssh;
never push, open, merge or close a pull request; never write to a store's database by hand, a schema change
is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
move another issue; **run the integration tests once before every push, not while developing** (the
operator, 2026-10-18): compile them with `go vet -tags integration ./...` while you work, and before the push
run the repository's whole merge check with `mesh-controller.try` on the pushed branch (the store and bus a
pull request's check gets) or `merge-check.sh` against a throwaway store, and push only on its pass. On
2026-10-18 three pushes in a row failed their first merge check on faults an integration run finds in a
minute (mesh-catalog #361, mesh-controller #380's stale keys, #381's 400 tests without the layout marker),
each costing a check, a fix and another check. Report back in under 150 words: commits, tests added and that they failed first, the
move another issue. Report back in under 150 words: commits, tests added and that they failed first, the
check results, anything you stopped on.
- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead
settles it, updates the context file, and continues the agent.
@@ -150,81 +105,71 @@ never into the running one, unless it blocks the running one.
tests pass; if another landed first, it merges again and repeats. In a shared clone, fetch the feature
branch by name before branching from it. Never resolve a conflict with a blind `git merge-file --union`.
## Step 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)
## Phase 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)
1. **Bring every feature branch up to the current trunk** (fetch by name, then merge), so the reviewed tree is
the tree that is pushed.
2. **One reviewer reads every repository's local commits** together, against the context files and the
decisions. **It is one agent, kept for the whole release** like the fixing agents: the lead continues it by
its id for every later round, for the extra cycle after a failed check (step 3) and for its review record, so it
reads the decisions, the issues, the context files and the commits once (issue 1042). A fresh reviewer per
round or per mark pays for all of that again. Its report is per repository: each finding as file:line, the failure it causes, and the fix.
decisions. Its report is per repository: each finding as file:line, the failure it causes, and the fix.
It says which round it is in ("round 1 of at most 3").
3. **The report goes to the same fixing agents** that wrote the code, continued by their ids, so they fix
with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
4. **Every new integration test is read against the code it drives before the first push.** It has never run: fixing agents only compile it, and its first run is the merge check, so each fault it hides costs a whole check. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
5. **The reviewer reads the answers and the new commits**, and approves or reports again (step 7 says what a round
4. **The reviewer reads the answers and the new commits**, and approves or reports again (step 5 says what a round
may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
6. **The reviewer reads every acceptance criterion against its test**: each criterion has a test at its level
that would fail without the change, and no test proves less than its criterion says. A criterion missing
for a case the issue's done-when needs is written onto the issue (step 0, item 5) and is a finding; a test
missing for a criterion is a finding.
7. **A finding outside the issues' done-when and their acceptance criteria is never a change asked, in any round.** More hardening, a
5. **A finding outside the issues' done-when is never a change asked, in any round.** More hardening, a
further case, a nicer form: the reviewer opens it with `issue-tracker.open` and names the number in its
report, and the round does not wait for it. What a round asks is only what makes the change wrong against
its issues, their acceptance criteria and its decisions.
its issues and its decisions.
## Step 3 — Check (no review)
## Is the coast clear?
1. **Push every feature branch and open one pull request per repository, together.**
The integration tests ran once before the push (step 1); the merge check runs them again as the gate. Pull requests in two or
Before the push of phase 3 (and of a small fix, playbook [08](08-small-fixes.md) step 5), the lead checks, for every
repository the change touches, whether a **release branch** is in flight there: a branch other than `main` that
collects a feature's merged work to bring it into `main` as one delivery.
1. **Look for one.** A pull request into `main` whose head is a release branch (`gitea.gitea_open_pulls`), a
release branch holding merged work that `main` does not have yet (`git log origin/main..origin/<branch>`), or
a delivery plan of that release branch still running (`mesh-controller.plans`).
2. **Compare the files.** `git diff --name-only origin/main...origin/<release branch>` against the files the
change touches.
3. **Overlap: hold.** If the release branch touches a file the change touches, nothing is pushed into that repository
until the release branch's delivery plan is done. Then the feature branch is brought up to the new `main`, the unit
tests run again, and the push follows. The hold is on the wait list (its bound: the release branch's delivery plan),
and is said to the operator with what the change waits for.
4. **No overlap: go on.** A release branch in another repository, or one that shares no file with the change, does not
hold it; its merge joins the merge window as any other (ADR 0276).
Merging beside a release branch still in flight would make its lead resolve someone else's conflicts in the
middle of it, and could send part of the change with its delivery (2026-10-16: the file module's
controller part touched four files feature 57's release branch also changed).
## Phase 3 — Check (no review)
1. **Push every feature branch and open one pull request per repository, together, once the coast is clear**
(above). The integration tests
run only in the merge check (the operator, 2026-10-16), never before. Pull requests in two or
more repositories that share the branch name are one delivery group (ADR 0239 §2).
Where a session pushes through the forge tool `gitea_commit` (one commit on the forge's current base), the
reviewer first confirms that commit's tree equals the reviewed one. **Each pull request's description carries
the review record** (ADR 0421 §5), one line the lead writes from the reviewer's approval:
`Reviewed-by: <who@node> at <full sha>` (the issue 1022 note gives its form). mesh-delivery reads it at every new
head and puts it in the Approve's ask; a missing line, or one naming another commit, is said first in the ask.
It is never a gate.
reviewer first confirms that commit's tree equals the reviewed one.
2. **The forge's merge checks run the integration tests once** (`mesh/merge-gate`, `mesh/repo-check`). No
second review: step 2 reviewed this tree.
3. **A check that fails** goes back to the same reviewer, continued by its id, with the failure, for **one more cycle** of step 2 with
the same fixing agents; then push once again. **The failure is read from the check's whole log**, never
reasoned from its summary: `mesh-delivery.show` names the check's build id, and `mesh-controller.builds` with
`log` set to that id prints everything the builder said. A check's summary picks only some failing lines;
on 2026-10-17 it showed a store test's `FAIL` without its cause, and a fix was first guessed from the code.
**A change to `merge-check.sh` is judged by main's version of it**, so a test the change adds must pass under
either version: one that runs the new script's behaviour runs a file of its own, never `merge-check.sh` itself.
**So a change that needs the new script to judge it ships the script first, as a small fix of its own**, and the
rest waits for its delivery: on 2026-10-17 milestone 3 of design 47 moved its integration tests behind a build
tag in the same pull request as the script that runs that tag, and main's script would have skipped every
tagged test, its acceptance tests among them. The prepare step looks for this: a change to `merge-check.sh` or to
what it selects (build tags, the store-tests list) is cut ahead of the milestone.
4. **The milestone's acceptance is proven by its check.** A milestone that adds or changes a module
second review: phase 2 reviewed this tree.
3. **A check that fails** goes back to the reviewer with the failure, for **one more cycle** of phase 2 with
the same fixing agents; then push once again.
4. **The step's acceptance is proven before it is marked ready.** A step that adds or changes a module
carries an integration test that starts the module whole, its process against the real bus and store, and
shows the milestone's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
shows the step's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
that proof; the mesh has no end-to-end tests yet, and the lab is not used (the operator, 2026-10-16).
5. **The operator's Approve is asked as soon as the checks pass** (ADR 0421). Nobody marks, labels, comments or
rechecks: mesh-delivery asks the Approve on Telegram, the channel that proves who answered today (ADR 0398 §3;
an Approve at the terminal is issue 953), once both checks passed, for a delivery group once for all its
members, naming the review record. On the Approve it merges the approved heads, a group's in one pass
(ADR 0418). A new head ends the Approve and is asked again once its checks pass. A watcher follows the checks,
the Approve and the merge, and wakes the lead. Beyond that
Approve, the operator's word is waited for only when the milestone changes what the operator sees, removes
data, or needs a step only the operator's terminal can take; the lead says which before the milestone starts.
5. **The reviewer marks each pull request ready as soon as the acceptance is shown** with
`mesh-delivery.mark-ready` (the delivery of the head it approved, who reviewed it and why), which writes the
label `mesh/ready` and then `ready at <full sha>`, the order the forge counts (issue 913); mesh-delivery merges it when its checks pass
(ADR 0364). The operator's word is waited for only when the step changes what the operator sees, removes
data, or needs a step only the operator's terminal can take; the lead says which before the step starts.
**One exception: a catalogue change that uses a new manifest word.** The catalogue's merge check reads
manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first;
then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way.
**A new key the node-engine checks is such a word too** (issue 1051): the controller judges every machine with the
node-engine's own validator, vendored at the commit `go.mod`'s `replace` pins, so a key added to the node-engine is
refused by the gate until the controller's pin moves to the delivered node-engine (`go mod vendor`); the feature is
then three releases, the node-engine's, the controller's, the catalogue's. **Every release that must merge before
the catalogue's is on a branch name of its own** (`feat/<slug>-node-engine`, `feat/<slug>-controller`): on the
feature's name it joins the catalogue's delivery group, and a group merges only whole, so neither merges.
The exception ends when milestone 3 of design 47 is delivered (issue 977, ADR 0402, ADR 0403): the group's composed
check, judged by the group's controller, is then each member's gate, and the feature is one group.
then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Plan such a feature as two steps.
## Step 4 — Deliver
## Phase 4 — Deliver
The merge starts the delivery plan: build, publish, send. A merge into a branch other than `main` is not a
delivery: no machine receives it (issue 889 while it still starts one). The lead follows it to done
@@ -232,14 +177,22 @@ delivery: no machine receives it (issue 889 while it still starts one). The lead
naming its pull requests, and removes what is left: no `feat/<slug>` branch and no `.work/<slug>` worktree
survive.
**Removing what is left means everything the feature made, wherever it lives**: every worktree the lead or its
agents made (under `.work/<slug>`, in a session's scratchpad, a reviewer's copy), every per-issue or per-package
branch, and every branch the feature pushed to the forge. Before one goes, check it holds nothing that is not on
`main`. Compare content, not hashes: a commit put on the forge with `gitea_commit` lands with a new hash, so a
merged branch still shows commits "not on main" (`git diff origin/main <branch> -- <the files it changed>` is
empty when its content is there). What is not on `main` is delivered in the next step or written on its issue
before it goes. Feature 57 left over a hundred worktrees and per-issue branches in one scratchpad (2026-10-16).
## Models
| Who | Model | Why |
|---|---|---|
| The lead (step 0 decisions, the context files, following and reporting) | larger | judgement |
| The lead (phase 0 decisions, the context files, following and reporting) | larger | judgement |
| Read-only questions about the code for the context files | smaller | reading, answered with file:line |
| Fixing agents (step 1, and the fixing in steps 2 and 3) | smaller | building to a written context |
| The reviewer (step 2, the extra cycle in step 3 and the review record; one agent, continued by its id) | larger | judgement, once per round |
| Fixing agents (phase 1, and the fixing in phases 2 and 3) | smaller | building to a written context |
| The reviewer (phase 2, and the extra cycle in phase 3) | larger | judgement, once per round |
| Decision records | larger | judgement |
Every agent the mesh runs draws on one weekly allowance shared by every session, and the larger model
@@ -256,97 +209,55 @@ on, with when it started and its bound.
| a fixing agent's issue | 30 minutes | ask it where it stands; split or take over |
| the review | 15 minutes a round | ask the reviewer; stop at round 3 |
| a merge check | 15 minutes | read the build queue (`mesh-controller.queue`) and the check's log |
| the Approve's ask | 5 minutes after both checks pass | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
| a merge after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
| the ready mark | 15 minutes after the acceptance is shown | the reviewer marks it, or the lead does |
| a release branch in flight that shares files | its delivery plan done | report it every ten minutes; bring the branch up to `main` after |
| a merge after ready | 5 minutes after the checks pass | read `mesh-delivery.show` for the delivery |
| a delivery plan | its class's budget (`mesh-delivery.times`), at most 30 minutes | read `mesh-controller.plans`, then `mesh-delivery.stalled` |
**Every wait on the list has a watcher** (the operator, 2026-10-17). A session runs only when a message reaches
it, so a check, a merge, a delivery plan, a record's review or the operator's Approve on a channel that lands
while the lead's turn has ended wakes nobody: on 2026-10-17 an approved group sat half merged for 14 minutes
unseen (issue 1032). For each wait the lead starts a watcher, an agent on the smaller model that changes nothing,
never the smallest (the operator, 2026-10-17).
It loops `mesh_wait` on the event families of what it watches (`mesh-delivery.transition` and
`mesh-delivery.group` for a delivery, for example) with a timeout under 60 seconds, since the call itself ends near
a minute. After every answer or timeout it reads the state from the verb that owns it (`mesh-delivery.show`,
`mesh-controller.plans`, `knowledge-base.get`), because an event only wakes it (ADR 0367). It finishes, and so wakes
the lead, when the state changes, when the wait passes its bound, **or when nothing changed for two minutes**
(the operator, 2026-10-18): a wait that stopped moving is a stall, never a wait. On 2026-10-18 a delivery plan stopped
at 12:46 because the bus refused the new controller's writes, its watcher waited for "done or failed", and the stall
was found ten minutes later only because the operator asked what was happening (issue 1121). The lead never sleeps or polls by hand to wait.
**Every ten minutes, and at once when something lands, stops or needs the operator,** the lead reports, in
one of the two layouts below. Times are local and absolute; every number has its unit and its bound or
baseline; an issue is named by its number and a few words, never by its number alone.
**Every ten minutes (the operator, 2026-10-17), every two minutes while a delivery plan runs, and at once when something lands, stops or
needs the operator,** the lead reports, in one of the two layouts below (the operator, 2026-10-16: an overview
of the issues, often). Times are local and absolute, from the clock (`date`), never guessed; every number has its
unit and its bound or baseline; an issue is named by its number and a few words, never by its number alone.
**The full layout** (in the session) is the whole design as a tree (the operator, 2026-10-17: always the full
overview): every milestone of the design with its state, and under the running milestone its repositories and
their issues, each issue under the one it waits for, marked ✓ landed or delivered, ▶ fixing, ⏸ waiting, ✗ stopped:
**The full layout** (in the session), the issues as a tree: grouped by repository, each issue under the one it
waits for, marked ✓ landed, ▶ fixing, ⏸ waiting, ✗ stopped:
```
Feature <slug> · milestone <n> of <m> · step <name>, <minutes used> of <bound> min
Feature <slug> · step <n> of <m> · phase <name>, <minutes used> of <bound> min
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting
Design <nn> · <its title>
├── ✓ milestone 1 <its outcome, a few words> delivered 12:58
├── ▶ milestone 2 <its outcome, a few words>
│ ├── mesh-controller (agent: fixing, 2 of 3 landed)
│ │ ├── ✓ 652 assignments on the bus landed 14:02
│ │ ├── ▶ 736 placement words leave the layers fixing since 13:51, 23 of 30 min
│ │ │ └── ⏸ 751 config keys named (mesh-catalog) waits for 736
│ │ └── ✓ 654 a file names its template landed 13:20
│ └── mesh-catalog (agent: waiting)
│ └── ⏸ 751 (above, under 736)
└── ⏸ milestone 3 <its outcome, a few words> <its issues>
mesh-controller (agent: fixing, 2 of 3 landed)
├── ✓ 652 assignments on the bus landed 14:02
├── ▶ 736 placement words leave the layers fixing since 13:51, 23 of 30 min
│ └── ⏸ 751 config keys named (mesh-catalog) waits for 736
└── ✓ 654 a file names its template landed 13:20
mesh-catalog (agent: waiting)
└── ⏸ 751 (above, under 736)
Opened along the way: <each issue opened during the feature, its number and a few words, and where it goes> — or "none"
Past its bound: <the wait, how long, what is being done> — or "none"
Needs you: <one line per question, with my recommendation> — or "nothing"
Allowance: week <n> % (<+d> since the last report), session <n> %
Next: <the next actions up to the delivery, in order>
Next: <the next action, one line>
```
**The short layout** (on a channel such as Telegram, or when nothing changed but time):
```
<slug> milestone <n>/<m> · <step> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> %
<slug> step <n>/<m> · <phase> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> %
```
A report says what changed since the last one first. In the session every report is the full layout, the whole
tree of the milestone's issues included, also when only time passed: the operator reads the state from it without
asking. The short layout is for a channel.
A report says what changed since the last one first; an unchanged table is not repeated, the short layout
says it instead.
## The context of the lead's own session
At the end of each step, and whenever the session's context grows large or spread over unrelated work, the
At the end of each phase, and whenever the session's context grows large or spread over unrelated work, the
lead prepares a `/clear` as the mesh's instruction "prepare before clear" says: a save point with the
feature's step, its branches and heads, the context files' paths, the agents' ids, the wait list and the
feature's phase, its branches and heads, the context files' paths, the agents' ids, the wait list and the
next action; then the prompt the operator pastes. The context files make the feature resumable after a clear.
## The design's state is written back
Other sessions know what is built only from the design's frontmatter and the issue tracker; what the lead
knows and does not write back, nobody else knows. A design merged in one delivery with three of its tasks
left, its frontmatter still saying `designed`, read to another session as not built, and a second design
was started beside it (issue 1009, 2026-10-17). So the lead writes the design's state back at each of these
moments, the same hour, an hq change going through [playbook 08](08-small-fixes.md):
- **The first milestone starts:** the design's `status: in-progress` ([playbook 04](04-build-handoff.md) step 7),
if it is not already.
- **A milestone's delivery plan is done:** its issues are resolved (step 4), with `fixed-by` naming its pull
requests. The design's milestone entry is not changed: it names the milestone's issues, and whether they are
delivered, when and by what is read from the issue tracker when it is asked, never copied into the design
(issue 1038; AGENTS.md: cross-cutting views are generated when they are read).
- **The building pauses with work left** (another design takes its turn, a hand-over, the session's end): the
design stays `in-progress`, and each issue left gets a note with `issue-tracker.note` saying what it waits
for and what comes after it ("built after design N is delivered, before design M"). The first issue of
a design that waits its turn gets the same note.
- **The last milestone is delivered:** [playbook 02](02-graduation.md), "When something ships": the as-is is
written and the design says `implemented`.
## Issues that stop
An issue stays in `fixing` only while an agent works on it. A milestone that stops, for a wait past its bound, a
An issue stays in `fixing` only while an agent works on it. A step that stops, for a wait past its bound, a
hand-over, or the session's end, moves each of its issues not delivered back to `open` with
`issue-tracker.move` and a note saying how far it got (its branch, its last commit, what is left). A list of
issues in `fixing` that nobody works on reads to the operator as stuck work.
@@ -355,46 +266,20 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
- A feature's issues show their commits on the feature branch (`git log --grep 'issue <n>'`), and no pull
request into a feature branch exists. A pull request per issue, or an agent waiting on a merge check in
step 1, is the failure this playbook prevents.
phase 1, is the failure this playbook prevents.
- A context file exists per touched repository under `.work/<slug>/` while the feature runs.
- No issue of a running milestone is sized over an hour in its prepare note; an issue whose fixing agent passes
twice its bound was not split.
- Every issue of a running milestone has a note from the prepare step naming its files and tests (file:line), how
it is checked and delivered, and for a group its merge order (`issue-tracker.get`); an issue moved to `fixing`
without one is a prepare step skipped.
- Every issue of a running milestone has a note headed `Acceptance criteria` before it moves to `fixing`
(`issue-tracker.get`), each criterion naming its test level, and every criterion's label is cited by a test in the
pushed tree (`git grep -n '<issue>/AC<n>'`); an issue without them is a prepare step skipped, and a
criterion no test names is a review that missed it (until issue 1118 makes the tracker refuse it).
- Every fixing agent's report gives the whole unit suite's result and time; a report naming only some packages'
tests is the whole-suite rule not kept.
- The lead's save point names each watcher with its model; a watcher on the smallest model is the failure of
"Following and reporting".
- A report or a save point that names a "phase" of a milestone is the old wording: a milestone has steps.
- Every repository's own clone says `main` (`git -C <clone> branch --show-current`); another branch there is
work done outside a worktree (issue 1010).
- After a feature's delivery, `git branch -r | grep feat/<slug>` and `git worktree list` return nothing for it.
- A design whose milestone issues are partly resolved says `in-progress`, and one whose issues are all
resolved and delivered says `implemented`: read its Milestones section against `issue-tracker.get` for each
issue. A design that says `designed` with a resolved milestone issue is a state not written back.
- A design's Milestones section names each milestone's issues and nothing of their state: a "delivered", a date
or a pull request in an entry is a copy of the tracker's state (issue 1038), read with
`grep -n 'delivered' <design>` over its Milestones section.
- Every open issue of a design that says `in-progress` and has no agent on it shows, in `issue-tracker.get`, a
note saying what it waits for; one without such a note is a pause not written back.
- The milestone issues in `fixing` (`issue-tracker.list`) belong to one design at most: milestone issues
of two designs in `fixing` at once is the failure of "one design is built at a time".
- More than one open pull request in a repository that share no feature name, or a milestone whose issues
- After a feature's delivery, `git branch -r | grep feat/<slug>` and `git worktree list` return nothing for it, in every clone the
feature used, its session scratchpads included.
- More than one open pull request in a repository that share no feature name, or a feature step whose issues
stay in `fixing` for more than a working day, is the smell: stop and bring it to the operator.
- A review report that asks a change outside the issues' done-when and acceptance criteria, or a fourth round, is the failure of the
- A review report that asks a change outside the issues' done-when, or a fourth round, is the failure of the
review rules; `issue-tracker.list` with `flag: abandoned` lists issues left in `fixing` with nobody on them.
- A pull request green for more than 5 minutes with no Approve asked is a fault of mesh-delivery
(`mesh-delivery.show`), said to the operator.
- Every review record of a release names one reviewer (`mesh-delivery.show`, the review record), and the lead's
save point names one reviewer id per release; a second reviewer within one release is a reviewer not kept
(issue 1042). A pull request whose ask says it has no review record at its head is a record not written.
- A milestone that adds or changes a module whose pull request has no integration test that starts the module
whole is the failure of the check step's fourth item.
- A pull request reviewed and green but not marked ready for more than 15 minutes is a lead that did not
mark it.
- A pull request opened into a repository whose release branch was in flight and shared a file with it is the failure of
*Is the coast clear?*; the lead's report names the check it made before the push.
- A step that adds or changes a module marked ready with no integration test that starts the module whole is
the failure of phase 3's fourth step.
- A bed run from `.work/<slug>/mesh-lab` that fails naming a `.work/<slug>/<repo>/…` path it cannot find is a
missing sibling worktree, not a mesh fault.
+25 -49
View File
@@ -2,10 +2,7 @@
**Trigger.** One issue in one repository, small enough to build in about an hour: a bug with a known cause,
a missing check, a wording, a playbook or design edit. Anything larger, or anything that needs a change in a
second repository first, is a feature and follows [playbook 07](07-feature-branches.md). A feature is the
larger kind: it touches several modules and has a research effort or a design before it. **Needing a decision
does not make a fix a feature**: the decision is settled first (step 1) and the fix stays small (the operator,
2026-10-16).
second repository first, is a feature and follows [playbook 07](07-feature-branches.md).
**Related issues in one fix.** A small fix may cover two or three related issues when they are in the same
repository, mostly in the same module, and together still fit in about an hour: they touch the same files, or
@@ -26,64 +23,47 @@ one delivery**. Everything before the push is local and fast.
1. **Read the issue and the code it names** (the session's own model, or one read-only agent on the smaller
model answering exact questions with file:line). If a design question is open, it is settled before any
code is written: on the issue, through the knowledge base, or by the operator. A fix that turns out to
need a second repository, or more than about an hour, stops here and becomes a feature; one that needs a
decision stays a small fix. **The session's own model (the larger) then writes the issue's acceptance
criteria** as playbook [07](07-feature-branches.md) step 0 item 5 says: each one sentence, given, when, then,
naming the level of the test that proves it, as an `issue-tracker.note` headed `Acceptance criteria`. A
playbook or design edit has none: its check is `sh merge-check.sh`.
need a decision or a second repository stops here and becomes a feature.
2. **Move the issue to `fixing`** with `by` as who@node.
3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`, never in
the repository's own clone, which stays on `main` (playbook [07](07-feature-branches.md), step 0, item 2). Write the
test that fails today first; make the change; keep `gofmt`, `go vet` and the repository's whole unit suite green
before every commit (each test well under a second; no database in the unit tests: how-we-build §5, ADR 0391); before the push the repository's merge check runs once, integration tests included, through `mesh-controller.try` or `merge-check.sh` against a throwaway store, as playbook 07 step 1 says.
The tests are written from the acceptance criteria, at least one per criterion at its level, each citing the
criterion it proves by its label (`<issue>/AC<n>`, playbook 07 step 0 item 5). Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
3. **Build on a local branch** `fix/<issue>-<slug>`, in a worktree of its own branched from `main`. Write the
test that fails today first; make the change; keep `gofmt`, `go vet` and the unit tests of what changed green
(each well under a second; never a merge check or a database locally: how-we-build §5, ADR 0391).
Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
`sh merge-check.sh`).
4. **One review of the local commit, before anything is pushed** (a reviewer that did not write it, larger
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; it reads each acceptance criterion
against its test, as playbook 07 step 2 item 6; a finding outside the issue's done-when and its acceptance
criteria is opened as an issue of its own, never asked as a change). The reviewer's findings go back to the same agent that wrote the fix, continued by
model; at most 3 rounds, as playbook 07; from round 2 only what is wrong; a finding outside the issue's
done-when is opened as an issue of its own, never asked as a change). The reviewer's findings go back to the same agent that wrote the fix, continued by
its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the
operator's Approve of its merge is its review, and the review record says so.
5. **Push once and open the pull request.** The trunk is what runs and nothing holds merged work back
(ADR 0401), so a fix waits for no other change's delivery. The integration tests ran once
before the push (step 3); the forge's merge check runs them again as the gate. A failed check
operator's word on the pull request is its review, and the lead marks it ready on that word.
5. **Push once and open the pull request, once the coast is clear** (playbook [07](07-feature-branches.md), *Is
the coast clear?*: no release branch in flight in the repository that shares a file with the fix). The forge's merge
check runs the integration tests, the only
place they run (the operator, 2026-10-16). A failed check
goes back to the same agent with the failure, and the reviewer reads only the change it makes.
6. **The pull request's description carries the review record** (`Reviewed-by: <who@node> at <full sha>`,
playbook [07](07-feature-branches.md) step 3), and mesh-delivery asks the operator's Approve as soon as its
checks pass and merges the approved head (ADR 0421); nobody marks, labels, comments or rechecks. A fix to a module that changes how it runs carries an
6. **The reviewer marks it ready at once** (for an edit that skipped the reviewer, the lead, on the
operator's word) with `mesh-delivery.mark-ready` (the delivery of the head it approved, who reviewed it and
why), which writes the label `mesh/ready` and then `ready at <full sha>`, the order the forge counts (issue 913);
mesh-delivery merges it when its checks pass. A fix to a module that changes how it runs carries an
integration test starting the module whole against the real bus and store, run by the forge's checks
(ADR 0391 §1, §6); unit tests alone are not that proof, and the lab is not used. The operator's word is waited for only when the fix changes what the operator sees, removes data,
or needs the operator's terminal.
7. **Follow its delivery plan to done** (`mesh-controller.plans` with `wait` and `until: done`), prove the fix on
the mesh (the symptom gone, measured), and resolve the issue with `located-in` and `fixed-by` the same hour.
8. **Remove the fix's worktree and its branch**, local and on the forge, as playbook [07](07-feature-branches.md)
phase 4 says: content checked on `main` first, not hashes.
A fix that stops before its delivery moves its issue back to `open` with a note saying how far it got
(branch, last commit, what is left), as playbook [07](07-feature-branches.md) says.
**A second fault found by the delivery.** When a fix's delivery shows a second fault that keeps the issue's
done-when from holding, and that fault is in the same files, it is taken into the same issue as a second pull
request with a review round of its own. A fault in other code is opened as an issue of its own, and the first
issue waits for it (issue 937: mesh-catalog #335 was taken in; issue 943 was opened).
**A fix that moves a file to a new filler compares the values first.** When a fix makes a module's file filled a
new way for the first time (the provisioner client in place of the controller, a provider's entry in place of a
composed value), the fix lists every value the old way put into that file on each machine it reaches and shows,
in its review, that the new way gives the same values, or says which differ and why. A difference nobody looked
at is an outage: on 2026-10-16 the forge and the identity provider on the control node were down for 17 minutes
because postgres's consumers were served the port inside its container instead of the port it is published on
(issue 957). The comparison is read from the machines (the file as it is today) before the merge, not from the
code.
## Bounds
| Waiting on | Bound | Past the bound |
|---|---|---|
| building the fix | 1 hour | it is a feature: stop and plan it under playbook 07 |
| the review | 15 minutes | ask the reviewer where it stands |
| the ready mark | 15 minutes after the review approved | the reviewer marks it, or the lead does |
| the merge check | 15 minutes | read `mesh-controller.queue` and the check's log |
| the Approve's ask | 5 minutes after both checks pass | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
| the merge after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
| the merge after ready | 5 minutes after the checks pass | read `mesh-delivery.show` for the delivery |
| the delivery plan | its class's budget (`mesh-delivery.times`), at most 30 minutes | read `mesh-controller.plans`, then `mesh-delivery.stalled` |
A wait past its bound is said to the operator at once, with what is being done about it.
@@ -98,13 +78,9 @@ reading itself and uses one subagent only for the review.
- A small fix has one pull request with one or few commits naming its issue, opened after its review: a pull
request with many heads is a fix reviewed after its push.
- Its issue moves to `resolved` within the hour its delivery plan ends.
- A pull request with no review record at its head (the Approve's ask says so first), a fix to a module's
running pushed with no integration test of the module whole, or a review report
asking a change outside the issue's done-when and acceptance criteria, is the failure of steps 4 to 6.
- A fixed code issue shows a note headed `Acceptance criteria` (`issue-tracker.get`) older than its first
commit, and each criterion's label is cited by a test in the pushed tree
(`git grep -n '<issue>/AC<n>'`); one without is step 1 skipped.
- After its delivery, no worktree and no branch named `fix/<issue>-…` is left, locally or on the forge.
- A pull request reviewed and green but unmarked
for more than 15 minutes, a fix to a module's running marked ready with no integration test of the module whole, or a review report
asking a change outside the issue's done-when, is the failure of steps 4 to 6.
- An issue in `fixing` with nobody on it shows in `issue-tracker.list` with `flag: abandoned`; a stopped fix
moves it back to `open` with its note.
- The repository's own clone says `main` (`git -C <clone> branch --show-current`); another branch there is a
fix built outside a worktree (issue 1010).
-49
View File
@@ -1,49 +0,0 @@
# Playbook 09 — Cleanup
**Trigger.** The operator asks for a cleanup, or the forge and the working copies have gathered what nobody
uses: merged branches, stale pull requests, worktrees and folders of finished work.
**Who runs it.** A session on the operator's word, with the operator deciding every deletion. Agents read and
propose; a deletion is the operator's act or is done on the operator's word for the list it was shown.
## The aim
Remove what is provably done, and nothing else. A cleanup that deletes one piece of unfinished work costs more
than every stale branch it removed. So every item is shown with its evidence, and anything not provably safe is
reported and left.
## Steps
1. **Take stock, reading only.** One agent on the smaller model reads, changing nothing:
- the forge: every repository, every branch **as git refs** (the branch list tool misses some, issue 949),
every open pull request with its head, title and last update;
- each working copy on the machine: its worktrees, local branches, stashes, and the folders beside the
clones, with size and newest file.
It writes one report with each item, its class and its evidence, and a list of the tools it lacked.
2. **Classify each item**, by evidence, never by name or age alone:
- **Safe** — a forge branch whose tip is contained in its repository's default branch, with no open pull
request, not protected and not a long-lived branch the mesh treats as a trunk; a worktree that is clean,
whose branch is contained in the remote default branch, untouched for two hours and not in another
session's working folder; a local branch contained in the remote default branch.
- **Likely safe, not proven** — a branch whose every commit is patch-equal to one on the default branch
(the forge lands some commits under new hashes): listed, deleted only on the operator's word for it.
- **Kept** — unmerged commits, a dirty worktree, a stash, an open pull request, a protected or trunk-like
branch, a repository of another owner, another session's worktree.
- **Unknown** — a folder or file outside git: described, never opened where a secret may be, left.
Stale open pull requests (no update for three days) are reported with their author's issue, never closed.
3. **Ask the operator** with the counts per class and the report's place. One question: delete the safe
items as listed? The likely-safe and unknown items are separate questions, item by item when few.
4. **Delete only the approved list**, each with the command that refuses an unsafe case (a worktree removal
that refuses a dirty one, a branch deletion that refuses an unmerged one). A refusal is reported, never
forced. The operator approves a deletion on a channel that proves who answered, as an ask bound to that
exact list and carried out on the warrant (ADR 0387, design 58 milestone 7, issue 884). Until that is built,
an agent session's permission check refuses these deletions, and the approved list is handed to the
operator to run.
5. **Report**: what was cleaned, what was kept and why, what was unknown, what the operator kept, and the
missing tools, each opened as an issue (`issue-tracker.open`) or added to an existing one.
## How it is checked
- The report names, for every deleted item, the evidence it was safe (contained, clean, no pull request).
- Nothing classed Kept or Unknown is deleted without the operator's word for that item.
- A cleanup's missing tools are issues, not notes in a session.
+81 -71
View File
@@ -1,90 +1,100 @@
---
layer: as-is
status: implemented
code: [mesh-controller, mesh-catalog modules/mesh-delivery, mesh-catalog modules/gitea, mesh-catalog modules/build-agent]
updated: 2026-10-17
code: [hal]
updated: 2026-08-23
decisions:
- "ADR 0239"
- "ADR 0267"
- "ADR 0276"
- "ADR 0282"
- "ADR 0289"
- "ADR 0304"
- "ADR 0350"
- "ADR 0358"
- "ADR 0360"
- "ADR 0364"
- "ADR 0382"
- "ADR 0395"
- "ADR 0398"
- "ADR 0010"
- "ADR 0010"
- "ADR 0010"
---
# Delivery — from a merge to the nodes, as it runs
# Delivery — from a push to a running node
What runs on the mesh's main branches on 2026-10-16, read from the code and the live record by research 055
(findings 1 to 12). It is what [to-be 47](../01-to-be/47-delivery-from-commit-to-delivered.md) replaces, milestone
by milestone. Until 2026-10-16 this document described the predecessor's pipeline, which no longer runs.
One trigger, three silos, and a fan-out point that is the most consequential boundary in the
mesh.
## Who does what
## The trigger
- **mesh-delivery** owns deliveries and delivery groups (ADR 0239): one delivery per commit, a group per branch
name shared across repositories. It records each transition in its state on the bus (key-value buckets
`deliveries` and `groups`), marks pull requests, asks the controller to deliver, and merges a pull request marked
ready at its head once both required checks pass against the trunk's head (ADR 0364) and the operator approved
the merge into main on a channel that proves who answered (ADR 0398 §3).
- **The controller** plans, orders, sends and judges. It computes a delivery plan for a repository and the files a
merge changes, moves only modules whose build source changed with their build dependents (ADR 0267), and
reuses a merge check's build of the same reuse key (ADR 0350).
- **The builders** (holders of the build seat) take build requests and checks from a work-queue stream on the bus
and build inside a toolchain image another module provides, with the network off; on 2026-10-13 no node had Go
or npm installed (issue 626).
- **The forge module** publishes every forge event on the bus through the forge's system webhook since 2026-10-14
(issue 729), and still polls the forge every 30 s, or every 300 s once it hears events. It keeps files of what it
announced so it does not announce twice.
A push to the forge. The forge calls a webhook; the receiving module verifies its signature
and emits an event. The coordinator resolves which modules the pushed commits affect, orders
them into dependency levels, and creates a pipeline per level.
## From a merge to a rollout
There is no second path. A manual trigger exists for a module the push detection missed, and
using it routinely is a sign the detection is wrong rather than a workflow.
- **The merge window.** The controller collects merges into an open batch; each merge restarts the window, which
closes after 90 seconds without a merge or 10 minutes after the batch's first merge (ADR 0276). A lone merge is
cut at once (ADR 0282 §2, as ADR 0304 §1 changed it). The batch and its merges are rows in the controller's
store, cut by a timer every 5 s, with a catch-up timer every 5 minutes for merges no batch holds.
- **The rollout.** A closed batch becomes one rollout, at most one commit per repository, tiered along the module
graph. Rollouts run one at a time: a batch cut while a rollout is open is queued behind it, whatever it shares
with it. Running unrelated rollouts side by side (ADR 0282 §3, ADR 0304 §2) is decided and not built (issue 525). A rollout's builds go to every machine at once, with no first-machine gate, and the rollout ends on
the machines' reports or after fifteen minutes of silence (ADR 0360). A controller timer advances rollouts every
30 s.
- **A merge during a rollout.** Its place in the next batch is a store row the open rollout's holder writes. On
2026-10-16, when another controller worked the plans, a merge was not recorded and waited until a person released
it (issue 921).
- **"Release branches".** A pull request into a branch other than a trunk is judged by the controller built from that
branch (ADR 0358); feature 57 shipped about 37 packages that way over about 2.5 days and held the controller for
55.7 h.
Detection is the pipeline's most fragile input. It has failed for reasons that have nothing to
do with the change: a webhook truncating its commit list on a large merge, a forge address
whose port broke the module-path matching. The characteristic outcome is the bad one — **a
merge that created no pipeline, and nothing said so**.
## Checks
## Three silos
- Every pull request's head gets `mesh/merge-gate` (the composed machines) and `mesh/repo-check` (the repository's
`merge-check.sh`), each judged against one trunk commit (ADR 0323). A trunk move asks the check of every active,
cleanly merging pull request again (ADR 0395).
- A delivery group's members are each checked beside their siblings' heads, and the group also gets a composed
check, `mesh/delivery-group`; each member keeps its own gate beside it (ADR 0382).
- The queue's order (delivery builds first, then ready checks, then the rest) is kept by pausing the worker and
deleting and republishing a request (issue 558); an old head's check is ended after asking the forge for the
current head (issue 726).
- mesh-delivery ticks every 10 s, reconciles with the controller every 30 s and polls the forge for ready marks
every minute, although the forge module already publishes labeled, commented and synchronized events.
Cardinality is the whole point, and the three differ
(ADR 0010):
## Where the record of what runs lives
| Silo | Runs | Where | Does |
|---|---|---|---|
| **build** | once per module feature | the build node | compile and bundle into a self-contained output |
| **publish** | once per module feature | the build node | package that output and upload it; a package-registry feature publishes here |
| **deploy** | once per module feature **per node** | every assigned node | install, configure, start, verify |
Builds and build requests, rollouts with their tiers, batches and merges, and assignments are rows in the
controller's store; artifacts are blobs in the artifact store by digest (ADR 0251); packages are versions in the
package registry. The controller writes three record mirrors to the bus: assignments, declared config and
predefined keys; of the five ADR 0289 §7 decided, the nodes, rollouts, builds and settings mirrors are not in the
code. An assignment names its module and node but not the build, artifact or rollout that put it there: which commit
runs where, and which delivery brought it, takes three verbs and a join by hand.
Commands and events are addressed per feature, not per module.
No backup is taken before a delivery moves a module that holds data; backups run nightly and on demand (ADR 0214).
Build hands over a **staged tree**, not a package. Packaging belongs to publish, so a failed
upload retries by re-packaging rather than by re-sending something stale, and build never needs
to know how each module composes its artifact. The handover is a tree in a known location,
because the build's own working directory is reference-counted and may be gone by the time a
later stage runs.
## The artifact
The artifact is **build output** — compiled and bundled with its dependency graph inlined —
never a filtered copy of source (ADR 0010).
A deploy is extract-and-run and touches no network.
The consequence is the whole cost of the decision: **anything not in the build output does not
ship.** Every file kind had to be brought into that rule separately, and each was discovered by
something silently not happening after deploy — migrations reading a source layout, provisioning
scripts reading a source layout, selection files never packaged at all.
## The fan-out, and the build node
After publish, work fans out to every assigned node. The build node is the only node that has
already passed through two silos when this happens, and that asymmetry has its own defect
class: anything advancing a node's stage must account for **both** pre-fan-out stages. Code
that knew only about the first parked the build node forever while every other node deployed
cleanly — and the recovery sweeper, which knew the same subset, reported nothing to recover.
A recovery mechanism that knows less than the thing it guards is worse than none, because it
reports success over a stall it cannot see.
## Levels
A level completes before the next begins, so a module builds against its dependencies as they
were just published. The shared library is at level zero, which means anything that breaks it
breaks the first module of every cascade.
## What green proves
**A green pipeline proves transport, not effect.** The stages report that a message was
dispatched and accepted, which is not the same as the thing being running, correct, or present.
This is the mesh's most consistent failure shape and it is not incidental to the design — it is
what the stage reporting currently measures. Documented instances include a service reported
started when the container command merely returned, an image pull failure that did not fail the
deploy, a package install that 404ed from every mirror while the job went green (see
[`04-ISSUES/001`](../../04-ISSUES/001-failed-package-install-reports-success/00-report.md)),
and a node left on old code after a failed artifact download with a version marker that had
already advanced.
A verify stage exists to close this gap. It has been built and, for a period, was never
scheduled, because the coordinator's stage list did not include it.
## What is not covered
The integration tests run only in each repository's merge check; there are no end-to-end tests of delivery yet,
and the lab is not used (the operator, 2026-10-16).
The end-to-end harness for this pipeline has not built since the workspace was removed, and
nothing reports that nothing runs it
([`04-ISSUES/005`](../../04-ISSUES/005-pipeline-test-harness-unbuildable/00-report.md)). The
pipeline's coverage is currently assumed rather than checked — which is the same class of claim
this repository exists to make people stop making.
+47 -122
View File
@@ -1,144 +1,69 @@
---
layer: as-is
status: implemented
code: [mesh-tools, mesh-host, mesh-controller]
updated: 2026-10-17
code: [mesh-catalog modules/mesh-console, mesh-tools src/mesh.ts, mesh-tools src/http.ts, mesh-tools src/runtime.ts, mesh-controller internal/broker, mesh-controller cmd/mesh-controller/check.go, mesh-controller cmd/mesh-controller/seatverbs.go]
updated: 2026-10-10
decisions:
- "ADR 0152"
- "ADR 0175"
- "ADR 0195"
- "ADR 0197"
- "ADR 0367"
- "ADR 0095"
- "ADR 0037"
- "ADR 0154"
---
# The mesh MCP server, as it runs
**The `mesh-console` container no longer exists.** Read 2026-10-17 from `mesh-tools` (package
`node-tools`), `mesh-host` and `mesh-controller` on their forges' main, and from the live mesh
through `mesh_overview`, `mesh_machine`, `mesh_runtimes` and `mesh_describe`. Checked: the
catalogue (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
in the glossary's retired-words list and in a test fixture — and none of the four nodes answers a
module of that name.
## What it is now
**The tool runner is one of the mesh's core components**, named `node-tools` beside
`mesh-controller`, `mesh-host` (the node-engine) and `nats` (the bus) — the mesh's own repositories'
check treats the four the same way, as the core whose health the mesh itself defines rather than a
module's own. `mesh-host` and `mesh-controller` supervise, compose and account for it as a core
process rather than as an ordinary assignment (`ComponentNodeTools`/`BusNodeTools` in both, the
witness and lease code that restarts and rolls it back, the account named `<node>.node-tools` on
the bus).
**It still carries a manifest, `node-tools/module.json` in `mesh-tools`,** and that manifest is
where its grants live: `invokes` names `*`, so its account may call every tool on the mesh;
`waits` names the five event families `mesh_wait` may subscribe to (the knowledge base's reviews,
mesh-delivery's transitions, the forge's status, the controller's checked event, and the operator
channel's decision for the agent module) — the bus refuses a subscription outside this list
(ADR 0367 §2); and it
`provides` one provision, the MCP endpoint, at node scope, and `listens` on the port it serves from,
opened only to the machine itself. Its code lives under `node-tools/` in the `mesh-tools`
repository (`internal/console`, `internal/announce`, `internal/bus`).
**One runtime per node, run by the node-engine as a sibling process, never a container**
(ADR 0175). It holds the node's one bus credential and may call every tool on the mesh; its account
publishes nothing else. It binds `127.0.0.1:<port>` and refuses to bind anything else, said before
binding rather than after (`node-tools/internal/console/http.go`, citing ADR 0152): whoever is on
the machine owns the mesh there, and nobody else may reach this port. An agent or a person on the
node is pointed at `http://127.0.0.1:<port>/mcp` once; the `serverInfo.name` the handshake answers
is `mesh`.
**The mesh's tools reach a person through a module the mesh assigned to their machine.** Since
2026-09-30 a workstation that is a node can be assigned `mesh-console`; the mesh mints a bus account
`<node>.mesh-console`, seals its credential to the machine, and the container binds
`127.0.0.1:<port>` with the port the mesh assigned for the manifest's declared one. An agent on the
machine is pointed at `http://127.0.0.1:<port>/mcp` and sees the mesh's tools; a person uses the same
endpoint. Nothing on the machine holds a credential a person had to carry.
## What it answers
`initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`, over HTTP, one JSON
body per request, no session and no event stream (`node-tools/internal/console/mcp.go`).
`initialize`, `tools/list`, `tools/call`, over HTTP, one JSON body per request, no session and no event
stream. `tools/list` is what the running modules answered: every tool runner built on or after that day
serves a `tools` verb for its module, and the mesh MCP server asks the catalogue for the roster and each module
for its tools. A module that did not answer is named in the list's `_meta.notAnswering`. On the day it
shipped that was 36 of 51 modules — those that serve no tools at all, and those whose rebuilt runtime the
mesh records rather than rolls out — and 62 tools from the rest.
**It announces seven tools, not five** (ADR 0195, extended by ADR 0367): `mesh_overview`,
`mesh_machine`, `mesh_search`, `mesh_describe`, `mesh_call`, `mesh_runtimes` and `mesh_wait`. The
handshake's `instructions` text names all seven and the four address forms. `mesh_overview` is
where the mesh-wide count of modules and tools is read live — a number copied into this document
would drift; asked on 2026-10-17 it answered 103 modules and 645 tools across the whole mesh, with
two module instances (`route-proxy` on two of the nodes) named as not answering rather than silently
absent.
`tools/call` reaches any tool by `<module>.<tool>`, listed or not. The mesh MCP server's grant is `*`, so what it
may call is every tool on the mesh; its account may publish nothing else and subscribes nothing.
**Addresses, exactly as the handshake states them:** `<seat>.<verb>` for a seat held once for the
mesh (`mesh-controller.status`); `<node>/<seat>.<verb>` for a seat every machine holds
(`<node>/node-packet-filter.rules`); `<node>/<module>.<tool>` for a module assigned to one machine;
and `<module>.<tool>` also for a module whose instances are interchangeable. A tool not listed can
still be called by address — listing is discovery, calling is the grant — and a module that is not
interchangeable is refused with the machines it runs on rather than guessed at.
**What exists is what announced itself** (ADR 0197). Every node's tool runner answers the NATS services
protocol's `$SRV.PING`, `$SRV.INFO` and `$SRV.STATS`, one service per process, naming in each
endpoint's metadata which module, seat, scope and machine serves it. `mesh_overview` and
`mesh_machine` gather one request's answers: at least 750ms so a runtime that only just answered
`$SRV.PING` is still waited for, up to 5s for one that said it is there but has not yet sent its
full announcement — a fixed shorter window lost the largest runtime's listing once a live
measurement found how slow its round trip over the bus could be. An announcement unchanged since
last asked (by its digest) is not re-fetched, which is why `mesh_runtimes` can show a runtime
answering in under 100ms for hundreds of kilobytes of discovery.
## mesh_wait
**The seventh tool, added by ADR 0367**, lets a session wait on the bus for an event instead of
polling: a call names one or more granted event families and an optional filter, and either answers
the first match or says it timed out. The tool runner's own bus account hears only the families its
manifest's `waits` list names; an event it hears is never taken as the state — a session reads the
state from the verb that owns it once woken. It is meant to be called in a loop from a background
command, not inline, because one call blocks until it answers or times out.
No call is refused for want of a person's approval: a tool runner serves every call it is issued, whoever made
it, and cannot tell an agent from the operator (ADR 0259 §6, issue 340). An act waits for the operator's warrant
only where its module asks first, in its own code: the agent module's instruction proposals, and the
controller's settings proposals and the actions it offers on a condition.
## The mesh's own verbs
**These already answer directly, not as a stated future.** Beside every assigned module's tools,
the tool runner asks the `mesh-controller` seat for its own verbs the same way (ADR 0154): measured
live, the mesh-controller runtime is one instance, mesh-wide, and it answers 63 verbs for that one
seat (`status`, `push`, `assign`, `build`, `artifacts` and the rest), and the `mesh-controller`
*module* answers none of its own — it serves no module tools, only its seat's, exactly as design 33
described while it was still only designed. The 56-to-93 seat-verb counts each node's tool runner
answers (below) are a different thing: the tool runner serves the verbs of whichever node-scoped
seats are held on that node (`node-backup`, `node-build-agent`, `node-service-manager` among them),
on behalf of the modules holding them, not the mesh-controller seat's — that one is served by the
mesh-controller runtime itself, a separate process, and the tool runner only asks it and relays what
it answers.
*Since 2026-09-30 evening (ADR 0154).*
The mesh MCP server asks the `mesh-controller` seat's `tools` verb beside the modules and lists every role's
tools as `<seat>.<verb>` — `mesh-controller.status`, `mesh-controller.push` and the other ten. A call
to `<prefix>.<name>` reaches the seat when the prefix is a seat declaring that verb, and the module
otherwise; `seat:<seat>.<verb>` says so outright. When the controller does not answer, the list
names `mesh-controller (seat)` as not answering and carries the modules' tools regardless. The
`mesh-controller` *module* is always named as not answering: it serves no module tools, only its seat's.
## Where it runs
## Around it
**Every node, not a per-operator assignment.** Measured live: all four of the mesh's machines
answer a `node-tools` runtime, each with its own module and tool count (between 294 and 312 tools,
56 to 93 seat verbs, in this reading). This is a fact that shipped past the design it came from: the
to-be document still describes it as assigned to "whichever machines an operator sits at"; what
actually runs is the opposite — every node the mesh holds carries one, because it is core
infrastructure the mesh itself depends on (discovery, the controller's own verbs, every module's
tools), not a convenience assigned to a workstation. There is accordingly no assign or unassign verb
for it: removing it from a node is not an operation the controller or the catalogue exposes.
- **`invokes`** in a manifest is the grant. It is composed into the bus's user list exactly as a
person's account is; the mesh MCP server is the only module that declares it.
- **`module check <file|dir>…`** on the controller's binary judges a manifest with no mesh: the strict
parse, every per-manifest problem, and the rules between the manifests given. It prints what it cannot
judge without a store rather than refusing. The mesh MCP server's own manifest was the first thing checked
with it, and the whole catalogue passes.
- **The person's client remains.** `operator issue` and `mesh tools|call|mcp` with a credential file
still work, for a machine that is not a node and for a mesh not yet able to assign anything.
`mesh tools --console <url>` goes through a running mesh MCP server with no credential; it is covered by the
runtime repository's tests and was not exercised on the live mesh.
## What this repository's prior text got wrong
## What shipped bent
The document this replaces described `mesh-console` as a catalogue module served by a container and
assigned only to workstations. That module and its container are gone; the manifest is not — it
moved to `node-tools/module.json`, still declares `invokes: ["*"]`, and gained `waits` and the
`mcp-endpoint` provision, described above. What also moved is which language builds it: the
manifest's `build.artifacts` names a Go binary built from `cmd/node-tools`, and that is what a node
runs. The repository still carries the earlier TypeScript sources (`node-tools/src/mesh.ts`,
`http.ts`, `runtime.ts`, `announce.ts`, `broker-nats.ts`, `client.ts`, `launch.ts`, `main.ts`,
`mcp.ts`) on main, but the manifest does not build from them. The quirks the prior text recorded
from the `mesh-console` era (a module registered by hand recording its source as a URL; a
*record*-policy module staying listed as not-answering until its rebuilt runtime is pushed) are not
re-checked here: they were about the `mesh-console` container's registration path, which no longer
exists.
## How it is checked
- `mesh_overview` and `mesh_machine <node>`: the live tool and seat-verb counts, and which modules
are named not-answering.
- `mesh_runtimes`: which runtimes answered discovery, how long they took, how large their
announcement was, and whether it was shortened to fit the bus — read this when an address is said
to be missing or on another machine.
- `mesh_describe <address>` for one tool's schema; a call through `mesh_call` reaches the module or
seat over the bus and the answer is its own, unshaped.
- The code: `mesh-tools` (`node-tools/internal/console`, `internal/announce`, `internal/bus`),
`mesh-host` and `mesh-controller`'s core-component handling of `node-tools` (witness, lease,
bus-user naming). Their unit tests hold the loopback-only refusal, the seven-tool handshake, the
discovery gather window and `mesh_wait`'s event-then-read-state rule.
- `knowledge-base.get` with `kind: decision` and number 152, 175, 195, 197, 367 or 154.
- A module registered by hand from the catalogue with `--source <url> --path modules/<m>` records a URL,
not a place on the git seat: `--self` takes the forge path form (`<owner>/<repository>`), which the
operator did not pass. The rebuild-on-merge matched the URL anyway.
- Modules whose upgrade policy is *record* — the forge among them — answered `tools` only once
something pushed their rebuilt runtime; until then they are listed as not answering while still
callable. That is the policy doing what it says, not a fault of the mesh MCP server.
@@ -1,116 +0,0 @@
---
layer: as-is
status: implemented
code: [mesh-controller, mesh-catalog modules/claude-code, mesh-catalog modules/gitea, mesh-catalog modules/ssh-client]
updated: 2026-10-17
decisions:
- "ADR 0266"
- "ADR 0399"
- "ADR 0409"
- "ADR 0410"
---
# Accounts on a machine, as they run
**A node record names at most two logins: the operator account, always, and an agent account, on one node of
four. Where a node names no agent account, agents run as the operator account. Nothing ties an identity to a
login, and a module that needs one declares it itself.** Read on 2026-10-17 from the controller's and the
catalogue's code on main (both local clones were a few commits behind the forge, so each claim below was
also checked against a live read) and from the live node records. The change planned in ADR 0411 to 0414
replaces this document when it is built.
## The node record
- **The operator account is stated at the controller's terminal** with `node account <node> <account> [home]`.
It is the person's login on that machine. The controller refuses to set it to the node's agent account.
- **The agent account is stated at the controller's terminal only** with `node agent-account <node> <account>
[home]`, and cleared with `--clear`. It is no verb and no setting, so an agent cannot name itself one
(ADR 0266 §1). The controller refuses root, a malformed login, a well-known service account and the
operator account.
- **Five machine facts come from the record.** `account` and `account-home` are the operator's, absent when
the node has no operator account. `agent-account` is the agent account, or the operator account where none is
named; `agent-home` is its home (`/home/<account>` when no home is stated); `agent-root` is `never` only where
the node names an agent account of its own, otherwise empty. Modules read them as `${machine:...}` words.
- **One node of four names an agent account today**, the control node. The workstation, the laptop and the
home server name none: `mesh-controller.node` says there "agents run as the operator account; no agent
account is named", and the plan for the workstation declares the `claude-code` account resource with the
operator's name and no `root` field. The tool runner also receives `MESH_AGENT_ACCOUNT` and `MESH_AGENT_HOME`.
- **Not verified:** whether the mesh-config snapshot carries either account. The controller's code reads both
from its own node table (columns for account, account home, agent account and agent home); no second place
that states them was found.
## What claude-code does with the agent account
- **It declares a `user` resource** named `${machine:agent-account}` with home `${machine:agent-home}` and
`root: ${machine:agent-root}`, and a `~/.claude` directory under that home owned by it, mode 0700. On the
control node the plan holds the resource with `root: never`; elsewhere it holds the operator's account.
Where the account is new, the mesh creates it with a home, its own group and no password (ADR 0266 §2).
- **`claude-agent`** is a file the module writes at `/usr/local/bin/claude-agent`. Run as the agent account
it starts `claude`; run as any other account it runs `sudo -iu <agent account> claude` through that
account's own sudo, carrying the operator's guard override only if it is set in that shell. The file
`/etc/claude-code/agent-account` holds the account's name.
- **The guard of ADR 0266 §28** is the module's hook on every tool call. On a node with an agent account
of its own it refuses each call of a session that runs as another account, with no override, and tells the
session to start with `claude-agent`. Its record goes to the machine's journal. The same guard also refuses
ssh to a mesh machine and writes to `/etc/hosts` and the resolver file (ADR 0245).
- **Not verified live:** this rests on the guard's code and README, not on a refusal seen on a running machine.
## The judgement that the agent account cannot become root
- **The node-engine judges `root: never` on every look**, by reading only: uid 0, a group that grants root,
a sudo, doas or polkit rule, a container runtime's socket or API port, a secret the mesh placed for another
account, a setuid program no package owns, and kernel link protection off (ADR 0266 §12 to §16). A way found,
or a question unanswered, makes the account resource unhealthy. It does not prevent any way to root.
- **The controller's self-check holds** that a node naming an agent account has it judged, in a verdict heard
within 15 minutes, as unable to become root (ADR 0266 §21 to §22). Otherwise it raises the urgent condition
`agent-can-become-root`. `mesh-controller.node` shows the verdict: for the control node it reads "cannot
become root without a person", judged the same day.
- **On the three nodes without an agent account** the check does not apply. The controller instead holds an
urgent condition `root-not-free` on each machine that runs reads or the mail notifier. Two of the three are
silenced until 2026-10-23 on the operator's decision; the home server's is open and not silenced.
## The accounts agents push as on the forge
- **Today the forge has a user per machine**, made by the gitea module's `forge-account` provision and
holding that machine's key, plus a write token the module still mints for each (ADR 0398, ADR 0400). ADR 0410
decides one forge user, `nox-agent`, in the agents' write team, each machine's key titled by machine. No tool
reads the forge's user list, so whether `nox-agent` exists yet is **not verified**.
- **The forge key is meant for the agent account** (ADR 0409 §2), but a module cannot run as that account:
the controller refuses it (issue 989, open). So the `ssh-client` module's key keeper still runs as the
operator account.
- **An ordering deadlock** between `ssh-client`, which wants `forge-account`, and `gitea`, which provides it,
stopped every node's declaration from composing on 2026-10-17 at 00:35 until gitea was registered three
minutes later (issue 986). The four urgent conditions it raised were not observed again but stay raised,
because the controller's declaration check times out and never clears them (issue 992).
Git over ssh was proven from the laptop only; the proof on the other three, and retiring the forge token the
module still places, wait on issues 989 and 991 (the latter in `fixing`).
- **Main stays closed**: a push to the main branch is refused, and a merge waits for a person (ADR 0398).
## Logins on the bus
Not here: see [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md).
## What it cannot do
- **No identity carries a login.** An agent, a person and a module are not tied to an account on a machine;
the node record names a login per node and nothing else.
- **A module declares a login itself.** Each module that needs an account writes a `user` resource of its own,
and nothing checks that two modules agree or that a login exists on every machine that needs it.
- **Agent and operator share a login on three of four machines.** There the agent runs as the operator account,
which may become root; the guard's account rule does not apply, and the exposure is a held condition, not a fix.
- **No per-agent bus password.** Every session on a machine calls through that machine's tool-runner login
(document 16), so one agent's calls cannot be told from another's.
- **Detects, does not prevent.** The judgement above lists the ways to root it looks for; a way it does not list
is not seen (ADR 0266 §17).
## How it is checked
- `mesh-controller.node` with a node name: "agents run as" line, the verdict and when it was judged, the open
conditions (`agent-can-become-root`, `root-not-free`, `uncomposable`).
- `mesh-controller.plan` with a node name: the `claude-code.agent-account` resource and its `root` field; with
`files` set, the files given, including `claude-agent` and `agent-account`.
- `mesh-controller.conditions` for the held conditions; `claude_code_guard` for the guard's record, `claude-code`
tools for its status.
- `knowledge-base.get` with `kind` decision and number 266, 409 or 410; `issue-tracker.get` with 989 or 991.
- The code: the controller's node-record and agent-account files, and the `claude-code` module's manifest and
guard in the catalogue. Their unit tests hold the node record's refusals and the guard's account rule.
-1
View File
@@ -25,7 +25,6 @@ Where the two disagree, the implementation wins and the disagreement is stated.
| [`14-a-modules-state.md`](14-a-modules-state.md) | A module's current state on the bus: what the controller creates, the runtime serves, and the first live use showed |
| [`15-the-agent-and-its-licences.md`](15-the-agent-and-its-licences.md) | The operator's agent on every machine and the licence manager that keeps its licences |
| [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md) | The bus's one list of logins, a login per module assignment, persons limited to their tools, sessions calling through the tool runner's login, and no effect on any tool |
| [`17-accounts-on-a-machine.md`](17-accounts-on-a-machine.md) | The operator and agent accounts on the node record, claude-code's own login and guard, the judgement that the agent account cannot become root, and the forge accounts agents push as |
## What these documents are not
+4 -7
View File
@@ -1,13 +1,12 @@
---
layer: to-be
status: in-progress
status: proposed
code:
- mesh-controller cmd/mesh-builder
- mesh-controller internal/builder
- mesh-controller modules/build-agent
updated: 2026-10-17
- mesh-catalog modules/build-agent
updated: 2026-10-08
decisions:
- "ADR 0405"
- "ADR 0262"
- "ADR 0233"
- "ADR 0219"
@@ -31,8 +30,6 @@ decisions:
# Building a module
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's builder (internal/builder, cmd/mesh-builder) implements the recipe kinds described here, and the sections marked built and proven live match the code. Left: not broken down into milestones; issue 1072 found it saying proposed.
[`12-a-module-repository`](12-a-module-repository.md) says what a module may build — an image, an
archive, an upstream mirror — and where the result goes. This says **how a build is modelled**, and
why the current model does not fit what a module is.
@@ -141,7 +138,7 @@ need to run. Third-party software still arrives as an image, because that is how
## What this costs, stated before it is chosen
**Every language is permanent.** It needs an SDK — broker client, sealed-credential reading, the
event envelope, tool serving — a build module that installs its toolchain on every builder (ADR 0405), and a bundler the builder understands. And the
event envelope, tool serving — a toolchain image, and a bundler the builder understands. And the
spine changes rarely but cascades when it does
(ADR 0039): with one language a contract
change is one edit; with four it is four that must land together, and a mesh whose SDKs disagree
+2 -4
View File
@@ -1,13 +1,13 @@
---
layer: to-be
status: in-progress
status: proposed
code:
- mesh-sdk src
- mesh-tools src/broker-nats.ts (and broker-amqp.ts until the rollout)
- mesh-controller internal/link
- mesh-controller cmd/mesh-controller (status)
- mesh-catalog modules/postgres, modules/keycloak (the Go provisioner loop)
updated: 2026-10-17
updated: 2026-10-06
decisions:
- "ADR 0224"
- "ADR 0230"
@@ -24,8 +24,6 @@ decisions:
# The module protocol
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: the wire this design describes is in mesh-sdk and mesh-controller's internal/link, and the design's own checks against the code (2026-09-16) agree. Left: not broken down into milestones; issue 1072 found it saying proposed.
**What a module's code and the mesh say to each other.** An SDK is an implementation of this in one
language and nothing more (ADR 0074).
@@ -1,11 +1,11 @@
---
layer: to-be
status: in-progress
status: proposed
code:
- mesh-host internal/bootstrap
- mesh-host cmd/mesh-bootstrap
- mesh-lab test/integration/one-node-mesh.test.ts
updated: 2026-10-17
updated: 2026-10-06
decisions:
- "ADR 0226"
- "ADR 0067"
@@ -16,8 +16,6 @@ decisions:
# The installation, in full
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: the node-engine's bootstrap (`mesh-host` internal/bootstrap) carries phases 2 and 3 of the installation, so the steps this text calls manual are partly automated. Left: not broken down into milestones; the text is not yet corrected against the code.
Every step from a machine with nothing to a mesh that maintains itself. Written out explicitly
because it is the procedure everything else depends on, and because the parts that do not exist
yet are easier to see beside the parts that do.
+1 -3
View File
@@ -6,7 +6,7 @@ code:
- mesh-controller
- mesh-catalog
- mesh-lab
updated: 2026-10-17
updated: 2026-09-15
decisions:
- "ADR 0067"
- "ADR 0074"
@@ -17,8 +17,6 @@ decisions:
# The work ahead
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Phase 2's blocker (issue 053) is resolved and delivered, and the forge's module declares the package registry in full, Phase 2's done-when. Whether every phase holds is checked under issue 1073, which marks it implemented or names what is left.
Everything decided this cycle and not yet built, in the order its dependencies allow. Each phase
ends at something provable on a running mesh, because a phase that ends at a claim is a phase that
went missing without anything complaining.
+3 -5
View File
@@ -1,8 +1,8 @@
---
layer: to-be
status: in-progress
code: [mesh-controller]
updated: 2026-10-17
status: designed
code: []
updated: 2026-09-25
decisions:
- "ADR 0084"
- "ADR 0027"
@@ -11,8 +11,6 @@ decisions:
# 23 — Choosing a provider
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's internal/catalogue/resolve.go implements the model described here, and designs 26, 27 and 29 rely on it. Not yet checked against this text clause by clause, nor given an as-is: issue 1074.
A provision is named for what the consumer's code is coupled to
(ADR 0027):
`postgres-database`, not `database`. That decides *what kind* of provider satisfies a requirement.
@@ -5,7 +5,7 @@ code:
- mesh-controller internal/inventory
- mesh-controller internal/catalogue
- mesh-controller cmd/mesh-controller
updated: 2026-10-17
updated: 2026-10-02
decisions:
- "ADR 0181"
- "ADR 0182"
@@ -14,13 +14,6 @@ decisions:
- "ADR 0051"
- "ADR 0113"
- "ADR 0117"
- "ADR 0399"
- "ADR 0400"
- "ADR 0412"
- "ADR 0415"
- "ADR 0416"
- "ADR 0417"
- "ADR 0422"
---
# 29 — A node has operator accounts, and the mesh owns what lives under a home
@@ -63,15 +56,6 @@ These are a **family**, not one module: an `ssh-client` module, a shell module,
module, each a *universal-tier* consumer of the account fact — assigned wherever a person logs in,
which is every node, unlike the graphical stack that a capability gates.
**A folder a module owns says how it treats a file it did not write** (ADR 0416, ADR 0422). Declaring a
directory under a home owns it (ADR 0182 §1), and the owner alone writes there and reacts to every file
that enters it, never losing what the file held. The manifest says how, on the directory itself:
`unknown` is `move-out` (the node-engine moves every path the manifest does not declare and `knows` does
not name into `~/.local/state/mesh-moved/<module>/`, with a list), `module` (the module's own process
reacts, as ssh-client does in `~/.ssh`) or `program` (everything in it is the program's, as in a
service's data directory). `move-out` is only for a folder under a home, the home of the account the
assignment runs for (ADR 0412 §5). A folder many modules write in is not declared at all.
## 3. The whole of `~/.ssh` is the mesh's — with one boundary drawn inside it
The predecessor owned a single file (`~/.ssh/config`) and left the rest alone; it drifted, because
@@ -98,9 +82,6 @@ ADR 0117 draws for the uplink and the sshd
module draws for the firewall: **the mesh must never be able to arrange the one failure that severs
its own way back in.** The carve-out is not a convenience; it is that rule, in `~/.ssh`.
Since ADR 0415 and ADR 0417 the found class no longer holds in `~/.ssh`: ssh-client moves out what it
does not know into a kept folder, and never moves a key or `authorized_keys`, which keeps this boundary.
## 4. Keys are the mesh's to generate — through a CA, and existing keys are adopted, not replaced
Key *generation* is the mesh's, not each node's improvising its own. The clean form is an **SSH
@@ -239,17 +220,6 @@ each need a decision before the modules that replace the generators can be writt
The modules §2 calls *a family* — the shell, the terminal, the desktop, everything under a home that is not `~/.ssh` — are designed in [37 — The operator's machine](37-the-operators-machine.md), under ADR 0173 to ADR 0177. This document keeps `~/.ssh`, the CA and the roster files. Two things it listed as not built are decided there: user-scoped services (ADR 0177) and the one-off steps a hook used to run (declared state, or a seat's verb).
## The forge over SSH — 2026-10-16
`~/.ssh` gains the account's own key for the forge. ADR 0399 decides that each machine's account has an SSH
key made on the machine, that the forge registers its public half on the machine's agent user, and that
`ssh-client` writes the forge's SSH key into `known_hosts`, ssh's configuration entry for the forge and git's
rule that rewrites the forge's `https://` address to its SSH address. ADR 0400 decides how the public half
travels: `ssh-client` writes it to its own state, keyed by node and account, and the forge's provider reads it
there, since a manifest's contribution cannot carry a value made at run time. The forge's SSH address is its
machine's name on the mesh, where its SSH port is open; the key's file is `~/.ssh/mesh_ed25519`, beside the
operator's own keys, which stay theirs (§3). Built under issue 955.
## References
- The gap was found generating `~/.ssh/config` from the *HAL* registry (`hal/terminal`'s
@@ -266,4 +236,3 @@ operator's own keys, which stay theirs (§3). Built under issue 955.
- ADR 0117,
ADR 0118 —
the never-sever-the-channel rule and the found-vs-owned semantics, applied here to `~/.ssh`.
- ADR 0399, ADR 0400 — git over SSH with a key made on each machine, and how its public half reaches the forge.
@@ -1,8 +1,8 @@
---
layer: to-be
status: in-progress
code: [mesh-controller]
updated: 2026-10-17
status: proposed
code: []
updated: 2026-10-06
decisions:
- "ADR 0238"
- "ADR 0221"
@@ -15,8 +15,6 @@ decisions:
# 30 — The mesh updates itself on a push
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's delivery plans, merge gate and merge window (plan.go, release_plan.go, merge_gate.go, merge_window_bus.go, changeplan.go, with their migrations), which design 47 builds on. Not yet checked against this text nor given an as-is: issue 1075.
**Today the mesh does not update itself; a person drives the pipeline by hand, and one class of
change freezes it.** A code change lands in `mesh-controller` or `mesh-catalog`, and getting it onto
the machines is a sequence somebody types. The predecessor's pipelines rebuilt and redeployed on a
+8 -15
View File
@@ -1,13 +1,11 @@
---
layer: to-be
status: implemented
code: [mesh-tools, mesh-host, mesh-controller]
updated: 2026-10-17
status: designed
code: [mesh-catalog, mesh-tools, mesh-controller]
updated: 2026-10-03
decisions:
- "ADR 0197"
- "ADR 0195"
- "ADR 0175"
- "ADR 0367"
- "ADR 0160"
- "ADR 0159"
- "ADR 0152"
@@ -165,16 +163,11 @@ matched it by URL.
## What this does not settle
- **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through
the `mesh-controller` seat, under ADR 0154 — settled, not open. [As-is 13](../00-as-is/13-the-console.md)
records it live.
- **Narrowing the tool runner's grant per assignment.** ADR 0046 makes it a setting; nothing reads
one yet. Owned by [design 58](58-agent-work-as-the-meshs-own.md) milestone 6 (issue 883): an
identity asked for each assignment is one of that milestone's parts.
- **A person's identity behind the mesh MCP server.** The mesh still sees only the tool runner's own
account, never the caller's; design 15 raised the question and leaves it open. Owned by the same
milestone (design 58 milestone 6, issue 883): its outcome is that every person, agent and module
becomes an identity with a role, and a call carries which one made it.
- Narrowing a mesh MCP server's grant per assignment. ADR 0046 makes it a setting; nothing reads one yet.
- The mesh's own verbs on the bus. Design 33's third family; this document only says where they appear
once they exist.
- A person's identity behind the mesh MCP server. The mesh sees the mesh MCP server's account; design 15 keeps the
question open.
## References
@@ -2,10 +2,8 @@
layer: to-be
status: in-progress
code: [mesh-catalog modules/claude-code]
updated: 2026-10-17
updated: 2026-10-13
decisions:
- "ADR 0411"
- "ADR 0412"
- "ADR 0343"
- "ADR 0274"
- "ADR 0273"
@@ -308,27 +306,21 @@ operator's word — and, for the operator's own home, only at the terminal or on
## 9. The account the agent runs as
*Added 2026-10-08 by ADR 0266; changed 2026-10-17 by ADR 0411 and ADR 0412.*
Everywhere above, "the operator's home" and "the operator account" mean **the account the agent runs as**:
the machine account of the identity the module runs for (ADR 0412). claude-code declares that it can run for
an agent identity, in its assignment or when a session is started: started for `nox` it runs with the agent's
home; started for the operator, it is the default run, with the operator's configuration. On a
machine where no agent is marked as the agent account, its assignment is the default run, as every module's is.
*Added 2026-10-08 by ADR 0266.*
Everywhere above, "the operator's home" and "the operator account" mean **the account the agent runs as**.
That is the operator account, unless the node names an **agent account** of its own. The control node does.
**The fact.** The machine accounts come from the identity seat (ADR 0411); the operator no longer states the
agent account at the controller's terminal. A machine's agent account is the machine account of the agent the
identity seat marks as the agent account there, on the operator's Approve: `nox` on the control node. There this module's
assignment is run for that agent, and the controller refuses one that is not. The controller resolves the
machine facts `agent-account`, `agent-home` and `agent-root` from it, and gives the tool runner the account and
its home. Where no agent is marked (every other machine today), the facts name the operator account and `agent-root`
is empty, so nothing changes there, though a login `nox` exists for the modules run for it.
**The fact.** The operator states the agent account at the controller's terminal. No verb and no setting
writes it. The controller resolves the machine facts `agent-account`, `agent-home` and `agent-root`, and gives
the tool runner the account and its home. Where the node names none, the facts name the operator account and
`agent-root` is empty, so nothing changes there.
**What the module declares.**
- No login. The mesh makes the agent's machine account (home, own group, no password) from the identity, and the
node-engine judges on every look that it cannot become root without a person, by the ways ADR 0266 §12 to §17 list.
It detects those ways and no others; it prevents nothing. The `user` resource named by `agent-account`,
which this module declared until ADR 0411, goes.
- A `user` named by `agent-account`, with `root` set from `agent-root`. Where the account is the agent's own,
the mesh creates it (home, own group, no password) and the node-engine judges on every look that it cannot
become root without a person, by the ways ADR 0266 §3 lists. It detects those ways and no others; it
prevents nothing.
- `~/.claude` under that account's home, owned by it.
- `claude-agent`, a command that starts the agent as that account through the operator's own sudo, or
directly when already run as it.
@@ -2,122 +2,70 @@
layer: to-be
status: in-progress
code: [mesh-controller, mesh-catalog modules/mesh-delivery]
updated: 2026-10-18
updated: 2026-10-16
decisions:
- "ADR 0426"
- "ADR 0421"
- "ADR 0420"
- "ADR 0419"
- "ADR 0418"
- "ADR 0408"
- "ADR 0407"
- "ADR 0406"
- "ADR 0405"
- "ADR 0404"
- "ADR 0403"
- "ADR 0402"
- "ADR 0401"
- "ADR 0398"
- "ADR 0395"
- "ADR 0382"
- "ADR 0377"
- "ADR 0364"
- "ADR 0362"
- "ADR 0360"
- "ADR 0350"
- "ADR 0335"
- "ADR 0323"
- "ADR 0313"
- "ADR 0306"
- "ADR 0304"
- "ADR 0299"
- "ADR 0298"
- "ADR 0282"
- "ADR 0276"
- "ADR 0270"
- "ADR 0267"
- "ADR 0250"
- "ADR 0249"
- "ADR 0239"
- "ADR 0238"
- "ADR 0233"
- "ADR 0214"
- "ADR 0236"
- "ADR 0201"
---
# 47 — Delivery: from a commit to delivered
**A change reaches the machines in one of two ways, both owned by mesh-delivery: a hotfix (one pull request,
delivered at its merge) or a feature (one branch name across its repositories: in two or more, one delivery
group merged in one pass on the operator's one Approve and delivered by one delivery plan; in one, a lone
delivery). A design's milestones are features delivered one after another. The trunk is what runs; there is no
"release branch"** (ADR 0401, ADR 0418 §1, ADR 0421 §2).
**A delivery is one commit in one repository, from the moment its pull request's head is announced to the
moment every machine of its deploy plan runs it, or to the moment it fails, is superseded or is stopped. A
delivery group is two or more deliveries that share a head branch name, delivered as one unit in order. Both
are owned by one module, `mesh-delivery`, which records every step and asks the controller for each act.**
(ADR 0239.)
**Every step starts on an event, and the state lives on the bus.** The forge's events, the controller's
events and the nodes' reports move a delivery; the bus's own rules keep the queues, the merge window and the
order of a plan's tiers, which only the module graph gives (ADR 0419); and one chain, from module to commit to build to rollout to assignment, is kept in the bus's
key-value store as its only source (ADR 0406, ADR 0407, ADR 0408). A running delivery never touches merging:
every merge is recorded at once for the next delivery plan of what it touches.
**From its merge on, a delivery is delivered by the rollout of its batch.** The controller collects merges in a
rolling merge window and plans each batch once, as one rollout with at most one commit per repository, one rollout
at a time. Every delivery the rollout answers ends with it
(ADR 0276).
This is the delivery of [to-be 10](10-delivery.md) carried out. To-be 10 says what is built: the comparison
of source and artifacts decides, and nothing here changes that. This design says who answers for one
commit's journey, in what order the steps are allowed, and where each fact of it is kept. It was rewritten on
2026-10-16 from the decisions of research 055, which replaced the "release branches", the toolchain image, the
controller's delivery tables and the timers that polled them.
commit's journey and in what order the steps are allowed.
## The parts
```
forge (system webhook) ──forge.pull-request.* / merged / status──► the bus ◄──built / checked / plan-moved── controller
│ ▲ (planner, order,
│ │ registration,
▼ │ sending)
mesh-delivery │
(deliveries, groups, a key per │
pull request, the state table) ▼
node-engine on every node
builders (build seat) ◄── work queues: delivery builds, approve-asked checks, other checks ───────────────── (reports; backups first)
forge's holder ──pull.updated / .merged / .closed──► mesh-delivery ◄──checked / plan-moved── controller
(statuses, the view, (deliveries, groups, (planner, gate,
the notes) the table, its state) registration,
▲ │ │ sending, walk)
└── its tools: note, view, status ◄───────────────┘ └── its verbs: delivery-plan, -order,
-check, deliver, delivery-stop,
delivery-walks
```
- **mesh-delivery** (the seat `mesh-delivery`) owns hotfixes, features and milestones as deliveries and delivery
groups, decides each step on events, keeps one key per pull request, and asks the controller for each act
and the nodes' backup seats for the backups a delivery needs. It never sends to a machine, writes a
declaration or registers a build (ADR 0239 §25).
- **The controller** keeps planning, ordering, registration, sending and judging. It writes the chain's
links it owns — builds, rollouts with their tiers, assignments — and the merge window's
keys, all on the bus, by compare-and-set, while it holds the lease.
- **The builders**, the holders of the build seat, build and check with toolchains installed on their node, as
the build seat's own account, and take their jobs from the bus's work queues, delivery builds first
(ADR 0405, ADR 0406).
- **The forge module** (`gitea`) publishes every forge event on the bus through the forge's system webhook
(issue 729): pull requests updated, synchronized, merged and closed, and commit statuses; no label or comment
moves a delivery, and mesh-delivery subscribes to none (ADR 0421 §11). It sets statuses, keeps the delivery's view as one comment on the pull request, and appends
to the commit's note under `refs/notes/mesh-plan`.
- **Each node's backup seat** (`node-backup`) backs up the data a delivery plan moves there before the plan
sends anything (ADR 0404).
## Two ways a change reaches the machines
*ADR 0401.*
| Way | What it is | How it is delivered |
|---|---|---|
| **hotfix** | one pull request in one repository | at its merge, as a lone delivery; a lone merge is cut at once (ADR 0282 §2, as ADR 0304 §1 changed it) |
| **feature** | one branch name across its repositories; in two or more, its pull requests are one delivery group; in one, a single pull request | a group is merged in one pass once its composed check passed at every member's head and the operator gave the group's one Approve (ADR 0418 §4, ADR 0421 §2); its merges are one batch and one delivery plan, each module built once and sent once (ADR 0418 §1); in one repository, as a lone delivery like a hotfix |
- **The trunk is what runs.** Every delivery is cut from a repository's trunk. A merge into any other branch
starts no delivery, and the controller says so for that merge (issue 889). Holding merged work back, on a
branch beside the trunk or per module, was dropped: from 2026-10-13 23:52 to 2026-10-16 09:32 feature 57's
branches held the controller for 55.7 h, and 28 of 68 other merges touched a held module (research 055,
finding 3).
- **A design's milestones are features, one after another.** Each milestone is delivered as a feature when its
own group is ready; the next starts when the previous one's delivery plan is done (playbook 04).
- **A milestone is functional, its delivery shaped technically.** A milestone is named by what the operator or
the mesh can do once it is delivered; how it ships is shaped by the delivery plan its changes would have, asked of
the controller's planner through mesh-delivery's `what-if` (ADR 0239 §3): the modules it moves and their fan-out.
- **What a merge waits for.** After its checks, a merge waits for one act of a person: the operator's Approve,
asked as soon as both required checks passed at the head (ADR 0421 §1, §3). For a group the Approve is asked
once, when every member's checks and the group's composed check passed, and covers every member's head
(ADR 0421 §2); it is the review ADR 0382 §6 asks (ADR 0421 §9). No ready mark, label, comment, forge review or
recheck stands before it. The operator's Approve is asked for every merge into main, a hotfix's too
(ADR 0398 §3; the operator, 2026-10-16 23:40).
- **The controller** stays the only writer of what it owns: machine declarations, bus objects, grants,
registration. It keeps the primitives: the planner (one function, ADR 0238 decision 3), the gate, the trunk
rule, a gated send, the rollback and the rollout of one trunk commit's tiers across machines. It still answers
every pull request's head with its check itself, so a check never depends on mesh-delivery.
- **mesh-delivery** is a Go bundle holding the mesh-scoped seat `mesh-delivery`. It owns the delivery, the
group, the state table and their state, and nothing else.
- **The forge's holder** sets a commit's statuses, keeps the delivery's view as one comment on the pull
request, and appends to the commit's note under `refs/notes/mesh-plan`.
- **The build seat** builds and checks, asked by the controller as before. **A change is built once** (ADR 0350):
a check that passes also builds what its merge would move into the artifact store's scratch namespace, under a
reuse key (the source fingerprint and the builder's version), and the rollout of the merge commit copies a build
of the same key, which the check's verdict names, by digest instead of building. Otherwise the rollout builds and
says why, per module, in its record. Check builds go when the rollout answering the merge ends, when the pull
request closes unmerged, and after seven days.
## A delivery
@@ -126,21 +74,12 @@ Its id is `<owner>/<repository>@<twelve characters of its commit>`. It holds:
- the pull request (number, base, head branch, description), or none for a commit that reached the trunk
without one;
- its **delivery plan**: build plan, deploy plan and verdict, as the controller's planner computed them for
its diffset (ADR 0238 §2, as ADR 0323 §1 changed it);
its diffset (what ADR 0238 called the "change plan", under the glossary's name);
- its **state**, its **transitions** (the last hundred, each with when, the event, from, to and why), and its
**machine steps** while delivering;
- its **Approve**: the ask, the operator's answer with its warrant, and the review record the pull request was
opened with (ADR 0421 §5, §8);
- once merged, **the commit it landed on the trunk as**, and the delivery plan that delivers it: the plan of the
batch that answers its merge, which may carry a later commit of its repository that contains it. Every member
of a delivery group is delivered by the same plan, and its delivery ends with that plan's result
(ADR 0418 §1).
**One key per pull request.** Beside the delivery, mesh-delivery keeps one key per open pull request on the
bus: its head, the trunk commit its current check is asked against, and its verdict, written by
compare-and-set (ADR 0406 §4). It writes a new head when the forge says one, and a new trunk commit only when it
asks the pull request's check again (ADR 0395 §1). A running check watches that key and stops when the head or
the trunk commit in it moves.
- once merged, **the commit it landed on the trunk as**, and the rollout that delivers it: the rollout of the batch
that answers its merge, which may carry a later commit of its repository that contains it (see *The merge
window and the batch*).
## The state table
@@ -153,422 +92,390 @@ allows.
|---|---|---|---|
| proposed | a head was announced and is being checked | 1 hour from when its check is asked; none while its verdict is out of date and no check is asked | nothing: the check's own watchdog (S6) speaks |
| checked | a verdict arrived | 1 minute | nothing: it is decided at once |
| ready | it passed; its Approve is asked (a group member's once the group is ready), and it waits for it and the merge | none | — |
| ready | it passed; it waits to be merged | none | — |
| rejected | it failed its check | none | — |
| published | merged on its trunk, recorded in a batch; it waits for its plan to start | 30 minutes | `superseded` when a plan that does not answer its merge took over its plan; `go` when its plan started |
| held | it waits for a person: a failed backup (ADR 0404 §4), a policy of record; it ends by itself when it moves nothing or a later merge covers it (ADR 0270) | 1 day | nothing: it is the operator's |
| delivering | its plan runs: backups, then builds asked and sent tier by tier | 2 hours | `delivered`, `failed` or `superseded`, as the plan's key says |
| published | merged on the trunk its modules follow, its rollout opened and asking nothing; it waits for its turn or its word | 30 minutes | `superseded` when a rollout that does not answer its merge took over its rollout; `go` when its rollout started |
| held | it waits for a person; read again each tick, so it ends by itself when it moves nothing or a later merge covers it (ADR 0270) | 1 day | nothing: it is the operator's |
| delivering | its rollout runs; its builds are asked and registered tier by tier | 2 hours | `delivered`, `failed` or `superseded`, as the rollout's record says |
| delivered, failed, superseded, stopped, retired, covered | final | — | — |
**A verdict holds for one trunk commit** (ADR 0323). The check judges the head merged into the trunk's head
when the builder clones it, and the verdict names both. When another merge moves the trunk, a `ready`
delivery goes back to `proposed` on the event `trunk moved`, and mesh-delivery asks the check of every active,
cleanly merging open pull request into that trunk at once (ADR 0395, *A move of the trunk asks every check
again* below). An Approve already asked stays; the merge waits for the fresh check, and the Approve is not asked
again for a trunk move (ADR 0421 §1, §3). The row from `proposed` to `checked` takes only a verdict that names the trunk's head.
at the moment the build seat clones it, the **trunk commit**, and the verdict names both. When another merge
moves the trunk, a `ready` delivery goes back to `proposed` on the observed event `trunk moved`, with both
commits in the transition's reason; a `rejected` one stays `rejected`, its statuses saying the verdict is out
of date. Nothing is asked for it: a recheck, a new head or the merge tool asks the check again. The row from
`proposed` to `checked` takes only a verdict that names the trunk's head as its trunk commit, so a verdict for
an older trunk is kept and shown and does not count, and the rows reach a fixed point. Checked by
mesh-delivery's `TestATrunkMoveSendsAReadyDeliveryBackToProposed` and the table's own rollout.
**What needs no person ends by itself** (ADR 0270): a merge that moves nothing is `delivered` at its merge; a
merge a later delivered merge contains is `covered`, by the forge's word that the later merge commit contains
it; a merge whose plan said its modules unchanged is `delivered` when that plan ends (issue 611); a stop whose
plan ended without building its modules is read as covered once a later merge covers it (issues 474, 480).
What this leaves as it is, said so nobody reads more into it:
**History is retired, not held** (ADR 0250). A delivery made from the forge's word at its merge, heard fifteen
minutes or more after it, with no plan of it open, goes to `retired` at adoption. **Nothing of a published
delivery is registered before its plan starts.**
- **mesh-delivery's trunk head is the last merge it heard** into that trunk while the delivery was open. A push
to the trunk that is not a merge it hears does not move it; the forge's holder, which reads the branch, and
the merge tool, which compares commits when asked, still catch it.
- **A verdict that names no trunk commit**, from a build seat older than ADR 0323, counts in mesh-delivery as it
did: such a delivery stays `ready`. The forge reads the same verdict out of date once its trunk moves, and the
merge tool refuses it and asks the check. The two agree once the head is judged again.
- **A verdict no build judges** (a change that touches no module, in a repository with no `merge-check.sh` or
not the mesh's) names the base branch's head the forge announced with the head as its trunk commit.
- **In a busy repository the merge tool can refuse, ask, and refuse again**: another merge may move the trunk
while the asked check runs, and the new verdict is then out of date too. Each refusal asks once more; the
merge goes through once a check finishes before the next merge.
- **A check that was asked and never answers** is listed by `stalled` an hour after the ask, and is asked again
with `mesh-delivery.recheck`, which the merge tool's refusal names.
**The machine steps.** While a delivery is `delivering`, each machine its plan reaches has a row with the
module and the build: `backed-up`, `sent`, `applied` or `failed`, and `silent` when no report came within
ApplySilentAfter (fifteen minutes). They are read from the plan's key, written by the controller from the
nodes' reports, never inferred from time.
The transitions are those of ADR 0239 decision 2, ADR 0270 and ADR 0323. A delivery that is
merged while not `ready` goes to `held`. Its check did not pass, and only a person decides that it goes on. A
delivery merged with no rollout answering it within ten minutes is held too, with the true reason, and listed by
`stalled` at once. Nothing waits silently.
## Order without a person
**What needs no person ends by itself** (ADR 0270):
*ADR 0419, ADR 0418 §2.*
- *a merge that moves nothing* (its plan builds nothing, or only adds a module the controller builds and
sends nowhere) is `delivered` as soon as it is merged, whatever its check said;
- *a merge a later delivered merge contains* is `covered`: every module it moves was sent to every machine
running it by the rollout of a later merge into the same trunk, delivered on its own passing check, and the
forge says that later merge commit contains it (`gitea_contains`, asked once per pair and kept). Merge
order is only the first filter, and a forge that cannot answer means not yet covered. `mesh/delivery`
says `covered: a later merge that contains it was delivered` as a success. A module that runs on one
machine only is covered too: its rollout records the empty rest as sent once its first machine passes
(ADR 0270, as corrected 2026-10-12);
- *a merge whose rollout said its modules unchanged* moves nothing too, and is `delivered` when that rollout ends,
whichever commit the rollout carried (issue 611). A delivery plan made while another rollout built a module reads
that module whole and names it; the rollout, cut later, reads the build source that build recorded (ADR 0267) and
may find none of the changed files in it. The rollout's record then says so in `delivery.unchanged`: each such
module with why, "its build source holds none of the changed files", and not in its modules, which keep
meaning what the rollout builds. A delivery whose every module the rollout built or said unchanged is delivered,
saying which matched what runs. One with a module the rollout neither built nor said unchanged is read as below.
Checked by the controller's `TestAWalkThatMovedNothingSaysWhichModulesMatchedWhatRuns` and mesh-delivery's
`TestAMergeWhoseModulesMatchWhatRunsIsDelivered`;
- *a stop whose rollout ended without building its modules* (issue 474) stays `stopped`, a final state, and
is read as covered once a later merge covers it by the same test: `mesh/delivery` then says `covered after
its stop` with the covering merge, once, as a success. No state or transition is added (issue 480);
- the covered row is tried before `go`, from `held` and from `ready` past the ten minutes.
A delivery group has no order among its members, and nobody writes one (ADR 0419 §1). Inside a plan only the
module graph orders: the planner orders the build and deploy tiers by edges between modules, never between
repositories or members (ADR 0418 §2, ADR 0419 §3). Modules with no edge between them share a tier.
**History is retired, not held** (ADR 0250).
A delivery made from the forge's word at its merge, heard fifteen minutes or more after that merge, with no rollout
of it open, was replayed from the bus's history: it reached the machines before this owner was there. It goes
to `retired` at adoption, before any check, hold or rollout, and its pull request is not told. A held delivery that
fits the same rule is retired by a person's `retire-history`, never released. **Nothing of a published delivery is registered before its rollout
starts**, because a send carries every registered build that waits on its machine (ADR 0236 §4a), and a
build registered before its turn would be carried by somebody else's send.
| Edge | A module goes before | Read from |
|---|---|---|
| *built by* and *stands on* | the modules built by it or standing on it | the module graph |
| *SDK before its users* | every module built on the SDK, `mesh-sdk` | the module graph |
| *verb before caller* | a module whose change adds to its invoked verbs a verb this module's change adds to a seat's served verbs | the manifests |
| *version skew* | (the controller, when the plan moves it) every module whose manifest the plan changes | the change |
- **Engine before controller is the one default.** When a plan moves both the node-engine and the controller and
no edge orders them, the node-engine's tier comes first; an edge either way wins over it (ADR 0419 §4).
- **Each tier says the edge that placed it.** The plan, `delivery-order`, `show` and the plan's note name, for
each module placed after another, the edge and its rule, and where the default was overruled, the edge that
overruled it (ADR 0419 §5).
- **A cycle is refused before anything merges.** Edges that form a cycle fail the group's composed check, or a
lone pull request's check, naming every module and edge in it. The way out is in the change itself (a verb
promised optional before it is required, ADR 0246), never an order typed by a person (ADR 0419 §6).
- **No `after:` line is read.** mesh-delivery reads no `after:` line, no refusal asks for one, and a description
that holds one is told once on its pull request that the line is not read (ADR 0419 §2).
**The SDK by version.** A Go module names the SDK by its released version and fetches it from the mesh's forge,
checked against its recorded checksum; vendored copies go. Inside a group, a member may name the SDK member's
head: its merge check resolves that commit from the forge and builds against it, and the head stays fetchable
after the group merges because the forge keeps every pull request's head. The catalogue's check accepts an SDK
commit on the SDK's trunk or the head of an SDK pull request the forge holds, open or merged (ADR 0402 §2 to §4).
## Checks
### One check per pull request, kept by the bus
*ADR 0323, ADR 0350, ADR 0395, ADR 0406.*
- **Three work queues, taken in order.** Delivery builds go on a work-queue stream of their own; checks of pull
requests whose Approve is asked or given on a second (ADR 0421 §10); every other check on a third. A builder takes from the first, then the
second, then the third. Each check stream has one subject per pull request and keeps only the newest request:
a newer request replaces one still waiting, by the stream's rule. The fresh check of such a pull request, asked
on the merge path, is published on the second stream; a request left behind on the third for a head and trunk commit already
judged or running is acknowledged and dropped when taken, by reading the pull request's key. Nothing pauses the
worker or deletes and republishes a request (issue 558).
- **A running check stops itself.** It watches its pull request's key and stops when the head or the trunk
commit in it moves, without asking the forge (issue 726). An ended check delivers no verdict, so none of its
builds is reused.
- **A failed builder loses nothing.** A job a builder took and never acknowledged goes to another builder
after its wait.
- **A change is built once** (ADR 0350). A check that passes also builds what its merge would move into the
artifact store's scratch namespace under a reuse key, and the plan of the merge commit copies the build of the
same key, named by the check's verdict, instead of building.
### A move of the trunk asks every check again
*ADR 0395.*
- A trunk move asks the check of every active open pull request into that trunk that merges cleanly, against the
new head; a conflict is told once on the pull request, not checked.
- A pull request with no push for 7 days and no Approve asked or given is left out; its statuses say out of date
and that a push or a recheck asks its check (ADR 0421 §10).
- For a group, a move asks each member's own check, and the composed check runs again through those checks; the
group's own merges, made in one pass on its Approve, ask its members nothing again (ADR 0418 §4).
- Its cost is at most one check per active, cleanly merging pull request per trunk move; issue 944 measures it.
### A delivery group's checks
*ADR 0239 §14, ADR 0323 §6, ADR 0382, ADR 0403, ADR 0418 §4, ADR 0421 §2, §9.*
- **The composed check is each member's gate.** The group's composed check composes every machine with all
members' heads, each merged into its own trunk's head first, judged by the controller built from the group's
controller member when it has one (ADR 0382 §3). Its verdict sets `mesh/delivery-group` and each member's
`mesh/merge-gate`, and names every member it read. No gate runs for a member alone.
- **Each member's own repository check stays required.** Each member's `mesh/repo-check` runs its repository's
suite with the siblings read beside it (ADR 0382 §2).
- **No composed verdict, no gate.** A member's gate is pending while no composed verdict at the members' current
heads exists. A member's new head, close or base move puts the composed verdict out of date, and with it every
member's gate. When mesh-delivery cannot say the groups, the pull request is checked alone and its verdict
says so (ADR 0382 §1).
- **A group merges in one pass, with no order.** Once the composed check passed at every member's head and the
operator's one Approve covers those heads, mesh-delivery merges every member in one pass, no member waiting for
another's merge or delivery (ADR 0418 §4). The Approve is the review ADR 0382 §6 asks: the merge tool reads its
warrant for those heads in place of an approving forge review (ADR 0421 §9). A member the forge refuses is
merged again on its fresh check while the group's batch stays open for it.
## The Approve and the merge
*ADR 0421, ADR 0398 §3, ADR 0406 §1.*
- **Asked as soon as the checks pass.** When a pull request's delivery reaches `ready` (both `mesh/merge-gate` and
`mesh/repo-check` passed at its head against its trunk's head), mesh-delivery asks the operator's Approve of its
merge into main at once, on a channel that proves who answered (ADR 0274; Telegram today). The ask names the
pull request, its issues, the reviewer's verdict, the delivery plan and the trunk commit the checks judged. A
verdict that names a trunk commit the trunk had already left raises no ask: mesh-delivery asks that head's check
against the trunk's head, and its passing verdict raises the ask (ADR 0421 §1).
- **A group is asked once.** One ask names every member and its head, once every member is `ready` and the
composed check passed at those heads; one Approve covers them all (ADR 0421 §2).
- **The reviewer's verdict is attached, not a gate.** The review finishes before the push (playbook 07), and the
session that opens a pull request gives its review record: who reviewed it, the commit the review approved and
the verdict. The ask carries it; with none, or one naming another commit, the ask says so in its first line and
the operator decides (ADR 0421 §5).
- **The merge.** mesh-delivery merges through the forge's merge tool at the heads the Approve names, only while
they are still the heads and both checks passed against the trunk's head. When the trunk moved, the merge tool
asks the fresh check, and the event of its passing verdict asks the merge again; no timer asks it, and no person
rechecks (ADR 0421 §3, §12).
- **A push or a Decline ends the Approve.** A new head ends its ask or its Approve, for a group every member's, and
is said once on the pull request; the new head is asked again once its checks pass. A Decline merges nothing and
is said once (ADR 0421 §6).
- **A failure is said, never merged past.** An approved head whose check failed is said once on the pull request
and listed by `stalled`; so is a merge the merge tool kept refusing for an hour (ADR 0421 §7).
- **Recorded.** The ask, the answer with its warrant and the review record are kept on the delivery, shown by
`show` and said in mesh-delivery's log; the merge's transition names the Approve (ADR 0421 §8).
**The machine steps.** While a delivery is `delivering`, each machine its rollout reaches has a row with the
module and the build: `sent` (the first machine's send, or the rest's), `judging` (the gate's healthy
readings so far), `passed`, `failed`, `rolled-back`. They are read from the rollout's own record in the
controller's `plan-moved` event, never inferred from time.
## The merge window and the batch
*ADR 0276, ADR 0282, ADR 0299, ADR 0304, ADR 0407.*
*2026-10-10, by ADR 0276.*
- **Every merge is recorded at once.** The forge's merged event records a merge into the open batch of what it
touches, or opens one, by whichever controller holds the plans. A merge is never refused, held or dropped,
and needs no person's release (issue 921). The bus's durable consumer keeps every merge event until it is
recorded; no catch-up timer looks for missed merges.
- **The window is two keys.** An open batch's window is a key whose time to live is `merge-window` (90 s),
refreshed by each merge into the batch, beside a key set once when the batch opens whose time to live is
`merge-window-at-most` (10 minutes). The first to expire closes the batch, heard by a watcher; no cutter
polls. A stop that records merges again into the open batch closes its window too (ADR 0299 §3).
- **A group's merges are one batch.** A batch holding a merge of a delivery group with a member not yet merged
does not close when either key expires; once the group's last member merged, the window runs again from that
merge. A group whose last member has not merged ten minutes (`merge-window-at-most`) after its first raises a
condition naming the member and why, and nothing of the group is delivered while it waits (ADR 0418 §3).
- **A lone merge is cut at once**: no batch it shares a repository, module, graph edge or delivery group member
with is assembling (ADR 0282 §2, ADR 0304 §1, ADR 0306).
- **The plan.** A closed batch becomes one delivery plan: every commit in it, tiered along the module graph
across repositories, at most one commit per repository, the latest of its trunk, each module it moves built once
and sent once to each node that runs it. It answers every merge in the batch, its own commits and the earlier
merges a later commit contains (ADR 0276 §2, as ADR 0299 §7 changed it; ADR 0418 §1). No member of a group has
a turn: only the module graph orders its tiers (ADR 0418 §2).
- **A plan fixes its commits when it starts.** A plan starts at its first send, after mesh-delivery's word, given once for a
group's plan when every member is merged (ADR 0418 §2), and
from then nothing that merges changes it. A plan still waiting for its word is folded into the next batch
(ADR 0276 §4).
- **Plans that share something run one after another.** A batch that shares a repository, a module or a graph
edge with an open plan waits for it, and starts with every merge recorded since when that plan ends. A batch
that shares none runs beside the open ones, so a hotfix is never held by an unrelated feature's delivery.
- **The ends.** A plan done ends every delivery it answers: `delivered` for its own commits, `covered` for an
earlier merge it carried by a later commit. A plan that failed fails its own commits' deliveries; each earlier
merge it answered is then delivered on its own commit, the newest first, until one is delivered (ADR 0276 §3). A
group's plan fails as one: no member is stopped because another failed, what earlier tiers delivered stays, and
the fix is a new change (ADR 0418 §5).
- **Shown.** `plans` lists the open batch first, with its window's time left and its merges, and a batch waiting
behind an open plan names that plan; the controller says the batch as `plan-moved` in the state `assembling`.
- **The window.** The controller puts every merge into a branch a module follows into the open **batch**.
Each merge restarts the **merge window**; it closes when none arrived for `merge-window` (90 seconds by
default) or at the latest `merge-window-at-most` (10 minutes) after the batch's first merge. The batch, its
merges and its times are kept in the controller's store and survive a restart.
- **The cut.** When the window closes and no rollout is open, the batch becomes one rollout: one delivery plan for
every commit in it, tiered along the module graph across repositories, with at most one commit per
repository, the latest of its branch. The rollout names every merge it answers: its own commits, and the
earlier merges of a repository that its later commit contains.
- **One rollout at a time.** Merges that arrive while a rollout is open join the next batch. A rollout that started
is never superseded. A rollout still waiting for its word when the next batch is cut is folded into the new
rollout, which answers every merge the folded one answered.
- **The controller's own path.** A merge that moves a module whose rollout waits for nobody's word (the controller,
the node-engine, the tool runner, the bus, mesh-delivery) never shares a batch with one that waits for
mesh-delivery's: it has a batch of its own, with its own window, and the catalogue's batch queues behind its
rollout like any other. An own-path rollout folds no waiting catalogue rollout; that rollout's merges are batched again
behind it, so no catalogue delivery skips its turn (ADR 0276, decided during the build, 2026-10-10).
- **A late merge.** A merge heard after a later merge of its repository and branch was put in a rollout is
answered by that rollout: the assembling batch, the open rollout, or, when that rollout was delivered, it ends
`covered` at once.
- **The ends.** A rollout done ends every delivery it answers: `delivered` for its own commits, `covered` for
an earlier merge of a repository it carried later, naming that merge. A rollout that failed fails its own
commits' deliveries and marks none delivered; each earlier merge it answered is then rolled out on its own
commit, the newest first, one rollout each, until one is delivered. A rollout stopped stops every delivery whose
merge it rollouts again nowhere (below).
- **A group inside a rollout.** The pairs of a delivery group's order (ADR 0249) are edges of the rollout's
tiers beside the module graph. mesh-delivery gives a rollout its word only when every delivery it carries is
at its turn.
- **The bookkeeping.** mesh-delivery ties a rollout to every live delivery whose merge it answers, follows a
folded rollout to the rollout that took it over, and never ties a rollout to a delivery that has ended. A waiting
rollout no live delivery holds is adopted as a delivery of its own, or listed by `stalled` at once.
- **Shown.** `plans` lists the open batch first: "assembling: 13 s left (at the latest 00:31:09); grouped:
novox/mesh-catalog@48bda475 (answers 553b7191), novox/mesh-controller@a6bc0931; plan not yet calculated",
and a batch waiting behind an open rollout names that rollout. The controller says the batch on the bus as
`plan-moved` in the state `assembling`, and mesh-delivery's `deliveries` and `show` say it for each merged
delivery it holds.
- **Bounds.** A batch still assembling a minute past `merge-window-at-most` with no rollout open, and a batch
waiting behind an open rollout past that rollout's bound, each raise a condition. A merged delivery that no rollout
answers within ten minutes is held with the true reason (the open rollouts of its repository and that none
names its merge, or that the controller holds none) and listed by `stalled` at once.
## A backup before the send
*ADR 0214 §7, ADR 0233, ADR 0404.*
## A stop on a shared rollout
When a delivery plan is ready to send, each node it reaches backs up, through its `node-backup` seat, every item
the plan's moved modules keep there whose protection is a backup (a copy or a dump). An item protected by
`redundancy`, one that says `backup: none`, and a cache are never asked, whatever their class. A node with no backup holder backs up
nothing, and the plan says so for that node.
*2026-10-11, by ADR 0299 (issue 459), with its progressive insight of the same day.*
- **The send waits for every backup.** The controller sends nothing of the plan to any node until every backup
it asked has answered done.
- **A failed backup holds the delivery.** A backup that fails, or does not answer within ten minutes, holds the
delivery with the node, the module and the backup's own words as the reason, and raises a condition for the
operator. A person's `release` asks the backups again, and the send goes on when they pass.
- **What it costs.** About 85% of data modules back up in under 30 s; the slowest measured, postgres on the home
server, about 165 s (research 055, finding 5). A plan moving no backed-up item costs nothing more.
## The send and the rollout
*ADR 0360, ADR 0362, ADR 0377, ADR 0406 §5.*
- **Every machine at once.** A plan's builds go to every machine that runs them in one step, tier by tier; a tier
is asked once the tier before it is built and sent (ADR 0360 §1, §6). No machine goes first and nothing waits
for a health reading. A module of policy `record` that would recreate a container or move data waits for a
person (ADR 0377).
- **Every move is said.** The controller says every move of a plan as `plan-moved`, a move a command makes among
them, so mesh-delivery needs no periodic reconcile; it reads `delivery-walks` once when it starts.
- **A rollout advances on events.** The `built` and sent events of a tier ask the next; the nodes' reports end
the rollout (ADR 0360 §7, with ADR 0362's provisioning report). Each send puts a key whose time to live is
ApplySilentAfter (fifteen minutes); its expiry names the machine silent. There is no rollout ticker.
- **A failed apply is loud and fixed forward** (ADR 0360 §8); the node-engine's witness still puts back a
controller or tool runner that does not come up (ADR 0360 §9).
## A stop on a shared plan
*ADR 0299.*
One plan carries every merge of its batch, so a stop ends only the change it is for:
- **No stop by member.** A group's plan fails as one (ADR 0418 §5), so mesh-delivery asks no stop of a member
after a failed one. The mechanism stands for a person's stop with the way `again`: the controller ends the plan
and records every other merge it answered again in the open batch of its kind, whose window the stop closes.
- **What is left out with it.** A later merge of the stopped merge's repository, and a merge that moves a
module a left-out merge moves, would deliver the stopped change; each is left out too, its delivery `stopped`,
naming why.
- **A person's stop.** `stop` of a delivery whose plan carries others first answers what it would take down, and
acts on `way`: `again` (the others delivered again without it) or `walk` (the whole plan stopped).
- **What it does not do.** What the stopped plan already sent stays on the machines it reached; putting it back
is issue 509's.
One rollout carries every merge of its batch, so ending a rollout for one delivery ends it for every other
delivery on it. A stop therefore ends only the change it is for:
- **A group's stop.** When a member after a failed one is stopped and its rollout is open, mesh-delivery asks
`delivery-stop` for that rollout with the stopped members' merges to leave out, every member the same pass
stops on that rollout in one ask. The controller ends the rollout, said as stopped, and rolls out every other merge
it answered again.
- **What is left out with it.** A later merge of the stopped merge's repository and branch contains it. A
module is built at its branch, so a merge that moves a module a left-out merge moves would deliver it too.
Each such merge is left out as well, until no more is, and its delivery ends `stopped`, naming the stopped
merge it contains or whose modules it shares. An earlier merge of that repository sharing no module is
rolled out again.
- **Where the others go.** They join the open batch of their kind, where merges heard while the rollout ran
already wait, so an older commit never rollouts after a newer one; only with none open do they form a new
batch. The stop closes that batch's window, so the unrelated merges in it, and any heard after the stop
before it is cut, are cut without the rest of their window. Its rollout withholds the left-out merges' modules
where it would build them only as dependents, and its record names them. A batch not yet cut can be stopped without a merge too: the merge goes
to a stopped record of its own, and the batch goes on without it. The stopped record, the batch and every
merge moved are written in one transaction.
- **The rollout's record.** The stopped rollout names the batch that rolls out its merges again, every merge it left
out, and each module it already sent that nothing rollouts again, with the machines it reached. A delivery
whose merge is rolled out again goes from `delivering` back to `published` (the row `rewalk`), or stays
`published`, and follows the fresh rollout once it is cut. A delivery whose merge was left out, and every
delivery on a rollout stopped whole, ends `stopped`, naming the rollout and the stop.
- **A stopped merge never ships by a rollout.** mesh-delivery asks every open rollout or batch that names a stopped
delivery's merge to leave it out, and never gives such a rollout its word. When the controller refuses, it
stops the whole rollout.
- **A person's stop.** `stop` of a delivery whose open rollout carries other deliveries does nothing at first: it
answers with each of them, which of them contain this merge and end either way, and two ways given with
`way`: `again` (the rollout ended and the others rolled out again without this merge) or `walk` (the whole rollout
stopped, every delivery on it with it). A delivery alone on its rollout is stopped at once. A delivery is
marked stopped only once its rollout is; a stop the controller refuses leaves it as it was.
- **What it does not do.** What the stopped rollout already sent stays on the machines it reached, and its
asked builds still register, so a later send may carry them further; the stop names the machines, and
putting them back is issue 509's. A merge of the same repository waiting elsewhere, or one that moves the
stopped merge's modules, contains the stopped change and delivers it when rolled out; the stop names each it
can see, and holding a merged change back for good is a revert's job.
## The delivery budget
*ADR 0282, ADR 0304 §4, ADR 0313, ADR 0360 §12, ADR 0362 §5.*
*Decided 2026-10-10 by ADR 0282.
Where it differs, this section replaces the window for every merge and the one rollout at a time above, slice by
slice as each slice below is built; until a slice is built, the text above is what runs.*
A delivery's **delivery time** runs from its merge until every machine its plan sent has reported the send
applied, and for a provisioned module also placed. A **leaf** delivery's budget is three minutes, a **core**
delivery's ten. A delivery held for a person, a failed backup among them, is shown and held to no budget. Each
phase is kept — the window, the wait behind a plan that shares something, the backups, the build, the send, the
reports — and a delivery over its budget raises `delivery.<class>.over-budget`, naming its longest phase. The
**fix time** (ADR 0313) runs from an issue's first move to `fixing` to its last fix delivered, and contains the
delivery time.
**The target.** A delivery's **delivery time** runs from its merge to every node of its deploy plan that is heard
from running its modules' builds and reporting them healthy. A **leaf module's** delivery budget is five minutes;
a **core module's** (the controller's own path and the mesh's resolver) is ten. A delivery held for a person is
shown and held to no budget. Measured on 51 rollouts on 2026-10-09 and 10, a leaf rollout took a median of 6.0 minutes
(7 of 20 within five), a core rollout of one tier 5.6 (worst 8.8), and a controller rollout dragging two catalogue tiers
14.2 (issue 338).
## Builds without a toolchain image
*A wider measure, around the delivery budget (2026-10-12, ADR 0313, extending ADR 0282).* The delivery budget
above runs from the merge; the operator's own target is wider, the **fix time**, which runs from an issue's
first move to `fixing` to the moment its last fix is delivered. Each issue's class carries a **fix budget** too
(five minutes leaf, ten core, the delivery budget's own figures), and the tracker splits a fix time into four
phases — implementing, review, checks, and merge to running (the delivery time above) — so a fix over its
budget is said loud, naming its longest phase, and clears at the next fix of its class delivered within budget.
The delivery budget stays as written: a measure inside the fix budget, not replaced by it.
*ADR 0267, ADR 0350, ADR 0405.*
**What changes in the rollout.**
- **One build module per language** (Go; Node and TypeScript) installs its toolchain as a declared package on
every node holding the build seat, pinned to one version that is the same on every builder. The toolchain
image and its module retire.
- **Every builder runs the same toolchain, and says so.** Each builder names its toolchain versions with every
build outcome, and the controller's self-check raises a condition while two builders differ.
- **Builds and tests run directly**, as the build seat's own account on its node, in no container. A
repository's `# mesh-check-toolchain` line selects which installed toolchain runs its check.
- **A judge has an account of its own.** A judge built from a sibling's merge (ADR 0382 §3) is built and run as a
second account of the Go build module, whose home, configuration and cache the build seat's account cannot write,
so a check's script cannot change it.
- **Every dependency comes by the version its lock file names**, checked against its checksum: the mesh's own
code from the mesh's forge, its own packages from its package registry, any other from its public source.
- **A builder fetches from the forge over SSH with a key of its own**, as the build seat's account, under a forge
account for that builder that reads the mesh's repositories and pushes nothing.
- **A build's reuse key** holds the installed toolchain's version and the build module's recipe version in place
of the toolchain image's digest, so a toolchain update rebuilds only the modules of that language.
- **A lone merge is cut at once**: no batch assembling, and no member of its delivery group still unmerged.
- **Independent rollouts run at once**: no shared repository, module or module-graph edge with any open rollout. One
node judges one rollout at a time; a send to a node under another rollout's judgement waits for its verdict; no leaf
build goes to a node where a core build is judged, and no leaf reading counts while a new controller is judged.
- **The first-node gate reads by class**: three readings for a core module, as ADR 0236 decided; two for a leaf
module with a long-running resource; one for a leaf module whose every resource is a tools bundle.
- **A tier's first nodes are judged at once.**
- **The bus's grants arrive before the gate** (ADR 0298): when a rollout's builds change the bus's user list (a new
tool, subject or user), the machine holding the bus is sent that list alone, every build there kept, before
anything is judged; a step that would carry anything else is refused unsent and said on the gate line.
- **Every phase is kept**: the window, the wait, the build, the send, each reading, the rest's first healthy
report; the plan shows an expected landing time from the last seven days' medians.
## One chain, on the bus alone
*Rollouts beside each other (2026-10-12, ADR 0306, extending ADR 0304, which narrowed the bullets above to what
a change shares).* Building the rollouts at once found what those bullets left open, and ADR 0306 decides it:
*ADR 0408.*
- **A rollout's builds are its own, never another's backlog.** A send composes a module whose build another open
rollout owns at the build its node was last sent, and neither carries nor judges it: that build is moved and
judged only by its own rollout, so a failed rollout puts back only its own builds. What waits on a node with no open
rollout to move it is still backlog, carried and judged by the next gated send there (this changes ADR 0236 §17
for builds an open rollout owns).
- **A delivery group still assembles.** A merge whose delivery group has a member not yet merged waits for the
merge window whether or not a batch is assembling, so a group's members roll out in one batch, in their order.
- **Related means everything the rollouts would move.** A module in common is any module a rollout plans to build or
send, the dependents of what its merges moved included; a change whose modules are not known yet is related to
every change. Two related rollouts are neither cut beside each other nor judged beside each other on one node.
- **A starved judgement is said.** A leaf judgement set aside by core builds three times in a row raises a
warning naming the rollout and the core rollouts that set it aside.
- **A node's own fault fails every judgement on it.** A node found unwell as a whole (a failed apply, an
unhealthy network or engine) fails every judgement open on it, leaf and core, each rollout putting back only its
own builds.
- **A rollback beside another rollout's judgement** behaves as it does today; whether it should wait is tracked by
issue 528.
Module, commit, build, published package or artifact, rollout and assignment are one chain, each link
answerable from the next. A **rollout** here is the controller's record of one batch's delivery; a delivery's own
delivery plan (*A delivery* above) stays the delivery's, and names the rollout that delivers it.
*How it is checked:* the controller's tests ADR 0306 lists, one per clause: an unsent build of rollout A left out
of rollout B's send and not taken up as backlog while A is open; a group's second member joining its first's batch;
overlapping dependents keeping two rollouts apart; the warning on the third set-aside; a failed apply failing every
open judgement on its node.
| Link | Names | Written by | Bucket |
|---|---|---|---|
| build | its module, repository and commit, its reuse key, what it published: an artifact by digest or a package release by name and version | the controller | `mesh-controller_builds` |
| rollout | the commit it fixed per repository, its tiers, the builds it carried, the machine steps, and every assignment it changed with the build before and after; its states and transitions | the controller | `mesh-controller_rollouts` |
| assignment | the module and node, the build, artifact or package and commit it runs, and the rollout that put it there | the controller | `mesh-controller_assignments` |
*A busy container on the first node (2026-10-13, ADR 0335, citing ADR 0282 §1).* A container may declare `busy`,
a command that says whether it holds work a replacement would lose; the build agent declares it, so a merge
into the controller's repository no longer throws away the checks in flight. When a rollout's send would replace
such a container while it says busy, the node-engine leaves it running, applies the rest, and replaces it once
it says idle, or after 15 minutes at the latest. Only that node's rollout waits: the rollout says it waits for a busy
container there, its first-node gate starts at the report that says the container replaced, and the wait is a
phase of the delivery time of its own, never exempted from the delivery budget. A delivery it puts over budget
is said over budget, with that wait as its longest phase. *How it is checked:* the controller's test that a
rollout whose module's container waits as busy on its first node says so and starts that node's gate only once the
node reports it replaced, and the walk-phase test that the wait is a measured phase between the send and the
judgement.
Beside the chain, on the bus too: deliveries and groups (mesh-delivery's `deliveries` and `groups`, ADR 0239 §37),
one key per open pull request (mesh-delivery), and each open batch with its merges and its window's two keys (the
controller, ADR 0407).
**Who builds what.**
- **The bus is the only source.** Each bucket is written by the controller that holds the lease, by
compare-and-set, and the controller reads its own buckets to decide; a rollout resumes from its key after a
restart. No table of the controller's store holds builds, rollouts, batches or assignments, so no record mirror,
audit or healer exists for them; the nodes, settings, declared-config and predefined-keys mirrors stay as
ADR 0289 and ADR 0356 have them. A module reads a bucket by naming it in `reads`; mesh-config reads the
assignments bucket.
- **No key of the chain is removed.** Each bucket keeps up to 64 earlier revisions of each key as its history. A
value is never cut to a summary and stays under the bus's largest message. The bus's storage is declared as data
protected by a backup, so it is backed up nightly and before a delivery moves the bus.
- **One read answers each question.** For any module on any node, one read of its assignment names the commit,
the build, the artifact or package and the rollout that put it there; one read of a rollout lists every
assignment it changed.
- **The tables go by a numbered migration**: builds, rollouts and assignments are written once into the buckets,
waiting build requests onto the work queues and an open batch into its keys; then the tables are dropped from the
controller's store and its consolidated schema.
- **The artifact store and the package registry keep the bytes**; the chain names them (ADR 0251).
| Part | Owner | What it adds |
|---|---|---|
| the rollout's phases and readings | mesh-controller | each phase's start and end and each reading in the rollout and in `plan-moved`, the rest's first healthy report per node, `durations` of kind `walk-phase`; a numbered migration |
| the delivery time | mesh-delivery | each delivery's delivery time, class and phases; the verb `times` (budgets, the week's median and worst per class, the deliveries over budget and their longest phase); a line atop `deliveries`; phases in `show` |
| the condition | mesh-controller | a self-check probe reading `times`, raising `delivery.<class>.over-budget` (a warning) and clearing it at the next delivery of that class within budget; the line in `status` |
| the lone merge | mesh-controller | the cut at once when nothing waits to join |
| rollouts at once | mesh-controller | the independence test, one judgement per node, a core rollout's ground, a repository's next batch |
| judgement by class | mesh-controller | the readings per class in the first-node gate; a tier's first nodes judged at once |
| the landing time | mesh-controller, mesh-delivery | an expected landing time in `plans`, in `plan-moved`, and in `show` |
**Slices**, each one pull request per repository, each with its acceptance sentence:
| Slice | Delivers | Done when |
|---|---|---|
| 1 — the measure | the rollout's phases and readings (controller); the delivery time, `times` and the line in `deliveries` (mesh-delivery); the probe and its condition (controller) | `mesh-delivery.times` shows the week's leaf and core median and worst delivery time, and a leaf delivery over five minutes raises `delivery.leaf.over-budget` naming its longest phase |
| 2 — no window for a lone merge | the cut at once | a lone leaf merge's build is asked within fifteen seconds of its merge, read from its rollout's phases |
| 3 — judgement by class | the readings by class, a tier's first nodes at once | a tools-only leaf module passes its first-node gate on one reading, read from its rollout |
| 4 — rollouts at once | the independence test and the per-node rule | two unrelated catalogue merges a minute apart are both delivered within five minutes, neither waiting for the other |
| 5 — the landing time | the expected landing time | `plans` shows it for an open rollout, and the week's deliveries land within a minute of it at the median |
Issue 338's path precision is the planner's, tracked by issue 338; the core budget is met by controller merges
only once it is built.
## A delivery group
- **Membership**: the pull request's head branch name, across the mesh's repositories, among open pull
requests. Two or more make a group. A group that has started delivering takes no new member.
- **Order**: `after: <repository>` lines in a member's description, and the controller's `delivery-order`
over the graph (ADR 0239 decision 4). The plan shows the order and why each pair is ordered: *declared*,
*built by*, *version skew*, *engine before controller*, *by name*.
- **Precedence** (ADR 0249): when rules order one pair both ways, the higher one decides. *Declared* comes
first, then *built by* and *version skew*, then *engine before controller*. The pair shows the rule that
won and the rules it won over. Rules of one rank both ways are a contradiction. The group is `rejected`,
and the reason names both members, both rules and the `after:` line that would settle it.
- **Check**: the members' heads composed together, one gate over every machine with all their definitions,
each head merged into its own trunk's head first and named with that trunk commit (ADR 0323). It is asked
by mesh-delivery through `delivery-check` when the group forms or a member's head moves, and posted on
every member's head as `mesh/delivery-group`. A member rebased or pushed is a new head with its own check:
it takes its old head's place in the group, even once the group is delivering, and the composed check runs
again for the new heads instead of holding the group. A merge into a member's trunk that is not one of the
group's members puts the composed check out of date: the group reads `checking` with the reason, and the
composed check runs again once a member is judged again. Checked by mesh-delivery's
`TestAGroupsComposedCheckGoesOutOfDateOnAMergeThatIsNotItsMember` and `TestARebasedMemberIsComposedAgainNotHeld`.
- **Each member's own check reads its siblings** (ADR 0382): the group's other open members into a base of
the same name are read beside a member's own check at their head merged onto their base, and a mesh-controller
sibling's merge judges it, so changes that are only consistent together pass their own checks. The controller
learns the siblings from mesh-delivery's `groups`; the verdict names each one. A sibling's new head, close or
base move puts that verdict out of date, and the merge tool merges such a member only when
`mesh/delivery-group` passed at the heads read and every sibling head read is reviewed.
- **Delivering**: in order. A member waits in `published` until the one before is `delivered` or `covered`.
A member published while its group's composed check is asked and not answered waits there for the answer;
one held because that check had not passed is *lifted* back to `published` once it passes or warns for the
heads that merged and its own rollout is still open (ADR 0270).
- **A failed member**: the members after it are `stopped`, naming it, and each one's merge is left out of its
rollout while the rollout's other merges are rolled out again (ADR 0299). The members before it stay delivered.
Its own builds were put back at its gate.
A group's state is derived from its members and its composed check, and is shown, never stored apart from
them.
## What every transition does
In this order, each by its owner:
1. **kept**: mesh-delivery puts the delivery in its state by compare-and-set before anything else; what it owes
outside is kept with it and retried from ten seconds doubling to an hour, at most sixty times (ADR 0250);
1. **kept**: mesh-delivery puts the delivery in its state (`deliveries`, one key per delivery; `groups`, one
key per group). Nothing is said before it is kept. What it owes outside is kept with it and retried, later
each time, from ten seconds doubling to an hour. The forge's answer that what it is for does not exist ends
it at once, and sixty failed tries end it too, each said among the owner's refusals (ADR 0250);
2. **said**: the event `mesh-delivery.transition` (and `mesh-delivery.group` when a group's derived state
changes), with no secret or address;
3. **noted**: a line on the commit's note, appended by the forge module as the forge's own account;
4. **reflected**: the delivery's view on the pull request, `mesh/delivery` on the head and `mesh/delivery-group`
on every member's head.
A doubled step is refused by compare-and-set and by the bus's message de-duplication, which also replaces the
forge module's files of what it announced (ADR 0406 §6).
changes), with the delivery's id, from, to, event, why and plan summary, and no secret or address;
3. **noted**: mesh-delivery asks the forge's holder (`gitea_note_append`) to append a line to the commit's
note: the head while the delivery is off the trunk, the commit it landed as once it is on it, and at its
end a line of what was executed there. The forge's holder writes it in its own repository as the forge's
own account, and a line already there is not appended again;
4. **reflected** (not for history, whose pull request the forge may no longer hold): the delivery's view, one comment on the pull request with the state, the plan, the machine
steps and the group, kept current (`gitea_delivery_view`); the status `mesh/delivery` on the head, and
`mesh/delivery-group` on every member's head (`gitea_commit_status`). Every status links to the pull
request, `mesh/merge-gate` among them.
## The verbs
**mesh-delivery's seat**:
**mesh-delivery's seat** (served by its holder; the first five read):
| Verb | Answers or does |
|---|---|
| `deliveries` | every delivery not final, the final ones of the last day, and every one `stalled` still lists |
| `show` | one delivery or group whole: plan, the edge that placed each tier, the Approve, transitions, machine steps, the chain's links |
| `groups` | every group with its members and its derived state |
| `what-if` | the delivery plan a diffset would have, asked of the controller's planner |
| `deliveries` | every delivery not final, the final ones of the last day, and every final one `stalled` still lists, one line each; filtered by state, repository or group |
| `show` | one delivery or group whole: plan, transitions, machine steps, order, the rollout |
| `groups` | every group with its members in order and its derived state |
| `what-if` | the delivery plan a diffset would have, asked of the controller's planner, kept nowhere |
| `table` | the state table and the machine-step table, with bounds and H2's repairs |
| `stalled` | every delivery held past its bound, with the transition H2 may take |
| `recheck` | an exception verb, no step of the flow: a check asked again for a proposed delivery nothing else asks again, an inactive one or a failed verdict gone out of date (ADR 0395 §6, ADR 0421 §12) |
| `release` | a held delivery goes on, with why; for a failed backup it asks the backups again |
| `stop` | a delivery to stopped, with why; on a shared plan it first answers what it would take down (ADR 0299) |
| `close` | H2's only verb: the transition the table names for a stalled delivery |
| `retire-history` | held history to retired (ADR 0250) |
| `times` | each class's budget, the week's median and worst delivery time, and the deliveries over budget |
| `stalled` | every delivery held past its bound, with the transition H2 may take, and every waiting rollout no live delivery holds |
| `recheck` | a rejected or ready delivery back to proposed, with why |
| `release` | a held delivery to delivering, with why: a person's word |
| `stop` | any delivery not final to stopped, with why; its rollout is ended through the controller. On a rollout that carries other deliveries it first answers what ending the rollout would take down, and acts on `way`: `again` or `walk` (ADR 0299) |
| `close` | H2's only verb: the transition the table names for a stalled delivery, refused otherwise |
| `retire-history` | held history to retired, with why: one by id, refused when the rule does not take it, or every held one, answering how many and naming those it left; `dry` changes nothing |
**The controller's verbs for it**: `delivery-plan`, `delivery-order` (the edges that order a plan's tiers,
ADR 0419 §2), `delivery-check`, `deliver`,
`delivery-stop`, `delivery-walks`, and `plans` (the open batch, the open plans, and a what-if for a repository
and its files). A person has `plans go <plan> --why`.
**The controller's verbs for it** (on the mesh-controller seat): `delivery-plan`, `delivery-order`,
`delivery-check` (a group's heads composed, or one head checked again), `deliver`, `delivery-stop` (which
takes the merges to leave out and rolls out the rollout's other merges again without them, ADR 0299),
`delivery-walks`. A person has `plans go <plan> --why`, which does what `deliver` does under their name and
is recorded as a hand act. The controller says every rollout it keeps as `plan-moved`, from a verb's command
too.
**The forge's holder's tools for it**: `gitea_note_append`, `gitea_delivery_view`, `gitea_commit_status`. It
also announces `pull.closed`, and a merge's head and the statuses the forge holds on it.
## The store
Key-value state on the bus, declared by the module (ADR 0201): `deliveries` and `groups`. One writer, the
seat's holder, whose code changes state from a single goroutine. A final delivery is kept for thirty days and
then removed. Its note on the commit keeps its record after that. The bus's own snapshot (ADR 0235) backs it
up.
## The bootstrap
- A plan that moves the controller, the node-engine, the tool runner, the bus or mesh-delivery is the
controller's own, started by the merge; mesh-delivery records it (ADR 0239 §31). Such a merge shares one batch
and one plan with the other merges of its delivery group, and that plan is started by the controller when the
group's batch closes, not by its own merge (ADR 0418 §6).
- Any other plan waits for `deliver` while the seat has a holder on record; with none, the controller starts it.
- A replaced controller or mesh-delivery carries on from the bus: the keys, the queues and the durable
consumers hold every step, so nothing is lost or repeated (ADR 0406 §6).
- A rollout that moves the controller, the node-engine, the tool runner, the bus or mesh-delivery is the
controller's own, started by the merge and witnessed on the machine. mesh-delivery records it.
- Any other rollout waits for `deliver` only while the seat has a holder on record. With none on record, the
controller starts it as it always did.
- A waiting rollout is said by the controller whatever mesh-delivery says of itself: S16 raises
`plan.<walk>.waiting` after 30 minutes, urgent after 4 hours, naming the merges the rollout answers and
`plans go <walk> --why` as the way on.
A holder that is down is also D3's `holder-silent`; one that is up and never says go is said by S16 alone.
## Acceptance tests
## The switch
What the build proves before this design is implemented (research 055's direction):
1. The controller's change (the seat, its verbs, `plan-moved`, the wait for a holder's word, `plans go`) is
merged and rolls out as the core does. With no holder on record it behaves exactly as before.
2. The catalogue's change (mesh-delivery, the forge holder's notes and view) is merged. mesh-delivery is built
and published.
3. A person assigns mesh-delivery to the control node. The seat now has a holder on record. Rollouts that start
from then on wait for its word. Rollouts already open finish on their own and are adopted as deliveries in
`delivering`.
4. To step back, unassign it. Waiting rollouts are then started by the controller at its next pass.
1. A feature across the controller and the catalogue with a new seat verb is delivered as one group, with no
`after:` line, one composed check, one Approve and one delivery plan: its merges are one batch, each module is
built once and sent once, its commits are carried by that one plan, and the plan names the edge that placed
each tier.
2. An SDK change and a Go module using it are delivered as one group.
3. A data module is backed up before a delivery moves it, and a failed backup holds the delivery.
4. A build on any builder uses the installed toolchain with no container, and a toolchain update rebuilds only
modules of that language when its version changes.
5. Two merges into one repository within a minute leave each open pull request with one queued check, by the
stream's rule.
6. The operator's Approve is asked within seconds of the event of a pull request's checks passing, with no ready
mark, label or comment written by anyone.
7. A controller replaced in the middle of a rollout resumes it with no step lost or repeated.
8. The merge window closes by its key's expiry.
9. A hotfix to a module the running feature's delivery does not touch reaches the machines in minutes, beside it.
10. For any module on any node, one read of its assignment names the commit, build, artifact or package and the
rollout that put it there, and one read of that rollout lists every assignment it changed.
11. Two merges into one repository while a delivery of that repository runs are both delivered by the next plan,
with no person's release and nothing held in mesh-delivery.
12. No table in the controller's store holds builds, rollouts or assignments.
## Phases
## What retires
- The "release branches" and the controller's judge of them (ADR 0358, superseded by ADR 0401).
- The toolchain image and its module (ADR 0405).
- The window cutter, the merge catch-up timer, the rollout ticker, mesh-delivery's tick, reconcile and
ready-mark poll, the forge's poll as a trigger, the queue reordering, the forge-asking cancel, and the forge
module's announcement files (ADR 0406, ADR 0407).
- The ready mark: the label `mesh/ready`, the `ready at` comment, `mark-ready`, the forge module's
`ready-authors` setting, and mesh-delivery's subscriptions to labeled, unlabeled and commented events
(ADR 0421 §4, §11); a recheck by hand as a step of the flow (ADR 0421 §12).
- A group's order: `after:` lines, the ranked rules and the order of the repositories' names (ADR 0419, which
supersedes ADR 0249).
- Delivery member by member, a stop of a member after a failed one, and the own path's separate batch for a
group's merges (ADR 0418 §1, §5, §6).
- The controller's tables of builds, build requests, rollouts and their tiers, batches and merges, and
assignments; the record mirrors, probe D17's audit and healer H6 for those kinds (ADR 0408).
## Milestones
Confirmed by the operator on 2026-10-17 (playbook 04). Each is one release of playbook 07; the next starts
when the previous one's delivery is done.
| # | Outcome | Acceptance tests | Repositories | Issues |
|---|---|---|---|---|
| 1 | A merge during a running delivery is never dropped and needs no person's release; the merge window lives on the bus; a rollout that shares nothing with an open one runs beside it | 8, 9, 11 | mesh-controller | 973 (with the rest of 525) |
| 2 | A ready mark is acted on within seconds, and each pull request has at most one waiting check | 5, 6, 7 | mesh-controller, mesh-catalog (mesh-delivery, gitea) | 974, 975, 976 |
| 2b | build-agent and route-proxy live whole in mesh-controller (ADR 0420, accepted 2026-10-17): the controller accepts a reviewed move, mesh-delivery announces each merge with its Approve, and the two modules move; built as two releases before the rule that a milestone is one release | — | mesh-controller, mesh-catalog (mesh-delivery, build-agent, route-proxy) | release 1: 1025; release 2: 1019 |
| 3 | A feature with a new seat verb across the controller and the catalogue is one group, one batch, one delivery plan and one Approve: no order lines, no ready mark, each module built and sent once | 1, 6 | mesh-controller, mesh-catalog (mesh-delivery, gitea) | 977, 978, 1022, 895 (with 1046 folded into it: unit and integration tests split, each level within its bound); 605 (1021 closed as its duplicate) is resolved by this build (ADR 0419 §7) |
| 4 | A delivery backs up the data it moves first, and a failed backup holds it and is said | 3 | mesh-controller, mesh-catalog (mesh-delivery, the backup seat's holder) | 980, split into 1065, 1066, 1067, 1068 |
| 3 fix | A group approved together merges in one pass and goes out in one delivery plan, also when its members' tests read each other's repositories | 1 | mesh-catalog (mesh-delivery, gitea) | 1082, 1049, 1047, 1083, 1079 (1084 closed as its duplicate) |
| 5 | One read of a module on a node names the commit, build and rollout that put it there | 10, 12 | mesh-controller, mesh-catalog (mesh-config, nats) | 981 |
| 5b | The controller makes no build or deploy plan: building and delivering a change are done outside it, driven by events on the bus, and the controller receives only what each node should run | — (its decision names them) | mesh-controller, mesh-catalog (mesh-delivery) | 1117 |
| 6 | Builds use installed toolchains with no container, builders fetch over SSH with their own key, and an SDK change ships in one group with its users | 2, 4 | mesh-catalog (build modules, build-agent, ssh-client, gitea), mesh-controller, mesh-sdk | 979, 971 |
The issues are linked `depends-on` in this order; 976 depends on 975, and 979 also on 971. Milestone 3's first live group (milestone 4's, on 2026-10-17) held one Approve but merged in two passes and went out in two delivery plans: a member's tests read the other member's repository, so the first merge made the second's verdict out of date, and a closed group never asked its composed verdict again (issues 1049, 1082). The operator put a fix of milestone 3 after milestone 4 and before milestone 5 on 2026-10-18; acceptance test 1 is proved again by it. The operator put milestone 5b after milestone 5 and before milestone 6 on 2026-10-18, as an issue rather than a research effort; where the planning goes is settled by a decision record before it is built (issue 1117), and milestone 6's build changes are read against it. Milestone 2b was
put before milestone 3 by the operator on 2026-10-17. Milestone 3 builds ADR 0418, ADR 0419 and ADR 0421
(issues 977, 978 and 1022), and splits mesh-controller's unit tests from its integration tests (issue 895, the
operator on 2026-10-17), so the unit level holds its bound of ADR 0391; the integration level's sixty seconds
is issue 1055. Acceptance test 6 spoke of a ready mark when milestone 2 was delivered; restated for
the Approve, it is proved again by milestone 3.
| Phase | Delivers | Done when |
|---|---|---|
| A — the owner | mesh-delivery with its table, state, verbs, events and adoption; the controller's seat, verbs, `plan-moved` and the wait; the forge holder's view, notes and statuses; the group's order and composed check | the tests of ADR 0239's *How it is checked* pass; a merge on the live mesh with mesh-delivery held is delivered by it and shown by `deliveries` |
| B — the conditions | S16, the controller's own `plan.<walk>.waiting`; probe D14 reads `stalled` and raises `delivery.<id>.stalled`; H2 calling `close` — **built 2026-10-07**, on a branch | a delivery stalled on purpose is raised and closed by H2 through `close`; a rollout never let go is raised by S16 |
| C — the rollout itself | the per-tier rollout moved into mesh-delivery behind single-send verbs, the controller's planner kept only for the built-in path | decided by its own record once Phase A has delivered for a while |
## What is not decided here
- A web view of deliveries beyond the pull request's comment.
- Whether `mesh/delivery-group` is required on the trunks: the operator's setting, through the forge module's
protection tool.
- How a builder's public SSH key reaches the forge: the forge's and the SSH client's mechanism.
- Whether `mesh/delivery-group` is required on the trunks. That is the operator's setting, through the forge
module's protection tool.
- Moving the per-tier rollout out of the controller (Phase C).
@@ -1,8 +1,8 @@
---
layer: to-be
status: in-progress
code: [mesh-controller]
updated: 2026-10-17
status: designed
code: []
updated: 2026-10-10
decisions:
- "ADR 0289"
- "ADR 0201"
@@ -12,8 +12,6 @@ decisions:
# 54 — The controller's records on the bus
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: mesh-controller's record mirrors (internal/recordmirror, cmd/mesh-controller/record_mirrors.go, internal/inventory's record mirrors), the mechanism specified here. Not yet checked mirror by mirror; not broken down into milestones.
**What each node runs, how far each rollout has got, what each build is doing and what each setting holds
can be watched on the bus, and what is watched never says something the controller's store does not**
(ADR 0289,
+10 -118
View File
@@ -1,19 +1,15 @@
---
layer: to-be
status: in-progress
code: [mesh-catalog, hq]
updated: 2026-10-18
status: designed
code: []
updated: 2026-10-12
decisions:
- "ADR 0290"
- "ADR 0321"
- "ADR 0423"
- "ADR 0424"
---
# 55 — The knowledge base
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built and live since 2026-10-11: the knowledge base is the module mesh-knowledge in mesh-catalog, holding every decision record and research effort (issues 425 and 426, resolved and delivered). Left: the later slices, issues 428 and 429.
**The mesh keeps its decision records and research efforts whole in a module of its own, `mesh-knowledge`, one
instance on the control-node, holding the seat `knowledge-base`: each record's number, status, topic, typed links,
its clauses with their own status, and its reasoning, with every version kept. A record is proposed, checked when it
@@ -30,8 +26,7 @@ gets, in one call under 4 KiB, the clauses in force, the records they come from,
read the reasoning behind each.
This document designs the first slice in detail, tracked by issue 425. The slices after it are in
[§ 9](#9-the-slices-after-the-first), which also designs the third: designs and the glossary's entries become records,
and the knowledge base works out each design's state (ADR 0423, ADR 0424).
[§ 9](#9-the-slices-after-the-first).
## 1. The parts
@@ -44,12 +39,9 @@ and the knowledge base works out each design's state (ADR 0423, ADR 0424).
├ verbs ask, get, holds, links, history, reserve, propose, import, return
│ served by the control-node's tool runner
├ events proposed, reviewed, accepted, changed, imported, drafted, confirmed, returned
├ consumes the router's warrants: the operator's Approve for a record that needs it (§ 5)
└ consumes the delivery module's delivery-done announcement: a design's state (§ 9)
└ consumes the router's warrants: the operator's Approve for a record that needs it (§ 5)
mesh-issues (control-node) the counters for decision and research numbers (§ 6);
the issues' design links, read for a design's state (§ 9)
mesh-delivery (its seat) announces each delivery done, on the bus (§ 9)
mesh-issues (control-node) the counters for decision and research numbers (§ 6)
claude-code (same node) claude_code_judge: review a proposal (§ 5), draft and confirm clauses (§ 7)
records (any node) records_read at a commit: the import's source (§ 8)
messenger (control-node) the router: the operator's ask and its warrant
@@ -63,11 +55,9 @@ the records module, as the issue tracker's import does.
## 2. What one record holds
- `number` and `kind` (decision or research; with the third slice also design and glossary entry, § 9), its key
together; `title`, `topic`, `status`, `date`, `deciders`;
- `number` and `kind` (decision or research), its key together; `title`, `topic`, `status`, `date`, `deciders`;
- its **reasoning**, in sections: for a decision, context, the options weighed, consequences, how it is checked,
references; for a research effort, its overview and each of its documents by name; for a design, its `to-be`
section and, where something of it runs, its `as-is` section (§ 9); markdown, as written;
references; for a research effort, its overview and each of its documents by name; markdown, as written;
- for a decision, its **clauses** (§ 3);
- its **links**, typed, each stored once and read from both ends;
- its **versions**: every change is a new version with who made it, when, and why in one line; `history` lists them
@@ -200,89 +190,11 @@ field by field and section by section, and the result is written on issue 426.
|---|---|---|---|
| 1 | this document: the module, the import, clauses, answers, `propose`, its own review and acceptance | the acceptance sentence above, live, and a record proposed, reviewed and accepted without a pull request | issue 425 |
| 2 | the decision records and research efforts leave this repository; path mentions become "ADR NNNN" and "research NNN"; playbooks 01 and 02 changed | this repository holds no decision record, and `get` answers every one | issue 426 |
| 3 | designs and the glossary's entries as records, moved in one delivery, and each design's state worked out by the knowledge base (§ 9.1 to § 9.3; ADR 0423, ADR 0424) | `get` answers design 47 with its sections and links, a word's entry answers from the knowledge base, and a design's state follows its issues' deliveries without anyone writing it | issue 428 |
| 3 | designs, playbooks and glossary entries as kinds | `holds` answers for a design's decisions and a glossary word | issue 428 |
| 4 | meeting notes, drawings and blog posts | not decided; needs its own decision | issue 429 |
Numbers from the issue tracker, which `reserve` needs, are the tracker's own slice, tracked by issue 424. This
repository keeps no copy of the records, generated or otherwise (issue 435, answered by the operator). Playbooks
stay in this repository as files: they are how work is done here, not records of what was decided (research 061).
### 9.1 Designs and glossary entries as records
**A design** is a record of the kind `design`, named "design NN" and keeping its number (ADR 0423). Its text is its
sections: the `to-be` section, what is being built, and the `as-is` section, what runs. Each of the as-is documents
becomes the as-is section of the design it describes, never a record of its own: the import matches 3 of the 18 by
name and 15 by topic, and shows the operator the list before it runs. Its owning repositories are its field `code`;
its decisions, and every design it names, are `cites` links. It holds no state written by anyone (§ 9.2). A new
design takes its number from `reserve` and is written with `propose`, and is reviewed as every record is (§ 5).
**A glossary entry** is a record of the kind `glossary entry`, one per entry of the glossary page, kept whole: its
head word or words (an entry may name several with one meaning, as research effort, graduation, hand-off and playbook
do), its domain, its meaning, its *Not:* line with each retired word's scope, and its identifier line. Each row of
the page's homonym table becomes an entry of its own, naming the qualified form each domain uses. The entries replace
the page as the authority on words (ADR 0423, changing ADR 0244).
**The readers change with them.** Everything that read the page or the design folders reads the records instead:
| Reader | Reads, after the move |
|---|---|
| the knowledge base's own rule on a retired word (§ 4) | the *Not:* and identifier lines of the entries, no longer the page through the records module; it judges a design when it is proposed, as it judges a decision |
| hq's word check | the entries' *Not:* and identifier lines, and its uniqueness check (a head word heading two entries, or also retired) over the entries; it covers what stays in this repository, and designs leave its coverage |
| the catalogue's repository check | the retired words of no scope or *(tools)* from the entries; its copied list is retired, so hq has no copy to compare |
| hq's cycle check | the design records, for every design naming its decisions, not files under `03-DESIGN/` |
Each of the four fails, saying so, when the knowledge base does not answer, and never passes on nothing.
**Links become links between records, and the old paths go.** At the import every file link inside a design becomes
a link between records, which the rule on a link to nothing then checks (§ 4). The links from documents that stay in
this repository are rewritten to name the design ("design 47"). No page stays at an old path: afterwards this
repository holds no file under `03-DESIGN/` and no `00-META/glossary.md`.
**It moves in one delivery**, as the records did: the import, the two kinds, the four readers, the rewritten links,
the playbooks' steps that write a design, and the instruction text sessions read, which names the glossary's place.
A reader left behind would pass on nothing, so none moves alone.
### 9.2 A design's state
**The knowledge base says a design's state, and nobody writes it** (ADR 0424): proposed, designed, in progress or
implemented, kept matching what runs. No person, session or document sets it, and `propose` refuses a design that
carries one.
It reads two things. The issues are those the issue tracker holds with a `design` link to the design. Whether one
is done comes from its deliveries: an issue is done when it is resolved and the delivery its fix went out in reached
`delivered`, whether a hotfix alone or a delivery group. The delivery module announces each delivery done on the
bus, and on each announcement the knowledge base works the state of every design that issue links out again.
```
issue tracker ── issues with a design link ──┐
├──▶ knowledge base ── works out ──▶ the design's state
mesh-delivery ── "delivery done", bus ───────┘ (served by get, ask)
```
**The rules.**
| Case | State |
|---|---|
| a decision the design names is not accepted | proposed |
| no linked issue done, or no linked issue at all | designed |
| some linked issues done | in progress |
| every linked issue done | implemented |
Three kinds of issue count apart: an issue closed won't fix is left out; an issue resolved with nothing to deliver,
a fix of documents only, is done when resolved; and a new issue linked to an implemented design makes it in progress
again, until that issue is done. Milestones are not read: a milestone is a group of a design's issues delivered
together, and the state depends only on the issues and their deliveries.
**Imported designs say what they lack.** A design imported with no linked issue shows `no issues linked yet` beside
designed, until an issue is linked to it; the import names each such design by number. Linking their issues is
tracked by issue 428.
### 9.3 Its place beside design 47
The delivery-done announcement this slice reads is design 47's
([47 — Delivery](47-delivery-from-commit-to-delivered.md)): its hotfixes and delivery groups, and each delivery's
end at `delivered`. This slice is an extension after design 47's last milestone. It changes none of design 47's
milestones and nothing of how a delivery runs; it only listens for the announcement.
repository keeps no copy of the records, generated or otherwise (issue 435, answered by the operator).
## 10. How it is checked
@@ -300,26 +212,6 @@ milestones and nothing of how a delivery runs; it only listens for the announcem
| A record written in this repository meanwhile writes its clauses | `records.py`, check `clauses`, on every hq pull request, until the records left this repository (issue 426); since then `records.py`, check `no-records`, refuses any record written here |
| Live | the acceptance sentence, asked from a session outside this repository |
**The third slice's acceptance tests** (§ 9.1 to § 9.3; research 062's direction), what its build proves before it
is done:
1. After the import, `knowledge-base.get` of design 47 returns its sections and its links, and every link from a
design resolves to a record: none is refused by the rule on a link to nothing.
2. This repository's main holds no file under `03-DESIGN/` and no `00-META/glossary.md`, and its link check passes
with the 76 links from documents that stay here rewritten.
3. hq's cycle check and word check read the knowledge base, and fail, saying so, when it does not answer.
4. A proposal holding a retired word is refused after the move, the word read from the entries; the entries are
175 and hold 183 head words and 29 retired words, 0 unparsed, both counts checked.
5. In the module's tests, each state rule of § 9.2 gives its state from fixture issues; live, an issue linked to a
design whose hotfix is delivered turns the design implemented within a minute of the delivery being done, and
a new issue linked to it turns it in progress.
6. The import reports every design with no linked issue as `no issues linked yet`, by number.
7. The instruction text sessions read names the glossary's new place.
8. Every entry of the glossary's Config section is filed under the Config domain, and the domains the entries name
are the twelve of ADR 0354.
9. Tests 1 to 8 pass together after the one delivery, run against main and the live knowledge base, with nothing
in this repository, the catalogue or the instruction text pointing at a removed path.
## What it leaves out
Semantic or vector search; a browser view; line comments on a proposal (the judgement's findings name the section and
@@ -1,8 +1,8 @@
---
layer: to-be
status: in-progress
code: [mesh-catalog, mesh-controller]
updated: 2026-10-17
status: designed
code: []
updated: 2026-10-13
decisions:
- "ADR 0316"
- "ADR 0317"
@@ -12,8 +12,6 @@ decisions:
# 56 — The mail client, set up by the mesh
**Where it stands, 2026-10-17** (the records cleanup, issue 1072; playbook 07, "The design's state is written back"). Built on main: issue 586, this design's core, is resolved and delivered (hq#336, mesh-catalog#262, mesh-controller#286), and mesh-catalog's betterbird module has every piece named here. Not yet proven live end to end, nor given an as-is: issue 1076.
**On every node that holds a display server, the `betterbird` module installs Betterbird from a pinned
upstream release, ships the Owl add-on with it through Betterbird's enterprise policy, and declares the
operator's mail accounts without their credentials. A password or token never passes through the mesh: each
@@ -1,8 +1,8 @@
---
layer: to-be
status: in-progress
status: designed
code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog]
updated: 2026-10-17
updated: 2026-10-14
decisions:
- "ADR 0348"
- "ADR 0349"
@@ -61,10 +61,6 @@ both, by the form of its manifest, and the old forms stay accepted until the las
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
@@ -337,17 +333,6 @@ removed code.
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
@@ -2,7 +2,7 @@
layer: to-be
status: designed
code: [mesh-catalog, mesh-media-catalog, mesh-sdk, mesh-tools, mesh-controller, mesh-host, mesh-lab]
updated: 2026-10-17
updated: 2026-10-16
decisions:
- "ADR 0383"
- "ADR 0384"
@@ -12,10 +12,6 @@ decisions:
- "ADR 0388"
- "ADR 0389"
- "ADR 0390"
- "ADR 0411"
- "ADR 0412"
- "ADR 0413"
- "ADR 0414"
---
# 58 — Agent work as the mesh's own
@@ -25,10 +21,8 @@ bus by a thread seat; a thread handler on an always-on node runs the agent harne
of its own, with the thread's own branch on the forge as its workspace; the agent works through a workspace toolkit
and named tools, never a general command runner. Every person, agent and module is an identity held by one identity
seat, with a role made of grants; the bus checks each login by its signature, and the tool runner checks each call
against the role, tool by tool, by the effect every tool carries. An identity is also its logins: the mesh makes each
person's and agent's machine account on every machine it is present on, gives each its own bus password, and runs a
module for an identity only where the module is set to; everything else runs as the operator.**
(ADR 0383 to ADR 0390, from research 053; ADR 0411 to ADR 0414, from research 057.)
against the role, tool by tool, by the effect every tool carries.**
(ADR 0383 to ADR 0390, from research 053.)
**Acceptance.** The operator starts a thread from the terminal client, an agent on an always-on node works it in a
sandbox, asks a question the operator answers in place, and the thread moves to another node and carries on with
@@ -112,95 +106,6 @@ agent carries a token the identity seat signed for that agent, and the tool runn
refusal names the missing grant; every call is recorded with its identity and thread. A session on an operator's
machine calls as the operator's person identity or as an agent, never through the machine's tool-runner login.
### 5.1 An identity on the machines (ADR 0411, ADR 0414)
A person or agent identity also holds a **login name**, the **machines** it is present on and, for a person, the
login name kept on a machine where it differs. The seat starts with two: the operator's person identity and `nox`, the one
agent; every piece of agent work is a thread of `nox` (§2), and a second agent identity is made only when a rule needs
another role. The agent is `nox` everywhere: its login, its forge account (the forge account `nox-agent` renamed,
keys and team kept), Keycloak, mail and the bus. A login or service account the mesh makes for the operator from now on carries the operator's identity name;
the operator's existing logins and forge account are kept as they are. On a machine where agents ran as the
operator's login, that login stays the operator's and `nox` gets a login of its own; nox's bus account is `agent.nox`. `nox` is also the
operator's terminal command (`mesh-cli`), and prose says which is meant.
An identity's login on one machine is its **machine account**. The controller composes the machine account of every
identity present on a machine into that machine's declaration, and the node-engine applies it with its `user`
resource. A login that exists is adopted under its name, uid and home; the mesh never renames or deletes one. An
agent's machine account has its own home and group, no password, no root group and no sudo rule, and the node-engine
judges on every look that it cannot become root without a person, by the ways ADR 0266 §12 to §17 list. No module declares a login for a
person or an agent: claude-code's `agent-account` resource goes, and the catalogue's check refuses a manifest that
declares one. The node record's operator account and agent account are read from the identities, no longer stated
at the controller's terminal or in the mesh-config snapshot; the machine facts `account`, `agent-account` and their
homes keep their names. A machine names an agent account only where the identity seat marks an agent as its agent
account: the control node, marked `nox`, where the guard and the licence stay as they are. The other machines are
marked with none, so the guard stays off there and the operator's sessions keep the mesh's tools, while the login `nox` serves the
modules run for `nox`. Where a person or an agent is present, its kept logins and the agent account mark change only
through a verb of the identity seat that only the operator calls, on an Approve; an assignment never sets the mark,
because an agent may call the verb that changes an assignment (ADR 0266 §4). The verb, and the controller at composition,
refuse an agent whose login on a machine would be `root`, a person's login there or a well-known service account
(ADR 0266 §5). The self-check judges every agent login,
marked or not. The seed: the operator on every machine, with the operator's existing login kept where it differs from the
identity's name; `nox` on every machine, the agent account on the control node.
```
identity seat controller each machine (node-engine)
───────────── ────────── ──────────────────────────
person operator, machines, composes, per machine: the operator's login (adopted)
the login kept on each the machine accounts present nox, root: never (adopted on the control node,
agent nox, its machines the facts account, agent-account made elsewhere)
each identity's bus password ──► its secret, owned by its login, 0600
```
### 5.2 Run for an identity (ADR 0412)
Every module runs as the operator account of its machine, as today, with no setting; running for the operator is that
default run. A manifest may declare that its module can **run for** an agent identity, at assignment, at start or both: serve one identity with that identity's machine account, home and bus password.
An assignment of such a module may name the identity it runs for, and the module may let the one who starts it
choose: claude-code started for the operator is the default run with the operator's configuration, and started for `nox`
it runs for `nox` with the agent's home. A start
goes down only: a module is started for an identity by a process already running as that identity's login, or by the
operator's own login for an agent; an agent never starts one for a person, and the module refuses it, naming who asked
and for whom. A module may be assigned to one machine once per identity it runs for there, each its own process under
its own login, and the controller keys an assignment by machine, module and identity. The first two to declare it are
claude-code and ssh-client. On a machine the identity seat marks with an agent account, claude-code's assignment is
run for that agent (`nox` on the control node), so its licence and home scope stay in the agent's home, and the controller refuses
one there that is not. ssh-client keeps its default assignment for
the operator wherever `nox` is present and gains a second one run for `nox`, which makes the agent's forge key in the
agent's home and closes issue 989; gitea's provider registers that key on the forge account `nox`. A module run for an identity calls as that
identity and gets no module identity of its own (this narrows ADR 0386 §4, which holds for every other assignment). A module with an account of its
own (`runs-as`, ADR 0259 §8) cannot declare it, and a run for an identity with no machine account on that machine is
refused at composition and at start. ADR 0266's guard
stands: on the control node, a session started for the operator gets the operator's configuration and its tool calls are still
refused.
### 5.3 A bus password per identity (ADR 0413)
Each person and agent identity has a bus account of its own (`person.<name>` for the operator, `agent.nox`), whose memberships are made
from its role, and its password is a secret owned by its machine account at mode 0600 on each machine it is present
on, which no other account but root can read. A person's password is placed only on a machine that names an agent
account of its own judged unable to become root, by a verdict at most fifteen minutes old; on the other machines, where agents can still run as the operator,
it is not placed, and the controller says so. A module run for an identity reaches the bus with that password, can do
only what the identity's role allows, and every call is recorded as that identity's. Nox's role never holds an approval
grant, a trusted party's grant or a dangerous tool named in it (ADR 0387 §4 does not apply to it), and the identity seat
refuses one; each dangerous act of nox's is asked one by one. The break-glass agent (§6) keeps its own rules and gets
no login and no bus password from this. This changes ADR 0259 §8, which kept every bus credential out of an agent's account; the rest of §8 stands.
The password lives in today's list of bus accounts and is replaced by a signed login for the same role at the bus
switch (§7, §8).
### 5.4 Traced to GENESIS
Checked on 2026-10-17 (playbook 02, step 1); no conflict was found.
- [Mission](../../00-META/mission.md): agents "hold identity", and there is one kind of participant, some of them
human; persons and agents are held alike, with the same fields, and differ in what their role allows, which the
mission calls modality. "The mesh brokers credentials": the bus password per identity and the service accounts made
from it. "The mesh owns the truth": logins are derived from identities, no longer stated per node. "Dogfood": logins
go through the node-engine's existing resource.
- [Context](../../00-META/context.md): "Identity is mesh-internal", with no directory and no SSO, so the identity seat
holds it and Keycloak only receives accounts; "Nodes are personal" and the mesh is a guest, so existing logins are
adopted, never renamed; human agents are few, often one, so the seat holds two identities and one agent.
- [Effect](../../00-META/effect.md): a human agent's environment is part of the mesh, its login included.
## 6. Effects, asks and break-glass (ADR 0387)
Every seat verb and module tool is one permission and carries one **effect**, set in its module's manifest:
@@ -251,36 +156,12 @@ carry them:
`research/053-signed-bus-logins`, loses nothing and leaves every identity on the new bus.
5. The acceptance sentence at the top, end to end.
From research 057 (ADR 0411 to ADR 0414):
6. The identity seat lists the operator and `nox` with their machines, kept logins and the control node's agent account mark; a
create or a presence change by any caller but the operator's Approve is refused.
7. On every other machine a login `nox` exists; on the control node the login `nox` is adopted with its uid and home unchanged;
on every machine the node-engine judges `nox` unable to become root; a login `nox` given a sudo rule in a test is left
unaltered and the self-check raises `agent-can-become-root` for it, on a machine that marks no agent account too;
the operator's existing logins are
unchanged.
8. No manifest in the catalogue declares a login for a person or an agent; the catalogue's check refuses one.
9. For every machine, the declaration before and after differs, for the modules not set to run for an identity, only
in the machine accounts' `user` resources (now the controller's, and `nox` added on the other machines); claude-code on the control node, run for `nox`, places the same
files in the same home; on the other machines the guard stays off and the operator's sessions keep the mesh's tools.
10. A session started for `nox` on the control node gets the agent's home configuration and one started for the operator the
operator's, with no tool call through; a session as `nox` that starts claude-code for the operator is refused; with
ssh-client run for `nox`, nox's key is on the forge account `nox` and the session pushes a branch over SSH as `nox`.
11. Nox's bus password is owned by `nox` at mode 0600 on every machine nox is present on; a call with it outside
nox's role is refused; a grant test finds no approval grant, trusted party's grant or dangerous tool in nox's
role; the operator's password is placed only on a machine with an agent account judged root-free within fifteen
minutes, and the controller says so for every other.
12. The forge account `nox-agent` is renamed `nox`, keeps every machine's key, stays in the agents' write team, and
every `forge-account` entry names `nox`.
## 11. Milestones
Each milestone is one feature step (playbook 07), usable as soon as its delivery plan is done, and starts when
the previous one's is. What it replaces is in the as-is documents
[15 — The agent and its licences](../00-as-is/15-the-agent-and-its-licences.md) and
[16 — Logins on the bus](../00-as-is/16-logins-on-the-bus.md), and for milestones 11 to 14
[17 — Accounts on a machine](../00-as-is/17-accounts-on-a-machine.md). The issues are opened once the operator confirms
[16 — Logins on the bus](../00-as-is/16-logins-on-the-bus.md). The issues are opened once the operator confirms
the milestones, and their numbers written here.
**Confirmed by the operator on 2026-10-16.** One issue per milestone: 1 is issue 878, 2 is 879, 3 is 880, 4 is
@@ -305,18 +186,3 @@ and the issue tracker takes only registered owners. The building waits until the
The front ends' later parts, the inference seat and the other agent harnesses are tracked by issue 850; the
seat-sense "role" in older designs by issue 872.
**Milestones 11 to 14, from research 057, confirmed by the operator on 2026-10-17.** One issue per repository
part, opened at the confirmation, so a fixing agent per repository can start when its step does. Milestone 11
waits for nothing; 12 waits for milestone 6.
| # | Outcome, in one sentence | Proves | Repositories and their parts (issues) | Depends on |
|---|---|---|---|---|
| 11 | The agent's forge account is `nox`, with its keys and its place in the agents' write team kept | test 12 | mesh-catalog gitea: the rename, keys registered on `nox`, every `forge-account` entry (993) | — |
| 12 | The mesh makes the operator's logins and nox's on every machine from the identities, and judges nox unable to become root everywhere | tests 6 to 9 | identity module: logins, machines, kept logins, the agent account mark, the operator's verb (994); the node-engine (`mesh-host`): existing logins adopted, every agent login judged (995); mesh-controller: machine accounts composed, node accounts read from the identities, the terminal lines retired (996); mesh-catalog: claude-code declares no login, the catalogue's check refuses one (997) | 6 |
| 13 | The operator and nox each reach the bus with their own password, held to their role; the operator's only where no agent runs as the operator | test 11 | mesh-controller: the operator's and nox's bus accounts, secrets at 0600, the fifteen-minute verdict (999); identity module: nox's role refuses approvals and dangerous tools (998); until milestone 7 builds the asks, a dangerous act of nox's is refused, not asked | 12 |
| 14 | The operator starts a claude-code session for nox or for themself, and nox pushes over SSH with its own key | test 10 | mesh-controller: run for, assignments keyed by identity, the refusals (1001); mesh-sdk: calls as the identity, a start goes down only (1002); claude-code: started for nox or for the operator (1003); ssh-client and gitea: nox's own key, closing issue 989 (1004) | 11, 13 |
The agent's and the operator's accounts in Keycloak and mail (ADR 0414 §2 and §3) follow milestone 13 and are
tracked by issue 1000, which needs a design section first. A second agent identity is made only when a rule
needs another role (ADR 0414 §1); no milestone makes one.
+1 -1
View File
@@ -10,7 +10,7 @@ Before changing anything here, read the playbooks in
amendment, issues, build handoff, constitution sync) is documented there, and agents operate
through them. Thin skills in `.claude/skills/` wrap these playbooks for invocation
(`hq-new-research`, `hq-graduate`, `hq-new-issue`, `hq-diagnose`, `hq-amend-design`,
`hq-handoff`, `hq-sync-constitution`, `hq-status`, `hq-cleanup`); each defers to its playbook as
`hq-handoff`, `hq-sync-constitution`, `hq-status`); each defers to its playbook as
authoritative and adds only the mechanical scaffolding.
## The development cycle