Compare commits
78
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0944437d4e | ||
|
|
2e6a989031 | ||
|
|
ec373556d0 | ||
|
|
f242b8763b | ||
|
|
bb6af05f5c | ||
|
|
672011ab92 | ||
|
|
05ff547358 | ||
|
|
1def984c07 | ||
|
|
76a671b8c5 | ||
|
|
19bd7fd960 | ||
|
|
f82a90dfa0 | ||
|
|
17c27ddf83 | ||
|
|
5c6b645c6b | ||
|
|
b6cf49d8ce | ||
|
|
1484ff7682 | ||
|
|
c10efa4d8c | ||
|
|
24fd567c31 | ||
|
|
be5284f7b8 | ||
|
|
21c4d212ec | ||
|
|
1bd6f4611e | ||
|
|
9d59c2ce14 | ||
|
|
64e357da7d | ||
|
|
9a9a78f882 | ||
|
|
e816b61cb7 | ||
|
|
610be93803 | ||
|
|
d5ddd1e715 | ||
|
|
a6db6a70cf | ||
|
|
e44ec3f612 | ||
|
|
dbc7406490 | ||
|
|
2001d6e063 | ||
|
|
90c6793096 | ||
|
|
499407c669 | ||
|
|
2cf1c64e24 | ||
|
|
e1987b3654 | ||
|
|
c87ab9af0f | ||
|
|
4e0dd1b0d3 | ||
|
|
b5d3f23947 | ||
|
|
c753ff068a | ||
|
|
7a1d3d89f0 | ||
|
|
cb79a28a80 | ||
|
|
0b9fda46cc | ||
|
|
6b58a6af82 | ||
|
|
9a39785116 | ||
|
|
c0b1c47be6 | ||
|
|
06440c1522 | ||
|
|
0e7eb9c2da | ||
|
|
cf73f96797 | ||
|
|
b0cfd08f7c | ||
|
|
0a6613f0d4 | ||
|
|
aaa0647ec3 | ||
|
|
de32fb82ea | ||
|
|
85a729c607 | ||
|
|
fb284fa6ee | ||
|
|
2b04a5f0a1 | ||
|
|
addbd74c39 | ||
|
|
6bf18062c4 | ||
|
|
0f63a73def | ||
|
|
d0ee14712e | ||
|
|
f6199fafce | ||
|
|
d901156614 | ||
|
|
e934a0286f | ||
|
|
8dbfd8d8ff | ||
|
|
2cea6c9b0c | ||
|
|
79dcc0a6ec | ||
|
|
65f69d96a9 | ||
|
|
b83a1e748e | ||
|
|
84b464d109 | ||
|
|
88d7db36c8 | ||
|
|
974b3e7d42 | ||
|
|
c61f01d63e | ||
|
|
4325467b4d | ||
|
|
0a010b8b79 | ||
|
|
cc0503ef66 | ||
|
|
56d8d546c7 | ||
|
|
edff1f25a1 | ||
|
|
85f0aecf5a | ||
|
|
0dc8cab178 | ||
|
|
98b731d5d1 |
+40
-21
@@ -98,6 +98,9 @@ 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).
|
||||
@@ -231,7 +234,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.
|
||||
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261, 0411.
|
||||
**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,
|
||||
@@ -264,15 +267,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 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).
|
||||
- **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).
|
||||
Not a name a manifest carries.
|
||||
- **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).
|
||||
- **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).
|
||||
- **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
|
||||
@@ -341,7 +344,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.
|
||||
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414.
|
||||
**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**
|
||||
@@ -355,8 +358,13 @@ 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. 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).
|
||||
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).
|
||||
- **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
|
||||
@@ -410,7 +418,7 @@ planner, the rollout). **Downstream of** Module, Core, Health and repair and Dat
|
||||
(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 marked ready, then every other check; each check queue keeps one
|
||||
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**.
|
||||
The queue's verbs still say *ask* for an entry; the conversation's ask is another thing
|
||||
@@ -618,8 +626,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*; 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
|
||||
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
|
||||
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,
|
||||
@@ -724,7 +732,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.
|
||||
0284, 0290, 0291, 0423, 0424.
|
||||
|
||||
- **research effort**, **graduation**, **hand-off**, **playbook** — an investigation, held whole in the
|
||||
knowledge base and named "research NNN";
|
||||
@@ -738,14 +746,23 @@ 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), takes them as proposals, has them reviewed and accepted,
|
||||
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,
|
||||
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 document in `03-DESIGN/`: **as-is** (what runs) or **to-be** (what is being built).
|
||||
- **milestone** — one step of a design's work breakdown, usable on its own once delivered and named by what the
|
||||
operator or the mesh can then do; delivered as one feature (playbook 04, ADR 0401).
|
||||
- **epic** — a run of milestones in one design, released milestone by milestone (ADR 0401).
|
||||
- **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)
|
||||
- **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.
|
||||
@@ -817,6 +834,8 @@ 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 |
|
||||
|
||||
@@ -12,8 +12,12 @@ 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 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.
|
||||
- **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.
|
||||
- **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`.
|
||||
|
||||
@@ -40,8 +44,10 @@ 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 the feature step; its issues are the
|
||||
step's issues; the next milestone starts when the previous one's delivery plan is done.
|
||||
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.
|
||||
9. **On completion of the last milestone**, run the "when something ships" section of playbook
|
||||
[02](02-graduation.md).
|
||||
|
||||
@@ -52,7 +58,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 ready mark and the delivery — is playbook
|
||||
- How the building is done — worktrees, agents, review, checks, the Approve and the delivery — is playbook
|
||||
07's, not this one's.
|
||||
|
||||
## How it is checked
|
||||
|
||||
@@ -28,45 +28,60 @@ 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).
|
||||
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.
|
||||
- **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 phases, and its time is bounded:
|
||||
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:
|
||||
|
||||
| Phase | What happens | Bound |
|
||||
| Step | 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, unit tests only | 1 to 2 hours |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
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 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.
|
||||
|
||||
**Scope is fixed at phase 0.** An issue found during a feature goes to the next step or the next feature,
|
||||
**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,
|
||||
never into the running one, unless it blocks the running one.
|
||||
|
||||
## Phase 0 — Prepare (the lead, larger model)
|
||||
## Step 0 — Prepare (the lead, larger model)
|
||||
|
||||
1. **Name the feature once:** `feat/<slug>`, the same branch name in every repository it touches. The
|
||||
branch is never protected.
|
||||
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`).
|
||||
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.
|
||||
`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.
|
||||
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), the test to write first, done-when in one sentence;
|
||||
with (quoted, with where each was decided), and its acceptance criteria (item 5), each with the test that
|
||||
proves it;
|
||||
- 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;
|
||||
@@ -75,25 +90,55 @@ 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. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
|
||||
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.
|
||||
|
||||
## Phase 1 — Develop (one fixing agent per repository, smaller model)
|
||||
## Step 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, 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
|
||||
- **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
|
||||
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. Report back in under 150 words: commits, tests added and that they failed first, the
|
||||
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
|
||||
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.
|
||||
@@ -105,53 +150,81 @@ 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`.
|
||||
|
||||
## Phase 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)
|
||||
## Step 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. Its report is per repository: each finding as file:line, the failure it causes, and the fix.
|
||||
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.
|
||||
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. **The reviewer reads the answers and the new commits**, and approves or reports again (step 5 says what a round
|
||||
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
|
||||
may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
|
||||
5. **A finding outside the issues' done-when is never a change asked, in any round.** More hardening, a
|
||||
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
|
||||
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 and its decisions.
|
||||
its issues, their acceptance criteria and its decisions.
|
||||
|
||||
## Phase 3 — Check (no review)
|
||||
## Step 3 — Check (no review)
|
||||
|
||||
1. **Push every feature branch and open one pull request per repository, together.**
|
||||
The integration tests
|
||||
run only in the merge check (the operator, 2026-10-16), never before. Pull requests in two or
|
||||
The integration tests ran once before the push (step 1); the merge check runs them again as the gate. 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.
|
||||
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.
|
||||
2. **The forge's merge checks run the integration tests once** (`mesh/merge-gate`, `mesh/repo-check`). No
|
||||
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
|
||||
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
|
||||
carries an integration test that starts the module whole, its process against the real bus and store, and
|
||||
shows the step's acceptance sentence; the forge's checks run it (ADR 0391 §1, §6). Unit tests alone are not
|
||||
shows the milestone'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 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
|
||||
and the operator approved the merge into main on Telegram, the channel that proves who answered today (ADR 0364,
|
||||
ADR 0398 §3; an Approve at the terminal is issue 953). Beyond that
|
||||
Approve, 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.
|
||||
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.
|
||||
|
||||
**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. Plan such a feature as two steps.
|
||||
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.
|
||||
|
||||
## Phase 4 — Deliver
|
||||
## Step 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
|
||||
@@ -163,10 +236,10 @@ survive.
|
||||
|
||||
| Who | Model | Why |
|
||||
|---|---|---|
|
||||
| The lead (phase 0 decisions, the context files, following and reporting) | larger | judgement |
|
||||
| The lead (step 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 (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 |
|
||||
| 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 |
|
||||
| Decision records | larger | judgement |
|
||||
|
||||
Every agent the mesh runs draws on one weekly allowance shared by every session, and the larger model
|
||||
@@ -183,57 +256,97 @@ 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 ready mark | 15 minutes after the acceptance is shown | the reviewer marks it, or the lead does |
|
||||
| a merge after ready | 5 minutes after the checks pass | read `mesh-delivery.show` for the delivery |
|
||||
| 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 |
|
||||
| a delivery plan | its class's budget (`mesh-delivery.times`), at most 30 minutes | read `mesh-controller.plans`, then `mesh-delivery.stalled` |
|
||||
|
||||
**Every five minutes, every two minutes while a delivery plan runs, and at once when something lands, stops or
|
||||
**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 (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), the issues as a tree: grouped by repository, each issue under the one it
|
||||
waits for, marked ✓ landed, ▶ fixing, ⏸ waiting, ✗ stopped:
|
||||
**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:
|
||||
|
||||
```
|
||||
Feature <slug> · step <n> of <m> · phase <name>, <minutes used> of <bound> min
|
||||
Feature <slug> · milestone <n> of <m> · step <name>, <minutes used> of <bound> min
|
||||
Issues: <landed> of <total> landed · <fixing> fixing · <waiting> waiting
|
||||
|
||||
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)
|
||||
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>
|
||||
|
||||
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 action, one line>
|
||||
Next: <the next actions up to the delivery, in order>
|
||||
```
|
||||
|
||||
**The short layout** (on a channel such as Telegram, or when nothing changed but time):
|
||||
|
||||
```
|
||||
<slug> step <n>/<m> · <phase> <used>/<bound> min · <landed>/<total> landed · past bound: <none | what> · needs you: <nothing | what> · week <n> %
|
||||
<slug> milestone <n>/<m> · <step> <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 step's issues included, also when only time passed: the operator reads the state from it without
|
||||
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.
|
||||
|
||||
## The context of the lead's own session
|
||||
|
||||
At the end of each phase, and whenever the session's context grows large or spread over unrelated work, the
|
||||
At the end of each step, 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 phase, its branches and heads, the context files' paths, the agents' ids, the wait list and the
|
||||
feature's step, 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 step that stops, for a wait past its bound, a
|
||||
An issue stays in `fixing` only while an agent works on it. A milestone 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.
|
||||
@@ -242,17 +355,46 @@ 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
|
||||
phase 1, is the failure this playbook prevents.
|
||||
step 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.
|
||||
- More than one open pull request in a repository that share no feature name, or a feature step whose issues
|
||||
- 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
|
||||
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, or a fourth round, is the failure of the
|
||||
- A review report that asks a change outside the issues' done-when and acceptance criteria, 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 reviewed and green but not marked ready for more than 15 minutes is a lead that did not
|
||||
mark it.
|
||||
- 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 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 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.
|
||||
|
||||
|
||||
@@ -27,27 +27,31 @@ one delivery**. Everything before the push is local and fast.
|
||||
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.
|
||||
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`.
|
||||
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`. 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,
|
||||
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,
|
||||
`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; 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
|
||||
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
|
||||
its id, so the fix keeps its context. A playbook or design edit of a few lines may skip the reviewer: the
|
||||
operator's word on the pull request is its review, and the lead marks it ready on that word.
|
||||
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 forge's merge
|
||||
check runs the integration tests, the only
|
||||
place they run (the operator, 2026-10-16). A failed check
|
||||
(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
|
||||
goes back to the same agent with the failure, and the reviewer reads only the change it makes.
|
||||
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
|
||||
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
|
||||
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.
|
||||
@@ -77,9 +81,9 @@ code.
|
||||
|---|---|---|
|
||||
| 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 merge after ready | 5 minutes after the checks pass | read `mesh-delivery.show` for the delivery |
|
||||
| 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 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.
|
||||
@@ -94,8 +98,13 @@ 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 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.
|
||||
- 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.
|
||||
- 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).
|
||||
|
||||
@@ -1,69 +1,144 @@
|
||||
---
|
||||
layer: as-is
|
||||
status: implemented
|
||||
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
|
||||
code: [mesh-tools, mesh-host, mesh-controller]
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0152"
|
||||
- "ADR 0095"
|
||||
- "ADR 0037"
|
||||
- "ADR 0175"
|
||||
- "ADR 0195"
|
||||
- "ADR 0197"
|
||||
- "ADR 0367"
|
||||
- "ADR 0154"
|
||||
---
|
||||
|
||||
# The mesh MCP server, as it runs
|
||||
|
||||
**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.
|
||||
**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`.
|
||||
|
||||
## What it answers
|
||||
|
||||
`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.
|
||||
`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`).
|
||||
|
||||
`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.
|
||||
**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.
|
||||
|
||||
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.
|
||||
**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.
|
||||
|
||||
## The mesh's own verbs
|
||||
|
||||
*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.
|
||||
**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.
|
||||
|
||||
## Around it
|
||||
## Where it runs
|
||||
|
||||
- **`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.
|
||||
**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.
|
||||
|
||||
## What shipped bent
|
||||
## What this repository's prior text got wrong
|
||||
|
||||
- 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.
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
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.
|
||||
@@ -25,6 +25,7 @@ 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
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: proposed
|
||||
status: in-progress
|
||||
code:
|
||||
- mesh-controller cmd/mesh-builder
|
||||
- mesh-controller internal/builder
|
||||
- mesh-catalog modules/build-agent
|
||||
updated: 2026-10-16
|
||||
- mesh-controller modules/build-agent
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0405"
|
||||
- "ADR 0262"
|
||||
@@ -31,6 +31,8 @@ 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.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: proposed
|
||||
status: in-progress
|
||||
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-06
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0224"
|
||||
- "ADR 0230"
|
||||
@@ -24,6 +24,8 @@ 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: proposed
|
||||
status: in-progress
|
||||
code:
|
||||
- mesh-host internal/bootstrap
|
||||
- mesh-host cmd/mesh-bootstrap
|
||||
- mesh-lab test/integration/one-node-mesh.test.ts
|
||||
updated: 2026-10-06
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0226"
|
||||
- "ADR 0067"
|
||||
@@ -16,6 +16,8 @@ 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.
|
||||
|
||||
@@ -6,7 +6,7 @@ code:
|
||||
- mesh-controller
|
||||
- mesh-catalog
|
||||
- mesh-lab
|
||||
updated: 2026-09-15
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0067"
|
||||
- "ADR 0074"
|
||||
@@ -17,6 +17,8 @@ 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.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: designed
|
||||
code: []
|
||||
updated: 2026-09-25
|
||||
status: in-progress
|
||||
code: [mesh-controller]
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0084"
|
||||
- "ADR 0027"
|
||||
@@ -11,6 +11,8 @@ 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-16
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0181"
|
||||
- "ADR 0182"
|
||||
@@ -16,6 +16,11 @@ decisions:
|
||||
- "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
|
||||
@@ -58,6 +63,15 @@ 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
|
||||
@@ -84,6 +98,9 @@ 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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: proposed
|
||||
code: []
|
||||
updated: 2026-10-06
|
||||
status: in-progress
|
||||
code: [mesh-controller]
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0238"
|
||||
- "ADR 0221"
|
||||
@@ -15,6 +15,8 @@ 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
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: designed
|
||||
code: [mesh-catalog, mesh-tools, mesh-controller]
|
||||
updated: 2026-10-03
|
||||
status: implemented
|
||||
code: [mesh-tools, mesh-host, mesh-controller]
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0197"
|
||||
- "ADR 0195"
|
||||
- "ADR 0175"
|
||||
- "ADR 0367"
|
||||
- "ADR 0160"
|
||||
- "ADR 0159"
|
||||
- "ADR 0152"
|
||||
@@ -163,11 +165,16 @@ matched it by URL.
|
||||
|
||||
## What this does not settle
|
||||
|
||||
- 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.
|
||||
- **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.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog modules/claude-code]
|
||||
updated: 2026-10-13
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0411"
|
||||
- "ADR 0412"
|
||||
- "ADR 0343"
|
||||
- "ADR 0274"
|
||||
- "ADR 0273"
|
||||
@@ -306,21 +308,27 @@ 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.*
|
||||
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.
|
||||
*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.
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
**What the module declares.**
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
- `~/.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,8 +2,13 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-controller, mesh-catalog modules/mesh-delivery]
|
||||
updated: 2026-10-16
|
||||
updated: 2026-10-18
|
||||
decisions:
|
||||
- "ADR 0426"
|
||||
- "ADR 0421"
|
||||
- "ADR 0420"
|
||||
- "ADR 0419"
|
||||
- "ADR 0418"
|
||||
- "ADR 0408"
|
||||
- "ADR 0407"
|
||||
- "ADR 0406"
|
||||
@@ -30,7 +35,6 @@ decisions:
|
||||
- "ADR 0270"
|
||||
- "ADR 0267"
|
||||
- "ADR 0250"
|
||||
- "ADR 0249"
|
||||
- "ADR 0239"
|
||||
- "ADR 0238"
|
||||
- "ADR 0233"
|
||||
@@ -40,14 +44,15 @@ decisions:
|
||||
|
||||
# 47 — Delivery: from a commit to delivered
|
||||
|
||||
**A change reaches the machines in one of three ways, all owned by mesh-delivery: a hotfix (one pull request,
|
||||
delivered at its merge), a feature (one branch name across its repositories: in two or more, one delivery
|
||||
group merged together and delivered member by member in its order; in one, a lone delivery), or an epic (a run of milestones, each delivered as a feature). The trunk is what runs; there
|
||||
is no "release branch"** (ADR 0401).
|
||||
**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).
|
||||
|
||||
**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; and one chain, from module to commit to build to rollout to assignment, is kept in the bus's
|
||||
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.
|
||||
|
||||
@@ -68,7 +73,7 @@ controller's delivery tables and the timers that polled them.
|
||||
(deliveries, groups, a key per │
|
||||
pull request, the state table) ▼
|
||||
node-engine on every node
|
||||
builders (build seat) ◄── work queues: delivery builds, ready checks, other checks ───────────────────────── (reports; backups first)
|
||||
builders (build seat) ◄── work queues: delivery builds, approve-asked checks, other checks ───────────────── (reports; backups first)
|
||||
```
|
||||
|
||||
- **mesh-delivery** (the seat `mesh-delivery`) owns hotfixes, features and milestones as deliveries and delivery
|
||||
@@ -82,34 +87,37 @@ controller's delivery tables and the timers that polled them.
|
||||
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, labeled, unlabeled, commented, merged and closed, and
|
||||
commit statuses. It sets statuses, keeps the delivery's view as one comment on the pull request, and appends
|
||||
(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).
|
||||
|
||||
## Three ways a change reaches the machines
|
||||
## 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 together once its composed check passes and every member is marked, reviewed and approved, then delivered member by member in its order (ADR 0239 §16); in one repository, as a lone delivery like a hotfix |
|
||||
| **epic** | a run of milestones in one design (playbook 04) | each milestone is a feature, delivered when its own group is ready; the next starts when the previous one's delivery plan is done |
|
||||
| **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.** mesh-delivery merges a group's members once the group's composed check passed at
|
||||
their heads, the independent reviewer marked each member ready (ADR 0364 §1, §2), each member carries the
|
||||
review ADR 0382 §6 asks, and the operator approved each merge into main (ADR 0398 §3). The operator's Approve is
|
||||
asked for every merge into main, a hotfix's too (the operator, 2026-10-16 23:40).
|
||||
- **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).
|
||||
|
||||
## A delivery
|
||||
|
||||
@@ -121,8 +129,12 @@ Its id is `<owner>/<repository>@<twelve characters of its commit>`. It holds:
|
||||
its diffset (ADR 0238 §2, as ADR 0323 §1 changed it);
|
||||
- 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.
|
||||
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
|
||||
@@ -141,7 +153,7 @@ 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; it waits to be merged | none | — |
|
||||
| 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 | — |
|
||||
| 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 |
|
||||
@@ -152,7 +164,8 @@ allows.
|
||||
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). The row from `proposed` to `checked` takes only a verdict that names the trunk's head.
|
||||
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.
|
||||
|
||||
**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
|
||||
@@ -170,23 +183,29 @@ nodes' reports, never inferred from time.
|
||||
|
||||
## Order without a person
|
||||
|
||||
*ADR 0239 §11 and §12, ADR 0249, ADR 0402.*
|
||||
*ADR 0419, ADR 0418 §2.*
|
||||
|
||||
A delivery group's order is a table of pairs, each with the rule that won (ADR 0249 §3). The controller's
|
||||
`delivery-order` infers it from the manifests and the module graph; a person writes an `after: <repository>`
|
||||
line only where the rules cannot see the order.
|
||||
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.
|
||||
|
||||
| Rank (highest first) | Rule | Read from |
|
||||
| Edge | A module goes before | Read from |
|
||||
|---|---|---|
|
||||
| 1 | *declared*: an `after:` line | the member's description |
|
||||
| 2 | *built by* and *version skew* | the module graph and the change |
|
||||
| 2 | *verb before caller*: a member adding a verb to a seat's served verbs before a member adding that verb to an invoked verb | the manifests (ADR 0402) |
|
||||
| 2 | *SDK before its users*: a member changing `mesh-sdk` before every member whose modules are built on it | the module graph (ADR 0402) |
|
||||
| 3 | *engine before controller* | module names |
|
||||
| 4 | the repositories' names | — |
|
||||
| *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 |
|
||||
|
||||
Rules of one rank in opposite directions reject the group, naming both members, both rules and the line that
|
||||
would settle it (ADR 0249 §4).
|
||||
- **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
|
||||
@@ -201,10 +220,10 @@ commit on the SDK's trunk or the head of an SDK pull request the forge holds, op
|
||||
*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 marked ready on a second; every other check on a third. A builder takes from the first, then the
|
||||
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. A ready mark publishes its pull request's
|
||||
check request on the ready stream; a request left behind on the third for a head and trunk commit already
|
||||
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
|
||||
@@ -222,15 +241,15 @@ commit on the SDK's trunk or the head of an SDK pull request the forge holds, op
|
||||
|
||||
- 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 and no ready mark for 7 days is left out; a push, a ready mark or a recheck asks
|
||||
its check.
|
||||
- For a group, a move asks each member's own check, and the composed check runs again through those rechecks; a
|
||||
move made by a member merging in the group's order asks the other members nothing.
|
||||
- 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 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
|
||||
@@ -242,20 +261,39 @@ commit on the SDK's trunk or the head of an SDK pull request the forge holds, op
|
||||
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 member merges only with its group passed and reviewed** (ADR 0382 §6), and in the group's order
|
||||
(ADR 0364 §8).
|
||||
- **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.
|
||||
|
||||
## Ready marks and the merge
|
||||
## The Approve and the merge
|
||||
|
||||
*ADR 0364, ADR 0406 §1.*
|
||||
*ADR 0421, ADR 0398 §3, ADR 0406 §1.*
|
||||
|
||||
The independent reviewer marks a pull request ready at the reviewed head with the label `mesh/ready` and a
|
||||
comment `ready at <full sha>`, through `mesh-delivery.mark-ready`. The forge's `labeled` and `commented` events
|
||||
reach mesh-delivery in seconds; once both required checks passed against the trunk's head, it asks the operator's
|
||||
Approve for a merge into main, with the pull request, its issues, the reviewer's verdict and the delivery plan, and
|
||||
merges the marked head through the forge's merge tool on that Approve (ADR 0398 §3). 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. A push clears the mark (the `synchronized`
|
||||
event), and a failed check is said once and listed by `stalled`.
|
||||
- **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 merge window and the batch
|
||||
|
||||
@@ -269,12 +307,19 @@ event), and a failed check is said once and listed by `stalled`.
|
||||
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. 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).
|
||||
- **A plan fixes its commits when it starts.** A plan starts at its first send, after mesh-delivery's word, and
|
||||
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
|
||||
@@ -282,7 +327,9 @@ event), and a failed check is said once and listed by `stalled`.
|
||||
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).
|
||||
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`.
|
||||
|
||||
@@ -325,9 +372,9 @@ nothing, and the plan says so for that node.
|
||||
|
||||
One plan carries every merge of its batch, so a stop ends only the change it is for:
|
||||
|
||||
- **A group's stop.** When a member after a failed one is stopped and its plan is open, mesh-delivery asks
|
||||
`delivery-stop` for that plan with the stopped members' merges to leave out. 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.
|
||||
- **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.
|
||||
@@ -426,27 +473,29 @@ forge module's files of what it announced (ADR 0406 §6).
|
||||
| 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, transitions, machine steps, order, the chain's links |
|
||||
| `groups` | every group with its members in order and its derived state |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| `mark-ready` | a reviewer's mark at a head: the label and the `ready at` comment, by the mesh's own forge account |
|
||||
| `recheck` | a check asked again for a delivery whose verdict is out of date (ADR 0395 §6) |
|
||||
| `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 |
|
||||
|
||||
**The controller's verbs for it**: `delivery-plan`, `delivery-order`, `delivery-check`, `deliver`,
|
||||
**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 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).
|
||||
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).
|
||||
@@ -456,14 +505,17 @@ and its files). A person has `plans go <plan> --why`.
|
||||
What the build proves before this design is implemented (research 055's direction):
|
||||
|
||||
1. A feature across the controller and the catalogue with a new seat verb is delivered as one group, with no
|
||||
`after:` line, no two steps and one composed check.
|
||||
`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. A ready mark is acted on within seconds of its event.
|
||||
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.
|
||||
@@ -480,24 +532,39 @@ What the build proves before this design is implemented (research 055's directio
|
||||
- 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 feature step of playbook 07; the next starts
|
||||
when the previous one's delivery is done. Milestones 5 and 6 are split into steps when they come up.
|
||||
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 |
|
||||
| 3 | A feature with a new seat verb across the controller and the catalogue is delivered as one group: no `after:` line, one composed check, no "release branches" | 1 | mesh-controller, mesh-catalog (mesh-delivery) | 977, 978 |
|
||||
| 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 |
|
||||
| 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.
|
||||
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.
|
||||
|
||||
## What is not decided here
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: designed
|
||||
code: []
|
||||
updated: 2026-10-10
|
||||
status: in-progress
|
||||
code: [mesh-controller]
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0289"
|
||||
- "ADR 0201"
|
||||
@@ -12,6 +12,8 @@ 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,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: designed
|
||||
code: []
|
||||
updated: 2026-10-12
|
||||
status: in-progress
|
||||
code: [mesh-catalog, hq]
|
||||
updated: 2026-10-18
|
||||
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
|
||||
@@ -26,7 +30,8 @@ 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).
|
||||
[§ 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).
|
||||
|
||||
## 1. The parts
|
||||
|
||||
@@ -39,9 +44,12 @@ This document designs the first slice in detail, tracked by issue 425. The slice
|
||||
├ 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 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)
|
||||
|
||||
mesh-issues (control-node) the counters for decision and research numbers (§ 6)
|
||||
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)
|
||||
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
|
||||
@@ -55,9 +63,11 @@ the records module, as the issue tracker's import does.
|
||||
|
||||
## 2. What one record holds
|
||||
|
||||
- `number` and `kind` (decision or research), its key together; `title`, `topic`, `status`, `date`, `deciders`;
|
||||
- `number` and `kind` (decision or research; with the third slice also design and glossary entry, § 9), 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; markdown, as written;
|
||||
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;
|
||||
- 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
|
||||
@@ -190,11 +200,89 @@ 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, playbooks and glossary entries as kinds | `holds` answers for a design's decisions and a glossary word | issue 428 |
|
||||
| 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 |
|
||||
| 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).
|
||||
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.
|
||||
|
||||
## 10. How it is checked
|
||||
|
||||
@@ -212,6 +300,26 @@ repository keeps no copy of the records, generated or otherwise (issue 435, answ
|
||||
| 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: designed
|
||||
code: []
|
||||
updated: 2026-10-13
|
||||
status: in-progress
|
||||
code: [mesh-catalog, mesh-controller]
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0316"
|
||||
- "ADR 0317"
|
||||
@@ -12,6 +12,8 @@ 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: designed
|
||||
status: in-progress
|
||||
code: [mesh-controller, mesh-host, mesh-sdk, mesh-catalog]
|
||||
updated: 2026-10-14
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0348"
|
||||
- "ADR 0349"
|
||||
@@ -61,6 +61,10 @@ 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
|
||||
@@ -333,6 +337,17 @@ 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-16
|
||||
updated: 2026-10-17
|
||||
decisions:
|
||||
- "ADR 0383"
|
||||
- "ADR 0384"
|
||||
@@ -12,6 +12,10 @@ decisions:
|
||||
- "ADR 0388"
|
||||
- "ADR 0389"
|
||||
- "ADR 0390"
|
||||
- "ADR 0411"
|
||||
- "ADR 0412"
|
||||
- "ADR 0413"
|
||||
- "ADR 0414"
|
||||
---
|
||||
|
||||
# 58 — Agent work as the mesh's own
|
||||
@@ -21,8 +25,10 @@ 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.**
|
||||
(ADR 0383 to ADR 0390, from research 053.)
|
||||
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.)
|
||||
|
||||
**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
|
||||
@@ -106,6 +112,95 @@ 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:
|
||||
@@ -156,12 +251,36 @@ 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). The issues are opened once the operator confirms
|
||||
[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
|
||||
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
|
||||
@@ -186,3 +305,18 @@ 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.
|
||||
|
||||
Reference in New Issue
Block a user