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