267 changed files with 964 additions and 1734 deletions
@@ -1,32 +0,0 @@
---
name: review-health-and-events
description: Reviews a research effort's settled direction or a design against the architectural domains health and events and logs, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **health** and **events and logs**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Health: what each part's health check says, which conditions are raised and cleared and by what, whether any failure can pass silently (work that reports success and did nothing is the worst fault), and whether a part that waits says so (nothing-moved-in-five-minutes).
- Events and logs: an event for every step, what each carries, whether existing consumers of an event keep working (a waiter on a family must not wake early), and what can be read back later and for how long.
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
@@ -1,32 +0,0 @@
---
name: review-running-and-testing
description: Reviews a research effort's settled direction or a design against the architectural domains running qualities and testing, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **running qualities** and **testing**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Running qualities: build capacity (one check per change, ADR 0548), speed, storage growth against each form's cap, what happens while a part, the bus or the control-node is down, concurrency (two writers, a race with a take-over), start-up time.
- Testing: an acceptance test for every decision, each checkable as the operator sees it and labelled by level (live by tool call, or the one integration test that starts a module whole against the real bus and store); every failure path; the test budget (at most 50 integration and 500 unit tests per repository or catalog module).
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
-32
View File
@@ -1,32 +0,0 @@
---
name: review-seams-and-tools
description: Reviews a research effort's settled direction or a design against the architectural domains seams and tools, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **seams** and **tools**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Seams: where each piece of state lives on the bus (key-value bucket, stream, object store) and which module owns it; which seat, verb or event joins two parts; any link that is not NATS-native (a poll, a timer, HTTP between modules, a shared file, state only in memory); whether a form the manifest lacks is named with its cost (a mesh-sdk change rebuilds every module).
- Tools: for every new piece of state a tool that reads it, and for every new behaviour a tool that steers it (do by hand, ask again, drop, repair, re-check), as the instruction tools-are-how-a-module-is-spoken-to says.
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
@@ -1,32 +0,0 @@
---
name: review-security-and-upgrade
description: Reviews a research effort's settled direction or a design against the architectural domains security and upgrade, before it is proposed or merged (hq playbook 01, Review by architectural domain). Read-only; never reopens a decision.
tools: Read, Grep, Glob, Bash, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search
model: opus
---
You review one subject of the Novox mesh against two architectural domains: **security** and **upgrade**. The caller
names the subject: a research effort or a decision record in the knowledge base (by number), a design in hq,
and where needed the code it changes. Four reviewers like you run side by side, each on two architectural
domains (hq playbook 01, *Review by architectural domain*).
Read first: the subject whole (`knowledge-base.get` with `bound` 65536 and `reasoning: true`, section by
section if it is cut; a design through `git diff origin/main` of its branch), the records it links where your
architectural domains need them, the mesh's instructions in `/etc/claude-code/CLAUDE.md`, and hq's glossary
(`00-META/glossary.md`). Read code and records only. Call no verb that writes, edit nothing, and make no git
write.
What you judge:
- Security: who may call each new verb, how its grants are scoped (ADR 0363's grant form), which credentials go or remain, what can be replaced or forged (a digest, an author, a tag), what enters from outside the mesh, and every act that removes data.
- Upgrade: how what runs today moves to the new shape without a delivery breaking, in which order (mesh-sdk, the grants part, the indexer's judge, the module), what readers see during and after, how each step is rolled back, what a node on an older node-engine does, and bootstrap before the bus.
Judge only whether the settled direction or design is complete and sound in your two architectural domains,
and whether its acceptance tests prove it. Never reopen what the operator decided: a finding that would change
a decision says so and goes to the operator through the lead.
Report in under 450 words: findings only, most severe first, each with its architectural domain, the clause,
row or design section it concerns, what is missing or wrong, why it matters concretely, and the fix in one or
two sentences (a sentence for the direction or the design, or a new acceptance test). Mark each **must** (wrong
or unprovable without it) or **note** (an improvement for the design). Write plain words in the glossary's
words, with no analogies. Say what you read; a guess is said as one.
+1 -1
View File
@@ -21,7 +21,7 @@ All through the mesh MCP server (`mesh_describe` gives each verb's arguments).
number is spent; never pick one by hand.
3. **Propose the effort.** `mesh_call knowledge-base.propose` with `kind: research`, the reserved `number`,
`title`, `status: active`, `date` (today), `sections` (the documents in order, each `name` and `text`;
each named by a plain title, never as a file; the first, **Overview**: **what** is investigated, **why**, and **what it touches**), and `links`
the first `00-overview.md`: **what** is investigated, **why**, and **what it touches**), and `links`
of kind `touches` for every record, effort, issue or design it touches (`cites` for one it only names).
4. **Do the research** in further documents of the same effort. Each change is `propose` again with the
effort whole and `why`. A refusal names every rule broken: fix them all and propose again. On review
+3 -3
View File
@@ -13,7 +13,7 @@ words_test.go the glossary's retired words are not used, no daemon's line is
```
A failing test fails `go test`, so it can be a gate rather than a report. With `MESH_CATALOG_DIR` set, the words
test also compares the catalog's copies of the retired words and the daemons' lines with the glossary.
test also compares the catalogue's copies of the retired words and the daemons' lines with the glossary.
**Why this exists.** Until now nothing in this repository was verified by anything but reading,
which is how a superseded decision stayed live in the constitution for days and in
@@ -99,7 +99,7 @@ quotations, struck-through text, link targets, comments and frontmatter are take
`03-DESIGN/`, `AGENTS.md`, `README.md`, and issues opened from 2026-10-07. Decision records and research
efforts live in the knowledge base, which holds a proposed record to the same words (the table above). It
also fails when a glossary head word heads two entries or is also retired.
[`words-allowed.md`](words-allowed.md) names a document that could not be reworded at once, with a date. With `MESH_CATALOG_DIR` set it compares the catalog's copy
[`words-allowed.md`](words-allowed.md) names a document that could not be reworded at once, with a date. With `MESH_CATALOG_DIR` set it compares the catalogue's copy
of the tools' retired words (`retired-words`) with the glossary.
It also fails on a **daemon's line** (ADR 0272,
@@ -107,7 +107,7 @@ its insight of 2026-10-09): the operator types one command, `nox`, and a documen
controller's or the node-engine's binary followed by one of its commands. The lines are the check's own list
(`daemonLines` in the words test); a command that is a line only with an argument after it is matched as a line is written. Looked
for in the same documents, from 2026-10-09 for issues, in the document's own words — code blocks and
spans included, quotations and block quotes taken out. With `MESH_CATALOG_DIR` set it compares the catalog's copy
spans included, quotations and block quotes taken out. With `MESH_CATALOG_DIR` set it compares the catalogue's copy
(`retired-lines`) with the list.
It failed on something real before it passed: 581 uses of retired words in 63 documents on its first run, besides the glossary itself and research 034 —
+10 -10
View File
@@ -35,7 +35,7 @@ import (
// struck-through text, link targets, HTML comments and frontmatter are taken out. A word's plural is the word.
// unique no head word heads two entries, and none is also struck through on a *Not:* line.
// lines no daemon's line in a checked document (ADR 0272, its insight of 2026-10-09): the operator types `nox`.
// copy with MESH_CATALOG_DIR set to a checkout of the catalog, its copies of the retired words and the
// copy with MESH_CATALOG_DIR set to a checkout of the catalogue, its copies of the retired words and the
// daemons' lines equal this glossary's and this list (skipped, with its reason, when unset).
//
// Checked documents: 00-META/, 03-DESIGN/ (both layers), AGENTS.md and README.md, always; an issue opened on or after
@@ -52,7 +52,7 @@ const (
linesFrom = "2026-10-09"
)
var wordsAlways = []string{"00-META/", "03-DESIGN/", ".claude/", "AGENTS.md", "README.md"}
var wordsAlways = []string{"00-META/", "03-DESIGN/", "AGENTS.md", "README.md"}
// daemonLines are the daemons' lines (ADR 0272): each daemon's commands. "alone" is a command that is a line by
// itself; "with an argument" one that is a line only when written as one ("the mesh-controller module" is prose;
@@ -396,7 +396,7 @@ func checkUnique(retired []retiredWord, heads []string) []string {
return out
}
// linesList is the daemons' lines as the catalog's copy lists them: "<daemon> <command>", with " …" when an
// linesList is the daemons' lines as the catalogue's copy lists them: "<daemon> <command>", with " …" when an
// argument must follow.
func linesList() []string {
var out []string
@@ -479,26 +479,26 @@ func readList(file string) ([]string, error) {
return out, nil
}
// checkCopy compares the catalog's copies of the retired words and the daemons' lines with this glossary's. A
// checkCopy compares the catalogue's copies of the retired words and the daemons' lines with this glossary's. A
// difference names what the copy should list, so regenerating it is copying that from the failure.
func checkCopy(catalogue string, retired []retiredWord) []string {
var out []string
copyWords, err := readList(path.Join(catalogue, "retired-words"))
if err != nil {
return []string{"the catalog's copy of the retired words cannot be read: " + err.Error()}
return []string{"the catalogue's copy of the retired words cannot be read: " + err.Error()}
}
sort.Slice(copyWords, func(i, j int) bool { return strings.ToLower(copyWords[i]) < strings.ToLower(copyWords[j]) })
want := toolsList(retired)
if strings.Join(copyWords, "\n") != strings.Join(want, "\n") {
out = append(out, "the catalog's retired-words differs from the glossary: it should list "+strings.Join(want, ", "))
out = append(out, "the catalogue's retired-words differs from the glossary: it should list "+strings.Join(want, ", "))
}
copyLines, err := readList(path.Join(catalogue, "retired-lines"))
if err != nil {
return append(out, "the catalog's copy of the daemons' lines cannot be read: "+err.Error())
return append(out, "the catalogue's copy of the daemons' lines cannot be read: "+err.Error())
}
sort.Strings(copyLines)
if strings.Join(copyLines, "\n") != strings.Join(linesList(), "\n") {
out = append(out, "the catalog's retired-lines differs from this check's list: it should list, one per line:\n"+strings.Join(linesList(), "\n"))
out = append(out, "the catalogue's retired-lines differs from this check's list: it should list, one per line:\n"+strings.Join(linesList(), "\n"))
}
return out
}
@@ -532,7 +532,7 @@ func TestWordsNoDaemonLineWritten(t *testing.T) { expectNone(t, checkLines(rootF
func TestWordsCatalogueCopyAgrees(t *testing.T) {
catalogue := os.Getenv("MESH_CATALOG_DIR")
if catalogue == "" {
t.Skip("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalog's copy of the list was not read")
t.Skip("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalogue's copy of the list was not read")
}
retired, _, _ := parseGlossary(readFile(rootFS(), glossaryFile))
expectNone(t, checkCopy(catalogue, retired))
@@ -691,7 +691,7 @@ func TestAllowanceHoldsAWordBackUntilItsDateAndShrinks(t *testing.T) {
expectProblem(t, checkRetired(mapOf(tree("an overlay\n", "")), retired, nil, "2026-10-23"), `AGENTS.md:1: "overlay" is retired`)
}
// H5d: the daemons' lines and the catalog comparison.
// H5d: the daemons' lines and the catalogue comparison.
func TestLinesCatchesADaemonsLineButNotProse(t *testing.T) {
fsys := mapOf(map[string]string{
+40 -123
View File
@@ -30,8 +30,7 @@ is listed under [Homonyms](#homonyms), with the qualified form each domain uses.
- **mesh** — the whole: the nodes, the modules assigned to them, the controller that decides and the
records it keeps. One mesh per operator; a second mesh is a second everything.
- **domain** — an area of the mesh that owns a set of concepts and the one word for each, as described
in the section above (ADR 0244); its qualified form is **mesh domain**. Bare, "domain" keeps this sense; the
review's sense is always written **architectural domain** (Homonyms).
in the section above (ADR 0244).
ADR 0006 called seven of them the controller's *contexts*; they carry over as domains under the same
names, except *observability*, which is now **Health and repair**. Where a domain's records live in
the controller, the domain owns that store alone (ADR 0008).
@@ -67,7 +66,7 @@ is listed under [Homonyms](#homonyms), with the qualified form each domain uses.
## Module — what a module is and declares
**Purpose.** To say what one module is, in the one document every other domain reads.
**Recorded in** the catalog, one manifest per module. **Upstream of** every other domain; its words
**Recorded in** the catalogue, one manifest per module. **Upstream of** every other domain; its words
are a published vocabulary, fixed by the manifest's schema. **Decided by** ADR 0040, 0126, 0174, 0188,
0207, 0236, 0262.
**Uses:** module, seat, config value (Config), provision (Provisioning), data class (Data), health (Health
@@ -105,8 +104,7 @@ and repair).
- **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).
The controller's verb `upgrade` shows it until the lease write; from L5's delivery the seat `mesh-wants`,
held once by the assigner, answers it too, as `mesh-wants.upgrade`; a person's choice is kept by the assigner, and the bus is never rolled (ADR 0565 §7).
The controller's verb `upgrade` shows it.
## Config — the values someone may change
@@ -136,15 +134,11 @@ downstream of Module and Placement. **Decided by** ADR 0262, 0277, 0348.
module has moved to their config names (ADR 0348 §1, issue 630).
What a "flavor" once varied is a config value or a separate module.
*Not:* ~~flavor~~
- **config candidate** — the config declarations a pull request's head adds or changes against the trunk's
manifests, kept by the indexer per open pull request in its state `config-candidates`; mesh-config judges
it again for the values proposed for that pull request (ADR 0565 §2, §3).
- **predefined key** — a key of the catalog's `predefined.json`, the fixed list of keys for which mesh
- **predefined key** — a key of the catalogue's `predefined.json`, the fixed list of keys for which mesh
config and node config hold values, each with its type, description, validation and whether it is
trusted, such as `font-size`. A config value may name the predefined key it falls back to, of its own
type; one that names none resolves from its default and the module's own value only. The list is the
catalog build's: a change to it is validated with every manifest naming a key, and removing a key still
catalogue build's: a change to it is validated with every manifest naming a key, and removing a key still
named or still holding a value is refused (ADR 0348).
- **trusted** — said of a config value that decides what runs or who can reach what. At every level it is
set only at the controller's terminal or through `config` with propose, on the operator's Approve; a
@@ -241,35 +235,24 @@ every other domain has something to run on. **Recorded by** the controller and t
## 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; from ADR 0567, by the seat `mesh-placement`, in its states on
the bus. **Upstream of** Provisioning, Connectivity, Data
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261, 0411, 0539, 0549, 0559, 0567, 0571.
**Uses:** node, machine, module, seat, manifest (Module), config value (Config), proposal (Operator and conversation).
**Recorded by** the controller's `inventory` database; from ADR 0549, by the catalogue's placement files, which
the indexer reads onto the bus. **Upstream of** Provisioning, Connectivity, Data
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252, 0261, 0411, 0539, 0549, 0559.
**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,
Config) are `mesh-config`'s, never part of what the controller records (ADR 0348 §9).
- **placement** — which module runs on which node, with its pins, host ports and bindings, and what a person
decided of the node, as one key per node in the state `placement` of the seat `mesh-placement`. It changes only by
a placement proposal the operator approves, and no repository holds it or names a node (ADR 0567). Until ADR
0547's third milestone the controller's assignments are kept equal to it by the holder.
*Not:* ~~placement file~~ (hq)
- **mesh-placement** — the seat, held once for the mesh by the assigner, that keeps placement: its states
`placement`, `proposals` and `candidates`, and its seven verbs `placement`, `proposals`, `propose`, `withdraw`,
`again`, `adopt` and `hand-back`. Only the holder writes its states (ADR 0567).
- **seat holders** — the state `seat-holders` of the seat `mesh-placement`, written by its holder, the assigner: one
key per seat naming each holder and its node, composed from placement and the manifests' claims as the indexer
holds them, and rewritten only when a key differs. The tool runner and every other reader of who holds a seat
read it there, never the controller (ADR 0571 §6, §7).
- **placement proposal** — a change to placement put forward through the verb `propose`: the changes, the revision
of each key they were laid over, the nodes' answers, the verdict and the ask. It takes effect only on the operator's
Approve, and is discarded if declined, expired or withdrawn. A kind of *proposal* (Operator and conversation).
- **placement candidate** — one node's placement as a placement proposal lays it, kept in the state `candidates`
of the seat `mesh-placement` under `<proposal>.<node>` while the proposal is open. The node composes it in
**shadow** (composed and compared, never applied) and answers in its own have whether what it would run is the
same; the verdict fails only on a refusal or a node that has not answered (ADR 0567). Not the indexer's candidates of
a group, which are fingerprints (Delivery).
- **hand-back** — the operator's verb `hand-back` of the seat `mesh-placement`, answered until the lease write: it
deletes every placement key, and every node returns to the compositions the controller sends. A hand act (ADR 0567).
- **placement file** — the catalogue's file for one node: the modules placed on it and, for each, its pins, its
host ports and its bindings to the providers it requires. It changes only by a pull request on the catalogue,
and the indexer reads it at each trunk merge into its state `placement`, one key per node, which every reader
of placement watches (ADR 0549 §1 to §3). Until ADR 0547's third milestone the controller's assignments are
kept equal to it.
- **placement candidate** — a placement file of one node as an open catalogue pull request has it at its head,
kept by the indexer in its state `placement-candidates` under `<group>.<node>` while the pull request is open.
Every node composes it in **shadow** (composed and compared, never applied) and answers in its own have whether
what it would run is the same; the composition check passes only when every placed node has answered the same
or differs only where the change intends (ADR 0559). Not the indexer's candidates of a group, which are
fingerprints (Delivery).
- **pending assignment** — an assignment of a module whose build is still running, kept, and made by the
controller when that build registers the module, or ended with why when it does not
(ADR 0261).
@@ -394,7 +377,7 @@ and vault (Identity and access).
**Purpose.** To issue, hold, rotate and check the credentials and grants by which modules, nodes, agents
and the operator act. **Recorded by** the controller's `identity` and `licences` databases and the vault.
**Upstream of** Provisioning, Delivery, and Operator and conversation.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414, 0550, 0571.
**Decided by** ADR 0085, 0113, 0183, 0225, 0234, 0386, 0387, 0388, 0411, 0413, 0414, 0550.
**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**
@@ -432,9 +415,7 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
- **root key** — the key at the top of the signed logins, which vouches for the signing keys: sealed in the
vault and used only to replace them, on the operator's Approve (ADR 0388).
- **bus account** and **membership** — a module's or a node's account on the bus, and the subjects it
is issued. The bus server calls its accounts *users*; the mesh says bus account. Each module's membership
is kept in the grants part's state `memberships` (seat `mesh-grants`), one key `<node>.<module>` written
only when it differs, and the runtime reads its membership there (ADR 0571 §10).
is issued. The bus server calls its accounts *users*; the mesh says bus account.
- **rotate** — replacing a credential without a consumer holding one the provider does not know.
- **licence** — a model-access account the licence manager holds and **binds** to a consumer, the
coding agent on a node (ADR 0183).
@@ -456,7 +437,7 @@ and the operator act. **Recorded by** the controller's `identity` and `licences`
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, 0444–0447, 0524–0530, 0539, 0548, 0551, 0571.
**Decided by** ADR 0162, 0218, 0236, 0237, 0238, 0239, 0250, 0267, 0270, 0276, 0323, 0401–0408, 0428, 0429, 0444–0447, 0524–0530, 0539, 0548, 0551.
**Uses:** module, node, declaration and send (Placement), health (Health and repair), person.
- **merge check** — what a pull request carries before it may merge, judging the merge of its head into a
@@ -504,12 +485,6 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
- **build module** — the module of one language that does the build steps for it on a node (fetch, build, digest,
test), claiming that language's build seat: `mesh-build-go`, `mesh-build-node` and `mesh-build-image`. It pulls the
build requests of its language and writes each step's end on its own key (ADR 0525 §3, §6). Not the **builder**.
- **build log** — the builder's own module log `build-log`, in ADR 0297's form, kept 30 days and keyed per
module: one entry when a build of a fingerprint starts and one when it ends (the module, the fingerprint, the
repository, the directory and the commit, the build node and the time, the build's manifest and its `against`,
and at the end the outcome and each artifact's digest). A build's duration is read from its two entries, and no
other part records durations; the builder's module tool `builder_builds_since` answers the builds a reader
missed from it (ADR 0571 §1, §12).
- **package** — what code resolves when it is **compiled**: an npm/cargo/pypi dependency, by **version**.
Served by the **package-registry** (gitea). Only a build module talks to it.
- **artifact** — anything a build produces and the mesh delivers to a node by **digest**: an `image`, a
@@ -518,13 +493,6 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
(ADR 0156).
An image is one kind of artifact, and a module is not an image. A package and an artifact are two
protocols, not one store being weak (ADR 0075).
- **artifact sweep** — letting go of the artifacts no one needs any more, the seat `mesh-artifact-sweep`, held
once for the mesh by artifact-store-tools beside the artifact store (ADR 0571 clause 2, as its progressive
insight of 2026-10-29 has it; clauses 3 and 4). Its keep set comes from the store's own listing, the wants and
the build log: a digest a want names is kept however old, and so are each module's five newest builds
(ADR 0526 §1). From the lease write it sweeps at each build's end and records each deletion in the hand-act
log; until then the controller sweeps. Not the artifact store itself, nor the registry's own collection of
unreferenced bytes (ADR 0189).
- **build source** — the files one artifact is built from, in one or more repositories: for a Go program
its import closure (the directories of its packages, its Go module files) and its recipe; for any other
artifact the paths its manifest names, by default its module's own directory. Each artifact of a build
@@ -534,9 +502,9 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
is built from; this entry narrows it to the files within, and that use reads as *the repository holding
the build source*. Sharing a repository is not a dependency, and no build source is a whole repository
holding another module's files.
- **catalog** — what the mesh holds of every module, at which commit; and the repository the
- **catalogue** — what the mesh holds of every module, at which commit; and the repository the
modules live in. `mesh-catalog` and `catalog_modules` are identifiers and keep their spelling.
*Not:* ~~catalogue~~
*Not:* ~~catalog~~ (hq)
- **delivery** — one commit in one repository on its way to the nodes, from its pull request's head
being announced to one of its final states: one pull request, one status, one note
(ADR 0239).
@@ -586,7 +554,7 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
nothing collects it; the word goes to a *Not:* line with the as-is update after that delivery.
- **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 catalog that owns deliveries and delivery groups, holding the mesh-scoped
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
@@ -597,8 +565,7 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel
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 seven rules, ADR 0539 §6), the **node
agent** (the node-engine's own process, applying its wants), the **watchman** (presence and pulses; it also holds the
Health seat `mesh-watchdogs`, whose watchdogs are pulses, and keeps the dead letters, ADR 0564 §4, §5) and the
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 and saying the events
`delivered`, `moving`, `settled` and `stuck`, derived from the keys). The reviewer is the review
agent's session, not a part. Built over releases 1 to 6 of ADR 0429.
@@ -666,7 +633,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
becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
**Uses:** node, module, node-engine (Core).
- **health** — a module's or a core component's statement of how it is alive and ready, which the
@@ -680,16 +647,7 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
reads it as *waits for a person*
(ADR 0283).
- **probe** — one look at one thing's health, run by whoever owns the verdict; what a probe returns is a
**finding**, before it becomes a condition. A finding is kept as a key `<scope>.<id>.<kind>` in its observer's
bucket `findings` until it is no longer observed (ADR 0564 §2).
- **node health report** — each node-engine's own report of its node's health, a key that node's engine writes
and the watchman reads (ADR 0564, as its progressive insight of 2026-10-29 has it); its layout is mesh-sdk's.
- **observer** — a part that writes findings for the condition keeper: the watchman, the self-check and the
healers (ADR 0564 §2). An observer never writes a condition.
- **condition keeper** — the module mesh-conditions, holder of the seat `mesh-conditions`: the one writer of the
open conditions, raising each from an observer's finding and clearing it when the finding goes, and saying
`condition-raised`, `condition-changed` and `condition-cleared` (ADR 0564 §2, §3). The controller keeps them
until the take-over.
**finding**, before it becomes a condition.
- **signal** and **watchdog** — something that must keep happening (a heartbeat, a report after a send),
and the watcher that raises a condition when it stops (to-be 45 §3).
- **condition** — one open fact about something the mesh owns that is wrong, with a key and a severity;
@@ -697,8 +655,8 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
(ADR 0231).
What a wrapped dashboard raises is its own; the mesh's notion is a condition.
*Not:* ~~alert~~ (hq)
- **self-check** — the mesh's own examination of itself, run on demand and on a schedule (to-be 45 §4), held
by the seat `mesh-self-check` (ADR 0564 §6); the controller runs it until that is delivered. Its verb is `doctor`, an identifier; prose says self-check.
- **self-check** — the controller's own examination of the mesh, run on demand and on a schedule
(to-be 45 §4). Its verb is `doctor`, an identifier; prose says self-check.
*Not:* ~~doctor~~ (hq)
- **healer** — a registered response to one condition kind: its **repair** (the ordinary path again),
its **budget**, its back-off and its brake. A healer may not withdraw, delete or recreate data.
@@ -708,15 +666,7 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
channel, to see an answer travel the whole way: its approval performs nothing and is recorded as the
operator's decision (ADR 0259).
Not a drill: nothing is broken.
- **hand-act** — a repair a person made by hand, recorded so the mesh knows it happened. The **hand-act log**
is the module's log in ADR 0297's form, the stream `acts` of the seat `mesh-hand-acts`, held once by the module mesh-hand-acts, which also records
who chose an ask (`warranted`); every recorder calls it, and the controller's own log is read beside it
until the lease write (ADR 0565 §10, §11).
- **take-over** — the operator's approved switch of a job from the controller to the part that replaces it, with a
way back until the lease write: for the bus's user list, the grants part's list becomes the one loaded (ADR 0547
§8); for conditions, the condition keeper's events become the ones the router hears (ADR 0564 §9); for
config, mesh-config's `take-over` is refused while its `compare` shows a difference between its new source and
the controller's mirrors (ADR 0565 §4).
- **hand-act** — a repair a person made by hand, recorded so the mesh knows it happened.
- **witness** — what rolls a core component back when its new build does not become healthy: never the
component itself (to-be 45 §8).
@@ -724,7 +674,7 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
**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, 0571.
**Upstream of** 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`,
@@ -734,10 +684,6 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
at one moment; **restore** — bringing it back beside the live data, never over it.
- **stream snapshot** — the bus's own backup of each stream, taken by the module holding `mesh-broker`
(ADR 0235).
- **disk readings** — the size of each data item a node's modules declare, measured by its `measure` and kept
with its largest size in seven days by disk-load on that node, in its own state, one key
`<node>.<module>.<item>` written only when it changes; the holder of `node-backup`, mounts, nfs-server and the
self-check read them there, never the controller (ADR 0571 §5).
- **sticky binding** — a consumer's tie to the data a provider keeps for it, which moves only by a
person (ADR 0232).
@@ -795,7 +741,7 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03
say `mesh-cli`. The operator also calls the mesh itself *nox mesh*. *nox* is also the agent (ADR 0414 §7): see
[Homonyms](#homonyms), and say which is meant. It is the one command the operator types: every line the mesh addresses to the operator — a
condition's words, a verb's answer, a README, a design — names a `nox` line, never a daemon's (the controller's
or the node-engine's binary followed by a command), which the words test and the catalog's check refuse
or the node-engine's binary followed by a command), which the words test and the catalogue's check refuse
(ADR 0272,
its insight of 2026-10-09).
- **channel / intake** — the two kinded benches the mesh talks to its operator through: `channel`
@@ -871,7 +817,7 @@ it was told, and the servarr apps' own libraries. **Upstream of** nothing. **Dec
**Uses:** module, seat, config value (Config).
- **content** — a work a person watches, listens to or reads: a film, an album and its tracks, an
episode, a book, a session of a sport event. It is known by an identity from a catalog outside the
episode, a book, a session of a sport event. It is known by an identity from a catalogue outside the
mesh, never by a file's name, and a work with no copy anywhere is still content (ADR 0302).
- **kind** — a kind of content, declared as configuration, never code: its parts, the metadata fields
that identify a work, the file extensions of its copies, and its folder and file templates. The first
@@ -924,7 +870,7 @@ and no session (ADR 0278), in its plain sense.
entry; the seat `mail-reading`, held by `mesh-mail-reader` (ADR 0459). Not *triage* (the issue tracker's
judgement), and not `mail_read`, `mail-store`'s tool that fetches one mail's text.
- **mail entry** — what mail reading keeps about one mail: its mail group, a one-line summary, its mark and why, a
due date; never the body (ADR 0459 §4). Not *catalog entry*: the catalog is Delivery's.
due date; never the body (ADR 0459 §4). Not *catalogue entry*: the catalogue is Delivery's.
- **mark** — what a mail entry says the mail is to the operator: *matters*, *scam warning* or neither, never two
(ADR 0459 §7). Not *kind* (Media's).
- **mail group** — a set of mails the model put together because they belong to one subject (ADR 0459 §4). Not *delivery
@@ -967,7 +913,7 @@ and no session (ADR 0278), in its plain sense.
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, 0592.
0284, 0290, 0291, 0423, 0424.
- **research effort**, **graduation**, **hand-off**, **playbook** — an investigation, held whole in the
knowledge base and named "research NNN";
@@ -985,33 +931,10 @@ Not a domain of the mesh but of the way it is built, listed because its words me
entries too (ADR 0423); takes them as proposals, has them reviewed and accepted,
and answers what holds: the seat `knowledge-base`, held by the module `mesh-knowledge`
([55 — The knowledge base](../03-DESIGN/01-to-be/55-the-knowledge-base.md)). Not **the record**, the literal reader
of this repository. It keeps them on the bus: each record's head in its state, its versions on a stream, its
sections' bodies once each in an object bucket by digest (ADR 0592).
- **section** — of a record: one part of its reasoning with a plain title, such as Context or Direction, stored once by
the digest of its body, and written and reviewed on its own; never a file, and never named like one. It is named as
"research 081 › Direction"; a clause as "ADR 0290 §7" (ADR 0592). Not an **instruction section** (see
[Homonyms](#homonyms)).
- **draft** — of a record: the version being written, one section per call, held by the knowledge base and by one
**author**, until it is submitted as a version or dropped; another session may **take it over** with a reason, as a
proposal (ADR 0592). Not a **drafted** clause, which the import's judgement wrote from prose (see
[Homonyms](#homonyms)).
- **opened** — of a draft: begun by `open` on a reserved or existing record, with its author, and said by the event
`opened` (ADR 0592). Not **drafted**.
- **drafted** — of a clause: written by the import's judgement from a prose Decision section, and never served as in
force until a second judgement confirms it; the event `drafted` says so (ADR 0290, ADR 0321). Never said of a
draft, which is **opened**.
- **form-only version** — of a record: a version that changes only its sections' titles and the targets of its links,
proved by a check rather than judged, and marked `form`; never a change of meaning (ADR 0592).
- **search index** — the knowledge base's full-text index of the records, built in the module's memory from the bus
at start and kept by the events; `ask` and `holds` answer from it (ADR 0592).
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/`.
- **architectural domain** — one of the eight concerns a research effort's direction and a design are reviewed
against before they are proposed or merged: security, seams, running qualities, health, events and logs,
testing, upgrade and tools (playbook 01, *Review by architectural domain*; the operator, 2026-11-02). A reviewer
judges whether the settled direction is complete and sound in its architectural domains and never reopens a
decision. Not to be confused with a **mesh domain** (Homonyms).
- **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).
@@ -1074,8 +997,6 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| | whether the machines a change touches still compose | **composition check** | Delivery |
| membership | the subjects a bus account is issued | **membership** | Provisioning |
| | an account in a group of the machine | **account group** | Placement |
| domain | an area of the mesh that owns a set of concepts and the one word for each (ADR 0244), the sections of this page; the one sense "domain" keeps bare | **mesh domain** | The record |
| | one of the eight concerns a direction and a design are reviewed against (playbook 01); never written bare | **architectural domain** | The record |
| push | the controller giving nodes their declarations, until release 6 | **send** (verb `push`) | Placement |
| | a git push | **git push** | The record |
| | a phone notification | **push notification** | Operator and conversation |
@@ -1107,15 +1028,11 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
| 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 |
| section | one titled part of a record's reasoning | **section** | The record |
| | one named section of the managed instruction file | **instruction section** | Operator and conversation |
| draft | a record's version being written, held by one author | **draft** | The record |
| | a clause the import's judgement wrote from prose | **drafted** clause | The record |
| | the predecessor's operational memory, in older playbooks (issue 430) | none: it no longer exists | — |
| check | a pull request's two statuses, until release 4 of ADR 0429 | **merge check**, **repository check**; from release 4, `mesh/check` | Delivery |
| | whether the machines a change touches still compose, the assigner's | **composition check** | Delivery |
| | a delivery group's verdict | **composed check** | Delivery |
| | the mesh's own examination | **self-check** | Health and repair |
| | 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 holder of the seat `node-build-agent` until R4g; from release 4 of ADR 0429 the part `mesh-delivery-builder` | **builder** (the part); **build module** (what builds) | Delivery |
@@ -1151,7 +1068,7 @@ The words test in `00-META/checks/` (`go test ./...`, run by the checker as `mes
word or a bare identifier in running prose in `00-META/`, `03-DESIGN/`, `AGENTS.md`, `README.md`, and
issues opened from 2026-10-07 — code spans, quotations and link targets excepted — and on a
head word that heads two entries or is also retired. The words retired with no scope or with *(tools)*
are copied into the catalog as `retired-words`, whose own repository check holds the descriptions of
are copied into the catalogue as `retired-words`, whose own repository check holds the descriptions of
the mesh's tools to them; a change here that retires such a word changes that copy in the same delivery.
A decision record or research effort proposed to the knowledge base is held to the same words by its
rules `retired-word` and `daemon-line`, which read this page (ADR 0290). Homonyms are checked by review.
+2 -2
View File
@@ -196,7 +196,7 @@ Three levels, by how much real system a test uses (ADR 0391):
| Level | Uses | Budget | Runs |
|---|---|---|---|
| **Unit** | one piece of logic alone: no database, network, bus, running service or other process | well under a second per test | locally, the tests of what changed, while working; and in the forge's checks |
| **Integration** | the real thing beside it: a database, the bus, a catalog tree | the repository's whole suite in seconds, never over sixty | only in the forge's checks, once per push |
| **Integration** | the real thing beside it: a database, the bus, a catalogue tree | the repository's whole suite in seconds, never over sixty | only in the forge's checks, once per push |
| **End to end** | the whole mesh, or a lab mesh, from the outside | — | only when the operator asks |
- **Test each behaviour at the lowest level that can prove it.** A defect found higher up gets a test at the
@@ -209,7 +209,7 @@ Three levels, by how much real system a test uses (ADR 0391):
### Tests within a time budget — how it was earned
On 2026-10-16 a feature's fixing agents ran the catalog's whole merge check (about ten minutes), a
On 2026-10-16 a feature's fixing agents ran the catalogue's whole merge check (about ten minutes), a
throwaway database and a locally built gate during local development. Each round cost tens of minutes and
caught neither real failure; the forge's checks found both.
+10 -49
View File
@@ -43,22 +43,16 @@ An idea arrives in one of two forms, and the first step differs:
is spent whether or not the effort is ever proposed; one never proposed is withdrawn after a week. Never
pick one by hand.
4. **Propose it.** `knowledge-base.propose` with `kind: research`, the `number`, a one-line `title`,
`status: active`, `date` (the day it began), and `sections`, each a `name` and its markdown `text`. A
section is a part of the record, never a file: its `name` is a plain title, a reference to another section
names that title ("the Direction's test 8"), and code evidence names the repository, the commit and the
function, never a path or a line (the operator, 2026-11-02: "don't use file references or local files
anymore"; the knowledge base is to be shown as a web page, where a file name links to nothing). Once
proposed, the record is the only copy: it is changed by `knowledge-base.get` and `propose`, never kept
in a local draft. The first sections:
- **Overview**: the idea in the operator's words, what exists, the operator's answers (dated), and the
`status: active`, `date` (the day it began), and `sections`, each a `name` and its markdown `text`:
- `00-overview.md`: the idea in the operator's words, what exists, the operator's answers (dated), and the
words the effort uses that the glossary lacks, each defined in one line;
- **Questions**: the questions to answer, the first ones first;
- **Method**: what will be tried and measured, and on what.
- `01-questions.md`: the questions to answer, the first ones first;
- `02-method.md`: what will be tried and measured, and on what.
Name what it touches as `links` of kind `touches` (`"0276"`, `"research 045"`, `"issue 365"`, or a
design's path), and what it names for another reason as `cites`.
5. **Do the research.** Try, measure and read, smallest question first. Each finding goes into
the section **Findings**: what was tried, what it showed (with numbers), what it means. Each change of the effort
`04-findings.md`: what was tried, what it showed (with numbers), what it means. Each change of the effort
is `knowledge-base.propose` again, whole, with `why`; every version is kept, and `knowledge-base.history`
lists them and gives the difference between two. Until issue 844 is delivered, the knowledge base
takes a new version of an accepted, active effort only as a progressive insight: give `insight`, saying
@@ -75,51 +69,20 @@ An idea arrives in one of two forms, and the first step differs:
server, a protocol) is tried in process, on the machine's loopback, in minutes. Lab runs (about 12 to
16 minutes fresh) are for the mesh's own code; before that code exists, a lab run tests nothing an
in-process test does not, and its lab scenario is written as the build's acceptance test instead.
6. **Place it in the domains.** Before the direction, write the section **Domains**: the domain or domains the idea
6. **Place it in the domains.** Before the direction, write `03-domains.md`: the domain or domains the idea
belongs to (the glossary's sections, ADR 0244); the modules and seats it lands in, each new, changed or
retired; what it does to each domain's words, each word added (with its domain and a one-line meaning),
changed or retired, and none on a *Not:* line or already meaning something else (the glossary's homonyms);
and whether a domain grows or shrinks, a concept moving between domains included. The direction names its
decisions in those words. Asked when the idea is set up, too: an option that lands a concept in the wrong
domain is offered with that cost named, like one that breaks a rule.
7. **Settle the direction with the operator.** When the questions are answered, write the section **Direction**:
7. **Settle the direction with the operator.** When the questions are answered, write `05-direction.md`:
the proposed decisions in a few lines each, the options rejected and why, and the build's acceptance
tests: what the build must prove before it is done, carried by its milestones (playbook
[04](04-build-handoff.md)). Nothing else is left open: each question closes by a test, by a decision the
effort records with its reason (a small design limit is the lead's), or by the operator. Ask the
operator to confirm or change the direction, one point at a time. Then review it by architectural domain (below) and
propose it once. Then the effort graduates through playbook [02](02-graduation.md).
## Review by architectural domain
A direction the operator settled can still be wrong where nobody looked. Before it is proposed, and again
before a design is merged (playbook [02](02-graduation.md)), it is reviewed against each **architectural domain**
(the operator, 2026-11-02), by reviewers on the larger model, about two architectural domains each, side by side:
the four reviewer agents in `.claude/agents/` (`review-security-and-upgrade`, `review-seams-and-tools`,
`review-running-and-testing`, `review-health-and-events`), each given the subject by number or path:
- **security**: who may call each new verb, how its grants are scoped, which credentials go or remain, what
can be replaced or forged, and what enters from outside the mesh;
- **seams**: where each piece of state lives on the bus, which seat, verb or event joins two parts, and any
link that is not NATS-native. Each piece of state is kept in the bus storage form that fits it (a key-value
bucket, a stream, an object store); a form the manifest lacks is added to mesh-sdk, and the review names that change
and its cost (a mesh-sdk change rebuilds every module) to the operator, never folds it in silently (the
operator, 2026-11-02);
- **running qualities**: build capacity, speed, storage growth, and what happens while a part, the bus or the
control-node is down;
- **health**: what each part's health check says, which conditions are raised and cleared, and whether any
failure can pass silently;
- **events and logs**: what each step says on the bus, and what can be read back later;
- **testing**: an acceptance test for every decision, each one checkable and within the test budget, and the
failure paths;
- **upgrade**: how what runs today moves to the new shape without a delivery breaking, in which order, and
how it is rolled back;
- **tools**: which tool reads each new piece of state and which one steers each new behaviour.
A reviewer judges only whether the settled direction is complete and sound in its architectural domains; it never reopens
a decision. Each finding is a sentence for the direction or a new acceptance test; a finding that changes a
decision goes to the operator. The direction ends with a section *Reviewed by architectural domain*: per architectural domain, what was
found and how it was answered, or that nothing was found.
operator to confirm or change the direction, one point at a time, then propose it once. Then the effort
graduates through playbook [02](02-graduation.md).
## Bounds and models
@@ -130,7 +93,6 @@ found and how it was answered, or that nothing was found.
| each experiment or reading task (5) | 30 minutes | smaller, one agent at a time unless the operator allows more |
| placing it in the domains (6) | 15 minutes | larger |
| the direction (7) | 30 minutes | larger |
| the review by architectural domain | 20 minutes | larger, about two architectural domains per reviewer |
A step past its bound is said to the operator at once, with what is being done about it. The lead reports
as playbook 07 does: every ten minutes and at once when something is found, stops or needs the operator,
@@ -172,8 +134,7 @@ The rules on an effort are the knowledge base's: `propose` refuses one that brea
record (the module's table test holds one case per rule), and the rules that judge the whole record set run
after every acceptance and raise a condition when one fails. The habits of steps 2, 5, 6 and 7 (options checked
against the rules, one version per settled set, the cheapest level, nothing left open) are the lead's, read by
the reviewer; the review by architectural domain is read in the direction's *Reviewed by architectural domain* section (nothing refuses a direction
without it yet: the lead and the reviewer hold it); of the pre-check, `propose` refuses a link to nothing and a retired word, and the review returns
the reviewer; of the pre-check, `propose` refuses a link to nothing and a retired word, and the review returns
a record named but not linked, an untracked deferral and a figure not as measured. That this repository holds
no effort is the records test's `no-records` check, on every pull request. An active effort with no new version
for seven days is the smell of research left behind: its lead closes or continues it.
+5 -9
View File
@@ -13,7 +13,7 @@ clauses in force for a topic or a record).
## Graduating research
The effort's Direction, confirmed by the operator (playbook [01](01-research.md), step 7), is what
The effort's `05-direction.md`, confirmed by the operator (playbook [01](01-research.md), step 7), is what
graduates. The lead writes the records and the design on the larger model; it is one sitting, bounded at
about an hour, and said to the operator if it runs past.
@@ -49,11 +49,9 @@ about an hour, and said to the operator if it runs past.
---
```
The design lists the direction's acceptance tests: what the build proves before it is done. Before it is
merged, the design is reviewed by architectural domain as playbook [01](01-research.md#review-by-architectural-domain) says, and
ends with its own *Reviewed by architectural domain* section; a design amended without an effort is reviewed the same way.
The design lists the direction's acceptance tests: what the build proves before it is done.
The words of the effort's Domains go into the glossary in the same change, each in its domain: a
The words of the effort's `03-domains.md` go into the glossary in the same change, each in its domain: a
word added gets its entry, a word whose meaning changed gets its entry rewritten, and a retired word moves to
its replacement's *Not:* line. A word that already means something else in another domain is named as a
homonym with the qualified form each domain uses.
@@ -131,8 +129,7 @@ A design changes only through a decision.
1. Write the decision record (above). If it reverses an earlier one, it `supersedes` it.
2. Edit the to-be design document, add the record to its `decisions:` as `"ADR NNNN"`, and set `updated:`
to today. Review it by architectural domain before it is merged (playbook [01](01-research.md#review-by-architectural-domain)), and
rewrite its *Reviewed by architectural domain* section.
to today.
3. If the amendment came from an issue, give the issue the document path in the issue tracker:
`amended-design` on its `issue-tracker.move` to `resolved`, or `issue-tracker.link` with kind
`design` before then — never in the report's frontmatter
@@ -167,8 +164,7 @@ record (its module's table test holds one case per rule), and the four rules tha
changes, an accepted record extending a superseded one) run after every acceptance and raise a condition
when one fails. In this repository, the records test checks that a design's `decisions:` entries and every
"ADR NNNN" in the text are well formed, and that no record is written here; the index test checks the reading
order's six topics, which the knowledge base reads. A design's *Reviewed by architectural domain* section is read by the
reviewer of its pull request; nothing refuses a design without it yet. The three habits of graduating step 2 (side by side, clauses
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
+1 -1
View File
@@ -91,7 +91,7 @@ import read, which every imported issue names (`imported.commit` in `issue-track
ADR 0281).
4. Resolve. `issue-tracker.move` to `resolved`, which the tracker takes only from `fixing`, with `fixed-by`, and, if the root cause was a design gap,
run playbook [02](02-graduation.md) and give `amended-design`. **A core issue** — its `located-in`
names the controller, the node-engine, the tool runner, the SDK, or the catalog's bus, forge or build
names the controller, the node-engine, the tool runner, the SDK, or the catalogue's bus, forge or build
agent — resolves with `replay`, the id of its replay in mesh-lab's replays register, proved to fail on
the commit before the fix and pass on it, or with `replay-none` saying why none is possible
(ADR 0237);
+60 -132
View File
@@ -24,7 +24,7 @@ A small fix (one repository, one issue, about an hour of work) follows
- **Review that keeps hardening.** One fix went through seven review rounds in five and a half hours, each
asking for more hardening than its issue asked (issue 704, 2026-10-14). A round is for what makes the
change wrong; everything else is an issue of its own.
- **The first check after the whole branch is written.** A feature's catalog branch, pushed once after
- **The first check after the whole branch is written.** A feature's catalogue branch, pushed once after
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
@@ -44,10 +44,6 @@ A small fix (one repository, one issue, about an hour of work) follows
on the bus; two reviews read the code, no test ran it under a node's real permissions, and once it was live every
node's agent was refused that read and stopped composing (issue 1126). How-we-build's rule "A bus permission is
proven under the real permission" (ADR 0560) is what the review now asks for.
- **A change built on what was remembered, not on what runs.** In the days up to 2026-11-02 a release took the
controller's jobs apart from the records and the survey alone. Jobs nobody had named surfaced only after the
delivery, a moved module's wiring and a changed failure label's readers were never read, and every check found one
more fault. Each layer had checked the layer before it; none had read the running code. Step 0 item 3a is the answer.
- **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.
@@ -59,8 +55,8 @@ opened **once**, after the review, and **one check** (ADR 0548). It runs in five
| Step | What happens | Bound |
|---|---|---|
| 0. Prepare | the lead reads what exists today in the code and on the live mesh, settles the open questions and writes one context file per repository, opening with what exists, what relies on it and what happens to each | 15 minutes, longer when what exists is large: code never starts on an incomplete answer |
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit, the integration tests compiled (those that exercise the change are run by name before the push, step 3); nothing is pushed | the milestone's issues, 30 minutes each |
| 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 integration tests compiled; nothing is pushed | the milestone's issues, 30 minutes each |
| 2. Review | one reviewer reads all of it, the local commits, judging the integration tests by reading them against the code; reports go both ways; at most 3 rounds | 15 minutes |
| 3. Check | push the reviewed heads once and open the pull requests together; the checker's one check runs the integration tests; no further review unless the check fails | one check run |
| 4. Deliver | merge; the source revision moves, each module is built once and wanted on its nodes, its canary first | until the view says the source revision `delivered` |
@@ -97,7 +93,7 @@ small fix ([playbook 08](08-small-fixes.md)) while the milestone waits on a chec
rest are fixed right after the milestone's delivery, before the next milestone starts, together on one branch where
they share a repository. Either way it stays out of the running milestone's branch and pull request, unless it blocks
the running milestone, so the scope above holds. Only an issue in another design's milestones (one design is built at
a time), or one that needs a decision or the operator first, waits, and the report says which and why. Each detailed report's line "Opened along the way" says for every
a time), or one that needs a decision or the operator first, waits, and the report says which and why. Each report's line "Opened along the way" says for every
issue whether it is being fixed in between, after the delivery, or waits, and on what.
## Step 0 — Prepare (the lead, larger model)
@@ -118,33 +114,8 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
2026-10-26: "that is simply our delivery plan and milestones, we've already researched it"); propose new research
only for what no record decides. A fixing agent that meets an undecided design question
stops, and its run is spent again.
3a. **Read what exists today, and write it down before any code** (the operator, 2026-11-02: "if i ask to implement
feature A, you'd better first check what exists today with regards to feature A"). Research, decisions, the survey
and the review each check the layer before them; this is the step that checks the running code. Each repository's
context file (item 4) opens with a section `What exists today` that answers three questions, in writing:
1. **What exists today for this feature**, read from the code (file:line) and from the live mesh through the mesh
MCP server's tools (the seats, the modules on each machine, their state on the bus, their tools' answers), never
from memory and never from a record alone. Where the code or the mesh contradicts a record, the answer says so,
and the lead settles it as item 3 says.
2. **Everything the change touches, replaces or removes, and what relies on each today.** For a part that is removed
or moved (a module, a seat, a job of a module, a file, a value's form, a label others read), every effect it has
is listed, found mechanically from its code and not recalled: each bus write (bucket key, stream subject, event),
each file it writes, each credential or grant it issues or rotates, each plan or record it makes, each process or
container it starts, each verb, tool or request it answers, and each timer it runs. Then, for each effect, every
reader that relies on it (a search across every repository and the live bus, file:line or address). The search is
written down as run (the pattern and where), so the review can run it again.
3. **For each item of the second answer, what happens to it after the change** (kept as it is, moved to a named seat,
module or verb, replaced by a named part, or dropped and why nothing needs it), **and the test that proves it**: an
acceptance criterion (item 5) at its level, or, for a dropped effect, the search showing no reader is left.
Code starts only when the three answers are complete for every repository: no fixing agent starts on a context file
without them, and an effect found later is the prepare step missed, written into the answer and its issue before the
fix. In the days up to 2026-11-02 the controller was taken apart without this answer: jobs nobody had named (making
a provision's credentials for a new consumer, planning a module's next build) surfaced only after the delivery; a
module was moved without reading how its pieces were wired; a failure label was changed without reading what relied
on it; and each check found one more fault the preparation should have known.
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:
- first, the section `What exists today` with item 3a's three answers;
- 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;
@@ -186,7 +157,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
the SDK's users (they pin an SDK commit, and a pin to a commit no branch holds is a download that fails later,
issue 1148); every program that parses a manifest strictly (search `ParseManifest` across every repository, with
the SDK commit each pins), which must take an SDK that knows a new manifest word (2026-10-21: design 60's
catalog milestone found mesh-build-go and the delivery's indexer reading manifests with an SDK from before the
catalogue milestone found mesh-build-go and the delivery's indexer reading manifests with an SDK from before the
word); the SDK's list of a defined seat's verbs, which must name a verb a module adds (2026-10-21: node-files' two
new verbs were refused on the claim); the node-engine's decoder for a new key on the wire; the route by which a
repository's first module registers; and a part that needs something the bus or a manifest cannot yet give, which
@@ -230,8 +201,8 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
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 is run by the pull request's one check. The integration tests (those that start a store, a bus
or a process) are compiled with `go vet -tags integration ./...` while developing and run by that check; before the group's push only the tests that exercise the change are run by
name (step 3), nothing else.
or a process) are compiled with `go vet -tags integration ./...` while developing and run by that check, nowhere
else.
- **Nothing is pushed and no pull request is opened in this step, and there is no try** (ADR 0548; the operator,
2026-10-26: "every release should only run 1 check or 1 try"). When every issue is committed, the lead brings each
feature branch up to the current trunk (fetch by name, then merge) and the review reads the local commits. Never call
@@ -244,8 +215,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
move another issue; **leave the integration tests to the check** (no store or bus started by hand): compile them
with `go vet -tags integration ./...`. A single integration test may be run locally against an embedded bus when
it is the fastest way to find a cause, said in the review report and the pull request; before the group's push the
tests that exercise the change are run this way, by name (step 3). After a failed check it
it is the fastest way to find a cause, said in the review report and the pull request. After a failed check it
usually pays to run that repository's whole integration level locally (`go test -tags integration -count=1 ./...` in each
module), by the lead or by the fixing agent on the lead's word, before the one more push: a verdict names only the first failed
test (issue 1921), and on 2026-10-23 R4g's faults surfaced one per run until a local run listed them all in a minute (a
@@ -254,7 +224,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and 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.
- **Two repositories that depend on each other** keep their order in the context files: the reader before the
writer, the controller's new manifest word before the catalog manifest that uses it.
writer, the controller's new manifest word before the catalogue manifest that uses it.
- **Several agents in one repository** (only for a large repository with independent areas): each works on a
local branch of its own from the feature branch's head and lands by fast-forward under a lock
(`flock <feature worktree>.lock git -C <feature worktree> merge --ff-only <its branch>`), after its unit
@@ -293,19 +263,10 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
work-queue retention; three review rounds passed it, and only its try showed that every seat's work stream has
that retention too, so dead asks across the mesh would no longer have been delivered again (issue 1178, fixed
before its pull request by a mark only a declared module work queue carries).
**A test fixture writes state in the form its real writer writes it** (for example a resolved config value with its
source, as the controller stores it); a fixture that agrees with the mistake proves the mistake. How it is checked:
the review asks it of every new fixture, and says which writer it compared with.
6a. **The reviewer reads the milestone file's three answers** (step 0, item 8) against the change: it lists every bucket
key and stream the diff creates or writes (search the diff for the bucket and stream names) and finds each in the
lifetime answer with a deleter and event, or a time to live; it reads the switch-over answer and checks it was done as
written or that the operator's choice is recorded; it reads the integration-level answer against the check the pull requests will ask. A missing answer, or a key or stream the diff creates that the answer omits, is a change asked.
6b. **The reviewer holds the change to each context file's `What exists today`** (step 0, item 3a). It refuses to
review a repository whose context file lacks the section or leaves one of its three answers open: that is the
prepare step undone, sent back to the lead, and not a round. For every part the diff removes, moves or changes the
form of, it runs the recorded search again on the reviewed tree and finds each effect and each reader in the second
answer; for each, it reads in the diff that the third answer's fate happened and that its test exists. An effect or a
reader the search finds and the answer omits, or a fate the diff does not carry out, makes the change wrong.
7. **A finding outside the issues' done-when and their acceptance criteria is never a change asked, in any round.** More hardening, a
further case, a nicer form: the reviewer opens it with `issue-tracker.open` and names the number in its
report, and the round does not wait for it. What a round asks is only what makes the change wrong against
@@ -324,17 +285,6 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
its shape, fixes every hit in one change and lists each file and line in the report. How it is checked: a
report to the second round naming a kind and one place only, followed by a third round on the same kind, is
this rule skipped.
10. **The reviewer asks two questions of every change and every design, and answers both in its report** (the
operator, 2026-10-28: "design everything NATS-native"; "repositories are fully mesh agnostic"). The first:
*where does each piece of state live, and how does each seam (an integration, data, a part talking to
another) travel?* Anything that is not bus state, a stream or a seat's verb on the bus is a finding, even when
an accepted record put it there. The second: *does a repository hold anything about this mesh's setup, such as
node names, node-specific placement or settings?* That is a finding: repositories are mesh-agnostic. On
2026-10-28 a placement design kept placement in repository files with node names, because the accepted record
it followed said so and nobody checked it against these two principles. A finding from either question makes
the change wrong, whatever record it follows; the reviewer names the record so it can be superseded. How it is
checked: the review report has a line for each question with its answer, and the lead reads both before the
push; a report without them is not an approval.
## Step 3 — Check (no review)
@@ -346,17 +296,8 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
and the integration level, through the build module of the repository's language and by that language's convention
(ADR 0445, ADR 0529); the check waits on the language's check stream until a build node has a free slot
(ADR 0525 §1). **No try:** never call `mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`, except when the
operator asks for one. While developing, no `go test -tags integration` run of a whole module or level, and no store or bus started
operator asks for one. While developing, no `go test -tags integration` run of a whole level, and no store or bus started
by hand on the machine (the operator, 2026-10-18).
**Before a group's single push, the integration tests that exercise the change are run locally against an embedded
bus**, picked by name (`go test -tags integration -count=1 -run '<names>'`): the tests in the touched packages that
start the part whole, not a whole module's or level's integration run, and not only a compile with `go vet`; a
change in how a node composes or applies always has such a test (2026-10-28: a compiled-only group failed in the one
check on what a local run shows). How it is checked: the pull request's description names those tests and their
results.
**While a group is being checked, nothing else is merged or pushed into a repository that is one of its members:**
a moved main asks the group's check again. How it is checked: the lead's wait list names the group's members, and
`mesh-delivery.show` shows one check per head.
**A group is one check.** Before the push, every member pins the group's own mesh-sdk head exactly (the checker refuses an
older commit of the same branch), then all members are pushed and opened together, so the group's check runs once. A head the review replaces
counts for nothing against a running check; say so, and ask no other until the new head is reviewed.
@@ -385,13 +326,13 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
ask named them): mesh-delivery takes a pull request's issues from the words `issue <n>` and `issues <n>` in its
description (the plural yields its first number only), so write `issue 1608, issue 1646`, and name any other
issue in words, never by `issue <n>` or `issues <n>`.
**A catalog pull request that removes a module, or a subject a module names, is also judged by the
controller's unit tests against the catalog's branch**, because those tests read the catalog and no check
of the catalog runs them: on 2026-10-20 mesh-catalog #381 removed the old delivery module, and every
controller check failed afterwards on a test of the catalog's subjects until issue 1406 removed the name
mesh-issues still listened to. A controller's check does not find it, since a check reads the catalog's main
(also in a delivery group that holds the catalog's pull request); the lead runs mesh-controller's unit
suite with `MESH_CHECK_BESIDE` naming a directory that holds the catalog's branch, as mesh-controller's
**A catalogue pull request that removes a module, or a subject a module names, is also judged by the
controller's unit tests against the catalogue's branch**, because those tests read the catalogue and no check
of the catalogue runs them: on 2026-10-20 mesh-catalog #381 removed the old delivery module, and every
controller check failed afterwards on a test of the catalogue's subjects until issue 1406 removed the name
mesh-issues still listened to. A controller's check does not find it, since a check reads the catalogue's main
(also in a delivery group that holds the catalogue's pull request); the lead runs mesh-controller's unit
suite with `MESH_CHECK_BESIDE` naming a directory that holds the catalogue's branch, as mesh-controller's
`internal/beside` says.
2. **The checker's merge check runs the integration tests once, as the gate** (its one status, `mesh/check`). No
second review: step 2 reviewed this tree.
@@ -438,31 +379,16 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
moved to include it): one Approve, one delivery. The old Approve ask closes itself on the new head and the new
head is asked once its checks pass. How it is checked: a follow-up that sits in a second pull request while the
first still waits shows in `gitea.gitea_list_pull_requests` as two open pull requests of one lead.
6. **A member pins mesh-sdk only at a commit that is a pull request's head**, merged or open (2026-10-28: the check
refused a pin to a merge commit on main as "on no pull request", though its tree equalled the head's). How it is
checked: the check's pin step; a refusal "on no pull request" is this rule skipped.
7. **Two groups that pin the same module's mesh-sdk conflict in `go.mod` and `go.sum`.** The group that merges
second merges main in and re-pins once, to a pull request head that holds both changes, after the first group
is merged and not before (a re-pin before makes the first group conflict). How it is checked: the check's merge
step; a group that conflicts after the first one merged is this rule skipped.
8. **A group that adds a seat, a verb or a manifest field in mesh-sdk and uses it in a module's manifest in the
same group cannot pass its check** until the running assigner pins a mesh-sdk that defines it, because the
check composes with the running assigner's mesh-sdk (issue 1585). The assigner's pin ships first, as its own
release. How it is checked: the check's composition step; a composition refusing the new word in such a group
is this rule skipped.
9. **A new head on an open pull request needs its `Reviewed-by` line at that head before the Approve is asked,
even for a pin-only commit.** How it is checked: the Approve's ask names the review record;
an ask whose line names another commit is this rule skipped.
**One exception: a catalog change that uses a new manifest word.** The catalog's merge check reads
**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 catalog'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.
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 catalog's. **Every release that must merge before
the catalog's is on a branch name of its own** (`feat/<slug>-node-engine`, `feat/<slug>-controller`): on the
feature's name it joins the catalog's delivery group, and a group merges only whole, so neither merges.
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.
@@ -480,10 +406,7 @@ the source revision equals its fingerprint and every have its want). After each
the view's wants page or its tools: `assigner_wants` and `assigner_want` (a want not yet at the newest fingerprint
names, in `newest` and `rule`, the rule that holds it back, and its words say why), and `node_haves` (the node
agent's haves: fingerprint, composition digest, phase and health). The delivery is done when the view says
`delivered` for its source revision; **"on the machines" from the delivery tools is not yet "delivered"**: the lead
also reads what the nodes run (the view's haves per node) and the indexer's record of the module (its newest
fingerprint) before saying a change is delivered. How it is checked: the report names each node's build.
Then the lead resolves the feature's issues with `fixed-by` naming its pull
`delivered` for its source revision; then the lead resolves the feature's issues with `fixed-by` naming its pull
requests, and removes what is left: no `feat/<slug>` branch and no `.work/<slug>` worktree survive.
**What a merge moves is read before the operator is told.** A change to mesh-sdk rebuilds every module that takes
@@ -588,43 +511,54 @@ Approve, a Retry or a Release waits for them, it reads `operator-channel.asks` a
on 2026-10-20 an ask was named to the operator that the router did not hold. A merge the gate passed but whose
ask is not on the router is the fault of the Approve's wait in the table above.
**The cadence is the instruction section "report-cadence"** (the operator, 2026-10-28; it replaces the full layout every
five minutes of "how-work-is-done"). 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.
**Every ten minutes (the operator, 2026-10-17), every two minutes while a delivery moves (the view says modules `moving`), 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.
- **Every five minutes** (ten until 2026-10-26, five since), every two minutes while a delivery moves (the view says
modules `moving`), and at once when something lands, stops or needs the operator, the lead sends the **short
report**: the tree exactly as `knowledge-base.progress` with `of: "design <nn>"` and `detail: minimal` answers it,
unedited, with the verb's read-at time, then at most three lines: what changed, what needs the operator, what is next.
Once the verb takes `since`, the short report asks `since: 5m`.
- **About every thirty minutes**, and whenever something stops, the lead sends the **detailed report**: the same verb
with `detail: detailed` (or `since: 60m`), then the full layout below.
- **Every report ends by scheduling the next** (a timer or a watcher set ahead), so that agents' incoming reports
never replace it.
- **An answer to the operator between reports is short and has no tree.**
**The tree is the verb's answer and is never drawn or corrected by hand.** `knowledge-base.progress` answers the
design's milestones, issues, pull requests and held groups in one call, each with when it last moved (issue 1947).
What it cannot read for certain from the design's milestone table it says in `warnings`; the lead fixes the table, it
does not correct the tree by hand.
**The full layout** (the detailed report) is the verb's detailed tree followed by these lines:
**The full layout** (in the session) is the whole design as a tree (the operator, 2026-10-17: always the full
overview): every milestone of the design with its state, and under the running milestone its repositories and
their issues, each issue under the one it waits for, marked ✓ landed or delivered, ▶ fixing, ⏸ waiting, ✗ stopped:
```
Feature <slug> · milestone <n> of <m> · step <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>
Opened along the way: <each issue opened during the feature, its number and a few words, and when it is fixed: in between, after the delivery, or waits on what> — 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> %
Worth it: <what in the last stretch served the goal, and what did not>
Next: <the next actions up to the delivery, in order>
```
How it is checked: the operator sees a short report at least every five minutes and a detailed one at least every
thirty; a report's tree that differs from the verb's answer of the same minute is a tree assembled by hand, a report
without the verb's read-at time is the same, and a report that does not end by scheduling the next is a breach the
lead reports.
**The tree is read, not assembled.** `knowledge-base.progress` with `of: "design <nn>"` answers the tree's
milestones, issues, pull requests and held groups in one call, each with when it last moved: `detail: minimal` for the
five-minute report, `detailed` about every thirty minutes and whenever something stops (issue 1947). What it cannot
read for certain from the design's milestone table it says in `warnings`; the lead fixes the table, it does not
correct the tree by hand. How it is checked: a report's tree that disagrees with the verb's answer of the same minute
is a tree assembled by hand.
On a channel such as Telegram the same short report is sent, the tree as a code block.
**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> %
```
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.
## The context of the lead's own session
@@ -667,12 +601,6 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
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.
- A context file exists per touched repository under `.work/<slug>/` while the feature runs.
- Every context file opens with `What exists today` holding step 0 item 3a's three answers, each search written as run
(`grep -n -A3 'What exists today' .work/<slug>/*.context.md`), and its time (the file's commit or the lead's note) is
older than the repository's first commit of the feature; the review report says it ran the searches again (step 2,
item 6b). A fixing agent started on a file without the section, or an effect of a removed or moved part met only
after the delivery, is the prepare step skipped, named in the next detailed report and written into the playbook's
lessons here.
- 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
+5 -17
View File
@@ -33,12 +33,7 @@ processing power"). Everything before the push is local and fast, and no try is
## Steps
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). **Read what exists in the fix's area today, briefly, before any
code** (playbook [07](07-feature-branches.md) step 0 item 3a, at a small fix's size): what the code and the live mesh
(through the mesh MCP server's tools) do there now, and for each value, label, file or job the fix changes or removes,
every reader that relies on it, found by a search across the repositories (written down as run) and not from memory,
with what happens to each after the fix. It is a few lines in the issue's note, under `What exists today`, and the
review runs the search again; a reader it finds that the note omits makes the fix wrong. If a design question is open, it is settled before any
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 (`knowledge-base.ask` first: what is decided is read before anything is designed), 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
@@ -58,14 +53,11 @@ processing power"). Everything before the push is local and fast, and no try is
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,
`go test ./...` at the root). The integration tests are compiled (`go vet -tags integration ./...`) and not run:
nothing is pushed, no pull request is opened and no try (`mesh-controller.try`, `mesh-delivery-checker.try`, `mesh-delivery-checker.checker_try`) is called, unless the operator asks for
one. A single integration test may be run locally against an embedded bus when it is the fastest way to find a cause;
before the push, the integration tests that exercise the change are run that way, by name
(playbook 07 step 3), and the pull request names them and their results. A test fixture writes state in the form its real
writer writes it; the review asks it.
one. A single integration test may be run locally against an embedded bus when it is the fastest way to find a cause.
4. **One review of the local commit** (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; it runs the `What exists today` search again, as playbook 07 step 2 item 6b; 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; it judges the integration tests by reading them against the code, as playbook 07 step 2 item 4 says; it answers playbook 07 step 2 item 10's two questions, on state and seams and on mesh-agnostic repositories, in its report). The reviewer's findings go back to the same agent that wrote the fix, continued by
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; it judges the integration tests by reading them against the code, as playbook 07 step 2 item 4 says). 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 the reviewed head once and open the pull request, only after the review approves.** The trunk is what runs and nothing holds merged work back
@@ -95,8 +87,7 @@ processing power"). Everything before the push is local and fast, and no try is
7. **Follow its delivery to done**: a watcher waits on the status view's events (`mesh-delivery-view.moving`,
`.settled`, `.stuck` and `.delivered`) and reads the view's `assigner_wants` and `node_haves` after each wake, as
playbook [07](07-feature-branches.md) step 4 says; the delivery is done when the view says `delivered` for its
source revision, and the haves per node and the indexer's record of the module name the build (playbook 07 step 4;
"on the machines" alone is not "delivered"). Then prove the fix on the mesh (the symptom gone, measured), and resolve the issue with
source revision. Then prove the fix on the mesh (the symptom gone, measured), and resolve the issue with
`located-in` and `fixed-by` the same hour.
A fix that stops before its delivery moves its issue back to `open` with a note saying how far it got
@@ -151,9 +142,6 @@ reading itself and uses one subagent only for the review.
- 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 with `What exists today` (`issue-tracker.get`) older than its first commit, its
search written as run; one without is step 1 skipped, and a reader of a changed value met only after the delivery
is that note left short.
- 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.
+8 -11
View File
@@ -221,8 +221,8 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
</g>
<!-- lead -->
<rect x="110" y="56" width="160" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<text x="190" y="78" text-anchor="middle" class="t">What exists · survey</text>
<text x="190" y="96" text-anchor="middle" class="mute">criteria · context file per repo</text>
<text x="190" y="78" text-anchor="middle" class="t">Survey · criteria</text>
<text x="190" y="96" text-anchor="middle" class="mute">context file per repo</text>
<rect x="340" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
<text x="410" y="78" text-anchor="middle" class="t">Up to trunk</text>
<text x="410" y="96" text-anchor="middle" class="mute">each branch, local</text>
@@ -275,7 +275,6 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<div class="box">
<h3>Step 0 · what the lead writes before any agent starts</h3>
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
<li><span class="tag new">new</span>What exists today, read from the code and the live mesh, never from memory: every effect of a part removed or moved (bus writes, files, credentials, plans, processes, calls answered), every reader relying on each, what happens to each after the change and its test. It opens each context file; no code before it is complete, and the review refuses work without it.</li>
<li>Every open question settled: on the issue, in the knowledge base, or by the operator.</li>
<li>Into each issue: every file and test it touches (file:line), how it is checked and delivered, a group's merge order.</li>
<li><span class="tag new">new</span>Every other reader and writer of each value the change touches, each with a criterion.</li>
@@ -356,8 +355,6 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
<li>Every criterion against its test: a test at its level that would fail without the change.</li>
<li>Every new integration test traced through the code by reading it: no run exists before the check.</li>
<li>That each integration test enters by the path the live trigger takes.</li>
<li>Where each piece of state lives and how each seam travels: anything but bus state, a stream or a seat's verb is a finding, even if a record put it there.</li>
<li>That no repository holds this mesh's setup (node names, placement, settings): repositories are mesh-agnostic.</li>
</ul>
<div class="note" style="margin-top:10px">Why reviews took more than one round (R3b, 2026-10-19): a test that "failed" only by not compiling, a reader of the changed record the survey missed, and bus tests judged by reasoning before they ran. ADR 0548 and playbook 07 step 2 item 4 change the last: bus tests are judged by reading them against the code, and the one check runs them after the review.</div>
</div>
@@ -416,15 +413,15 @@ const P = [
{n:"01",name:"Research",kind:"idea",c:"--idea",
trig:"An idea worth investigating before it is committed to design. A symptom is not research.",
who:"The session that heard the idea leads.",
steps:["Read what is known: ask, get, the tracker's list.","Ask 3 to 5 setup questions, one at a time, each with a recommendation.","Check every option against the mission and how-we-build first.","Reserve a number (kind research), never pick one by hand.","Propose the effort: Overview, Questions, Method, sections named by title, no file references.","Research; findings in the section Findings, one version per settled set.","Try at the cheapest level that answers the question.","Write the Domains: modules, seats, glossary words added or retired.","Write the Direction: decisions, rejected options, acceptance tests.","Ask the operator to confirm the direction, one point at a time.","Review the direction by architectural domain: security, seams, running qualities, health, events and logs, testing, upgrade, tools.","If the review asks changes: propose again, at most three times."],
steps:["Read what is known: ask, get, the tracker's list.","Ask 3 to 5 setup questions, one at a time, each with a recommendation.","Check every option against the mission and how-we-build first.","Reserve a number (kind research), never pick one by hand.","Propose the effort: overview, questions, method, with links.","Research; findings in 04-findings, one version per settled set.","Try at the cheapest level that answers the question.","Write 03-domains: modules, seats, glossary words added or retired.","Write 05-direction: decisions, rejected options, acceptance tests.","Ask the operator to confirm the direction, one point at a time.","If the review asks changes: propose again, at most three times."],
prod:"A research effort in the knowledge base, status active, every version kept. Nothing in hq.",
next:"02",bounds:"Setup 30 min · proposing 15 min · each experiment 30 min · direction 30 min · review by architectural domain 20 min · report every 10 min",
next:"02",bounds:"Setup 30 min · proposing 15 min · each experiment 30 min · direction 30 min · report every 10 min",
prev:["Research that measured nothing","A recommendation the operator must argue back","Efforts copied into hq"],
check:"propose refuses rule breaks; the knowledge base reviews; the records test on every PR."},
{n:"02",name:"Graduation & design change",kind:"idea",c:"--dec",
trig:"A research effort concludes, or an existing design must change.",
who:"The lead, on the larger model; the decision is recorded before the design moves.",
steps:["Check the conclusion against mission, context and effect.","Reserve a number per decision.","Propose one record per choice that can be reversed on its own.","Pre-check, then propose siblings together.","On changes: revise whole; after three rounds fix findings yourself.","A finding that would change what was decided: ask the operator.","needs-operator: wait for the operator's Approve.","Amend the design, or create a to-be design with status designed.","Add the decisions, updated:, and the effort's glossary words.","Review the design by architectural domain before it is merged.","Hand the design to the breakdown (04).","Close the effort: graduated with became links, or abandoned.","When it ships: write the as-is, set the to-be implemented."],
steps:["Check the conclusion against mission, context and effect.","Reserve a number per decision.","Propose one record per choice that can be reversed on its own.","Pre-check, then propose siblings together.","On changes: revise whole; after three rounds fix findings yourself.","A finding that would change what was decided: ask the operator.","needs-operator: wait for the operator's Approve.","Amend the design, or create a to-be design with status designed.","Add the decisions, updated:, and the effort's glossary words.","Hand the design to the breakdown (04).","Close the effort: graduated with became links, or abandoned.","When it ships: write the as-is, set the to-be implemented."],
prod:"Decision records; a to-be design with frontmatter; glossary entries; later the as-is.",
next:"04",bounds:"One sitting of about an hour",
prev:["A decision rewritten silently","A deferral nothing tracks","A design amended without a decision"],
@@ -464,15 +461,15 @@ const P = [
{n:"07",name:"Features",kind:"build",c:"--build",
trig:"Work that changes code across issues or repositories, too large for a small fix.",
who:"A lead (larger model), one fixing agent per repository (smaller), one reviewer (larger).",
steps:["Prepare: name the branch, make the worktrees, settle every question.","Read what exists today from the code and the live mesh: every effect of what is removed or moved, every reader of each, what happens to each and its test. No code before it is complete; the review refuses work without it. (new)","Write one context file per repository, opening with what exists today.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, what the one check does not cover. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per issue.","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk; nothing is pushed, no try runs. (new)","Review all of it, reading the integration tests against the code; at most 3 rounds; the report answers the state-and-seams and mesh-agnostic questions. (new)","A group pins the group's own mesh-sdk head in every member. (new)","Push the reviewed heads once and read each forge head against the reviewed commit; no draft pull request, no fix round on an open one. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by; the one check runs. A failed check is read, fixed, reviewed, pushed once more. (new)","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
steps:["Prepare: name the branch, make the worktrees, settle every question.","Write one context file per repository.","Write the findings and acceptance criteria into each issue.","Survey every other reader and writer of each changed value. (new)","Answer three questions in the milestone file: who deletes each new key and stream, how the switch-over is tried on one node first, what the one check does not cover. The review checks them. (new)","List every part the change reaches; copy fixtures from the real writer.","Develop: one agent per repository, one commit per issue.","A test fails first on behaviour, not on compiling. (new)","Bring each branch up to the trunk; nothing is pushed, no try runs. (new)","Review all of it, reading the integration tests against the code; at most 3 rounds.","A group pins the group's own mesh-sdk head in every member. (new)","Push the reviewed heads once and read each forge head against the reviewed commit; no draft pull request, no fix round on an open one. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by; the one check runs. A failed check is read, fixed, reviewed, pushed once more. (new)","The operator's Approve on Telegram; the group merges in one pass.","Follow the delivery in the status view (moving, settled, stuck, delivered) to done; resolve the issues; clean up. (new)","When the builder cannot deliver its own fix: revert, a build by a session's call, a registration at the terminal, copies kept outside the store; a broken controller is put back by a want the operator writes, a hand build by mesh-hand-compose and a want. (new)"],
prod:"One pull request per repository, one source revision delivered, resolved issues, no branch or worktree left.",
next:"02 (when it ships)",bounds:"Prepare 15 min · 30 min per issue · review 15 min, ≤ 3 rounds · one check, pushed once",
prev:["Pull requests as scratchpads","Context bought again and again","A change built on what was remembered, not on what runs","Review rounds spent on what a run would show","A node agent's bus reads, proven only by review"],
prev:["Pull requests as scratchpads","Context bought again and again","Review rounds spent on what a run would show","A node agent's bus reads, proven only by review"],
check:"The checker's one status, mesh/check; the review record; the tracker's move."},
{n:"08",name:"Small fixes",kind:"build",c:"--build",
trig:"One issue in one repository, about an hour of work.",
who:"The session, with one reviewer on the larger model.",
steps:["Read the issue and the code; settle any open question.","Read what exists in the fix's area today, briefly: every reader of what the fix changes. (new)","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Nothing is pushed and no try runs before the review. (new)","One review of the local commit.","Push the reviewed head once; only then open the pull request. (new)","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
steps:["Read the issue and the code; settle any open question.","Write the acceptance criteria.","Move the issue to fixing.","Build on fix/<issue>-<slug> in its own worktree.","A test that fails today first, on behaviour. (new)","Nothing is pushed and no try runs before the review. (new)","One review of the local commit.","Push the reviewed head once; only then open the pull request. (new)","The operator's Approve; the merge; the delivery, followed in the status view. (new)","Prove the fix live and resolve the same hour.","During a milestone: ride in its delivery group, unless broken for the operator now. (new)"],
prod:"One pull request, one delivery, a resolved issue.",
next:"—",bounds:"About an hour · one review, one push, one check",
prev:["A fix reviewed after its pull request opened","A long part run twice","A fix waiting on someone else's delivery","Ten SDK merges and eight controller merges in a day, one per fix"],
+2 -2
View File
@@ -14,7 +14,7 @@ and a forge address is an operational detail (see [`README`](../README.md)).
| Repository | Owns |
|---|---|
| `hal` | The monorepo — the node runtime, the module catalog, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
| `hal` | The monorepo — the node runtime, the module catalogue, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
| `hq` | This repository, under the company organisation — mission, research, design, decisions, issue diagnosis. Company-scoped (ADR 0019); the mesh is its first product. The source of truth for *why*. Carries no implementation. |
| *(one per application)* | Every standalone application, site or side-project gets its own repository, with `module.yml` at the root. Registered with the mesh as a build source; built and deployed by the same pipeline as anything in the monorepo. |
| `migration` | **Private.** The record of one installation replacing the predecessor mesh with this one: the runbook, a dated log of every step and what it cost, the per-service data procedures, the readiness checks, and the scripts. Private because it is the opposite of this repository in every way that matters — it names machines, addresses, ports and paths, because a procedure that cannot be followed is not one. Where hq asks *what did we decide and why*, that repository answers *what happened on the machines, in what order, and what to do next*. Its `HANDOFF.md` is where somebody picking the work up starts. |
@@ -48,7 +48,7 @@ Named by role, because the layout is itself part of the as-is design — see
| Area | Holds |
|---|---|
| Module catalog | One directory per module, each with a manifest. Core modules sit under the mesh's own namespace; everything else at the top level. |
| Module catalogue | One directory per module, each with a manifest. Core modules sit under the mesh's own namespace; everything else at the top level. |
| Node runtime | The daemon and interactive runtime that every node runs. |
| Bootstrap scripts | First-node initialisation, joining an existing mesh, and node rescue. |
| Shared library | The SDK every module builds against. |
+1 -1
View File
@@ -11,7 +11,7 @@ decisions:
# The mesh as it stands
A set of machines, each running the same runtime, each loading only the parts of the catalog
A set of machines, each running the same runtime, each loading only the parts of the catalogue
it has been assigned. They hold no shared filesystem and make no direct connections to one
another. What makes them a mesh is a database that knows what should run where, and a message
broker that carries everything between them.
@@ -105,9 +105,9 @@ the resource rather than on the node that built the module.
## A documented rule with no enforcement
Every module exposing capabilities is documented as required to declare the mesh's core runtime
as a dependency. **Zero of the catalog's modules do.**
as a dependency. **Zero of the catalogue's modules do.**
This is recorded here rather than quietly corrected, because it is the clearest instance of the
rule this repository states about itself: a rule whose enforcement does not exist is
indistinguishable from a wrong one, and costs more, because people believe it. Whether the rule
or the catalog is wrong has not been decided.
or the catalogue is wrong has not been decided.
@@ -24,7 +24,7 @@ is what makes a node a participant — it is reachable whether or not anyone is
protocol. Its capability surface is larger, because it includes stand-ins for every capability
discovered on peers.
The two are the same code with the same catalog. A capability is written once and is
The two are the same code with the same catalogue. A capability is written once and is
available to both.
## Anatomy naming, and what it obscures
@@ -52,7 +52,7 @@ For each assigned module the runtime, at startup:
4. For a service module, ensures it is installed and running.
Installation is idempotent and does the unglamorous work: ensure the runtime directory,
reconcile the link from the catalog's definition into it, generate the environment, run any
reconcile the link from the catalogue's definition into it, generate the environment, run any
outstanding local migrations, create data directories with the right ownership, then start the
service under supervision.
@@ -85,7 +85,7 @@ import is one-time and not persisted.
Three bootstrap scripts, and which one runs depends on the situation:
- **First node.** Nothing exists yet, so the script stands up the database the rest of the
mesh reads from, publishes the catalog, and starts the mesh. This resolves the
mesh reads from, publishes the catalogue, and starts the mesh. This resolves the
circularity of a mesh whose source of truth is itself a provisioned module.
- **Joining.** The node registers, takes its assignment from the database, and syncs.
- **Rescue.** A node that cannot reach the mesh is brought back far enough to.
+8 -8
View File
@@ -9,9 +9,9 @@ decisions:
- "ADR 0009"
---
# The catalog, and what its shape says
# The catalogue, and what its shape says
The catalog holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
The catalogue holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
ninety-one run *on* the mesh rather than being *of* it
(ADR 0001).
@@ -30,7 +30,7 @@ By namespace, and the namespace records origin rather than purpose:
## What the flat level actually contains
Grouped by what they are *for* — a grouping the catalog itself does not express:
Grouped by what they are *for* — a grouping the catalogue itself does not express:
| Purpose | Roughly |
|---|---|
@@ -51,7 +51,7 @@ connectivity is made four times.
## What the shape records
**The catalog's shape records what was installed, not what anything is for.** One module is
**The catalogue's shape records what was installed, not what anything is for.** One module is
the unit of one piece of software, because that is the only granularity the module system
offers.
@@ -64,7 +64,7 @@ deliberately does not yet settle the domain list.
## Where the shape came from
The catalog's shape is not arbitrary and it is not a series of mistakes. **This repository
The catalogue's shape is not arbitrary and it is not a series of mistakes. **This repository
began as a dotfiles repository**, and most of what looks inexplicable is inherited from that
directly.
@@ -88,7 +88,7 @@ The generalisation from *place files on my machines* to *manage a mesh of nodes*
move and it worked. What it did not do is revisit the assumptions underneath, because they were
never stated as assumptions — they were just how the thing already worked.
**This is the most useful single fact for anyone changing the catalog**, and it is why the
**This is the most useful single fact for anyone changing the catalogue**, and it is why the
linking principle in particular reads as a deliberate architectural choice when it is an
inheritance. See ADR 0012, whose case
this strengthens: the argument for links was never made *for a mesh*.
@@ -109,10 +109,10 @@ module out of the repository safe.
**Placement is already decided.** A standalone application belongs in its own repository
(ADR 0015), and reviewers reject
it in the monorepo. The catalog's flat level is not a dumping ground by policy; it is one by
it in the monorepo. The catalogue's flat level is not a dumping ground by policy; it is one by
history.
## Known inconsistencies in the catalog itself
## Known inconsistencies in the catalogue itself
Recorded because a reader will meet them:
+2 -2
View File
@@ -11,7 +11,7 @@ decisions:
# The seats, as they run
Written from the controller's code and the catalog's manifests on their main branches. This is
Written from the controller's code and the catalogue's manifests on their main branches. This is
the second piece of the new shape to exist, after [the lab](11-the-lab.md), and the first that the
predecessor has no equivalent of: there, any well-formed name became a seat by being claimed.
@@ -24,7 +24,7 @@ have; nothing else can add to it.
Five seats deliver a provision: the mesh's store delivers the relational database, the mesh's broker
the message transport, the artifact-store seat the registry, and two more the package registry for
one ecosystem and the git service. The remaining nine — the controller and catalog seats, and the
one ecosystem and the git service. The remaining nine — the controller and catalogue seats, and the
node-scope ones for the builder, the DNS port, the packet filter, intrusion prevention, the
private network, the resolver's configuration and the showcase — mark a role without answering for
anything a consumer requires.
+3 -3
View File
@@ -21,7 +21,7 @@ decisions:
**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
catalog (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
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. How the tool runner reaches a node, its count of modules and tools and the
controller's verbs were read again on 2026-10-26, after the delivery of release 6 (ADR 0539).
@@ -120,11 +120,11 @@ to-be document still describes it as assigned to "whichever machines an operator
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 catalog exposes.
for it: removing it from a node is not an operation the controller or the catalogue exposes.
## What this repository's prior text got wrong
The document this replaces described `mesh-console` as a catalog module served by a container and
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
+2 -2
View File
@@ -18,7 +18,7 @@ machine and its licence manager; on the day this was written, three buckets exis
## What runs
- **The controller** creates a key-value bucket `<module>_<name>` for every declared state, from the
catalog — on every start and, since the first module that declared state found it missing, on every
catalogue — on every start and, since the first module that declared state found it missing, on every
push before the memberships that name it. A bucket nothing declares any more is reported and kept.
Every bucket carries the mesh's caps: 256 KiB a value, 64 MiB a bucket.
- **The grants**: the machine's runtime is granted, for each bucket a module it carries owns, writing
@@ -46,7 +46,7 @@ machine and its licence manager; on the day this was written, three buckets exis
## How it is checked
The controller's catalog and broker tests (names, grants, memberships, a bucket asserted in place
The controller's catalogue and broker tests (names, grants, memberships, a bucket asserted in place
against a real server); the runtime's tests over a real bus (current values without deletions, refusals,
the TypeScript SDK through the runtime); `module check` names a read whose owner on the shelf keeps no
such state.
@@ -15,7 +15,7 @@ decisions:
**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
catalog's code on main (both local clones were a few commits behind the forge, so each claim below was
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.
@@ -113,4 +113,4 @@ Not here: see [`16-logins-on-the-bus.md`](16-logins-on-the-bus.md).
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 catalog. Their unit tests hold the node record's refusals and the guard's account rule.
guard in the catalogue. Their unit tests hold the node record's refusals and the guard's account rule.
+1 -1
View File
@@ -18,7 +18,7 @@ Where the two disagree, the implementation wins and the disagreement is stated.
| [`07-knowledge.md`](07-knowledge.md) | The mesh keeps no store: what it knows is what modules answer, and the record is read by one |
| [`08-agents-and-work.md`](08-agents-and-work.md) | Agents as employees, tasks, workflows, and the meeting model |
| [`09-interfaces-and-observability.md`](09-interfaces-and-observability.md) | How the mesh is reached and watched — tools, board, proxy, health, thoughts |
| [`10-module-catalogue.md`](10-module-catalogue.md) | The catalog's shape, and what its shape says |
| [`10-module-catalogue.md`](10-module-catalogue.md) | The catalogue's shape, and what its shape says |
| [`11-the-lab.md`](11-the-lab.md) | The lab — the first piece of the new shape that exists, and what it does not yet do |
| [`12-the-seats.md`](12-the-seats.md) | The seats the mesh defines, who holds one, and where a seat changes resolution |
| [`13-the-console.md`](13-the-console.md) | The mesh's tools on the machine a person sits at, served by a module the mesh assigned there |
+8 -8
View File
@@ -431,17 +431,17 @@ It is the same rule the node-engine follows about a service that does not exist
(ADR 0004) — absence must be distinguishable
from a failure to answer — applied to coverage instead of to a machine.
## A bed reads the catalog it proves
## A bed reads the catalogue it proves
*Written 2026-09-21, from resolving [04-ISSUES/073](../../04-ISSUES/073-beds-carry-copies-of-catalogue-manifests/00-report.md);
decided in ADR 0089.*
The rule above — the run rebuilds what it tests — was held for binaries and images and not for
manifests. Beds built the manifests they install inline, as literals copied from the catalog
when each bed was written; the copies did not move when the catalog did, and a catalog change
manifests. Beds built the manifests they install inline, as literals copied from the catalogue
when each bed was written; the copies did not move when the catalogue did, and a catalogue change
was proven by no bed at all, while every bed stayed green against its copy.
**A bed that installs a catalog module reads that module's manifest from the catalog the run
**A bed that installs a catalogue module reads that module's manifest from the catalogue the run
was pointed at.** It rewrites what the lab must — a build artifact becomes the image the machine
holds, an image is pinned, a host port is remapped where one machine carries colliding modules,
an address may point at a stand-in the bed raises — and nothing else.
@@ -451,15 +451,15 @@ server, a secret in the environment, a requirement edge cut so no second provide
that is a mesh test. It does not get a name of its own if it runs the module's runtime — a
module's name is its tool namespace and its broker scope
(ADR 0093) —
so it reads the catalog and installs what the module requires; a name of its own is for a
so it reads the catalogue and installs what the module requires; a name of its own is for a
fixture that runs no real runtime.
**The receipt names the catalog's commit** with the others', so a run taken before a manifest
**The receipt names the catalogue's commit** with the others', so a run taken before a manifest
changed says so — the same rule as for the binaries, for the same reason.
*How it is checked:* a unit test in the lab refuses an inline manifest literal that names a
catalog module unless the bed is declared, with its reason, in the test's own list, and refuses
a declaration for a copy that is gone; the receipt test asserts the catalog is claimed whenever
catalogue module unless the bed is declared, with its reason, in the test's own list, and refuses
a declaration for a copy that is gone; the receipt test asserts the catalogue is claimed whenever
the run is pointed at one.
## Consequences
+1 -1
View File
@@ -31,7 +31,7 @@ offered one driver, everything worked, and snapshots took **seventy-six times lo
needed to. Nothing failed. Nothing warned. A lab in that state runs correctly and is simply too
slow to use — and the inner loop it exists to provide quietly does not exist.
The second is the more dangerous shape and it is a variant the mesh has not cataloged before.
The second is the more dangerous shape and it is a variant the mesh has not catalogued before.
Its usual failure is *reported success and did nothing*. This is **reported success and did it
seventy-six times slower**, which no error surface catches because nothing is wrong.
+1 -1
View File
@@ -177,7 +177,7 @@ raises the bootstrap forge under the forge module's container name, with the mod
its data directory, so the module holds it by the found rule; the network is the one difference a take
has left to say. *How it is checked:* a host test joins a kept network and refuses one it cannot; a
host test keeps a left-out module's record and hold and removes an absent module's; a bootstrap test
holds the installer's constants to the module's manifest where the catalog is checked out beside it.
holds the installer's constants to the module's manifest where the catalogue is checked out beside it.
**What filters the machine, and the found firewall kept retired** — revision, 2026-10-02
(ADR 0168). The node-engine
+1 -1
View File
@@ -326,7 +326,7 @@ shape moves — it names a thing and the node-engine fetches it — and the cont
genesis must raise before the controller can exist. It still raises one, for the store and the broker,
which is where somebody else's software belongs.
The mesh's own components — the node-engine, the controller, the catalog, the builder, the vault — are
The mesh's own components — the node-engine, the controller, the catalogue, the builder, the vault — are
delivered as binaries into directories named for their versions, by the mechanism
ADR 0141 describes. Third-party
software stays a container. The split is not about isolation; it is about who built the thing.
+5 -5
View File
@@ -425,8 +425,8 @@ time.*
A network manager rewrites `/etc/resolv.conf` on every connectivity change unless it is told not to
(ADR 0117), so the module holding
`node-uplink` — the one telling it — writes the file itself, and there is one owner for it, the one
whose program would otherwise overwrite it. Each of the catalog's managers' modules
(NetworkManager, systemd-networkd; dhcpcd's left the catalog with
whose program would otherwise overwrite it. Each of the catalogue's managers' modules
(NetworkManager, systemd-networkd; dhcpcd's left the catalogue with
ADR 0226, held by no machine) renders the same template from the resolver's holders and
requires the mesh's resolver, so a machine is refused when nothing in the mesh resolves rather than
given a file listing nothing. The managers' own mechanisms were weighed and not used: NetworkManager's
@@ -440,7 +440,7 @@ node declaring one path are refused. The module that wrote the file before, its
retire. The file changes owner in one apply on each machine: the node-engine hands a whole file to the
resource declaring its path now rather than removing it first, so a machine is never without it.
*Checked by the controller's tests that the uplink modules carry one identical template and that
nothing else in the catalog writes the path, a resolution test refusing a second writer, and the
nothing else in the catalogue writes the path, a resolution test refusing a second writer, and the
node-engine's handover test, in which the file is present at every step of the apply.*
**A machine's names are one seat's** (ADR 0223).
@@ -643,7 +643,7 @@ to stop working.*
### The name is a label, not a domain
*2026-09-09, from running the whole mesh in the lab.* A route contribution carried its public name
in full — the forge as `git.example.tld`, spelt out in the module. Pointing the same catalog at a
in full — the forge as `git.example.tld`, spelt out in the module. Pointing the same catalogue at a
different domain — a lab standing in for production, or a second operator's mesh — meant rewriting
that name on every routed module. The mesh was holding a **map of names to services**, which is the
one thing it must not: a public name is two facts owned by two different places, and neither is the
@@ -976,7 +976,7 @@ run-once step before it starts. The step names the binding it reads and the prox
step, so when the authority moves the root is fetched again and the proxy is recreated with it
(ADR 0099).
*How it is checked:* the route-forwarding bed installs the authority, the proxy and a consumer
from the catalog and asserts the routed name is served.
from the catalogue and asserts the routed name is served.
**And a machine trusts that authority because a module put its root in its trust store**
(ADR 0147). The proxy's fetch
+10 -10
View File
@@ -57,10 +57,10 @@ produced from there. A module whose path is the repository root is the ordinary
not a separate shape.
*Corrected 2026-09-12. This section previously said the manifest was at the root, full stop, which
made a repository hold exactly one module. Nothing that exists is shaped that way: the catalog
made a repository hold exactly one module. Nothing that exists is shaped that way: the catalogue
holds sixty-seven modules in their own directories, the system being replaced has always built a
module from a repository and a path, and the root-only reading left every existing module
unbuildable — pointed at the catalog the builder finds no manifest, pointed at a module's source
unbuildable — pointed at the catalogue the builder finds no manifest, pointed at a module's source
it finds no manifest either.*
## A manifest is checked where it is written
@@ -84,7 +84,7 @@ read without a store (ADR 0122
keeps the store authoritative, so a claim on a mesh seat is judged fully only at registration, and the
check says that too).
*How it is checked:* the controller's test runs the check over the catalog checkout beside it and
*How it is checked:* the controller's test runs the check over the catalogue checkout beside it and
over a manifest with a known fault, and asserts the first passes and the second names the fault; the
test that used to be the only check, `TestEveryCatalogueManifestParses`, now stands beside a command
anybody can run.
@@ -218,7 +218,7 @@ for, arrived at by an implementation detail.
until it is told otherwise; the mesh changing its mind is not a machine acting on it, and
collapsing the two is how a mesh comes to report success for something that has not happened.
*Checked end to end: a commit, a build, a catalog entry, and a machine that ends up running what
*Checked end to end: a commit, a build, a catalogue entry, and a machine that ends up running what
the source says — with both halves that make the answer trustworthy. It is still running the old
one until it is pushed, and it stops being reported as behind once it has caught up, because a
status that says "behind" for ever is one nobody reads.*
@@ -398,7 +398,7 @@ that distinction once already.*
## The four the loop cannot build
*2026-09-12. Written down because it keeps being rediscovered as if it were new, once per
component. It is one rule; the instances are listed below. Revised the same day, when the catalog
component. It is one rule; the instances are listed below. Revised the same day, when the catalogue
gained an owner and turned out to be a fourth — which is the argument for stating the rule rather
than the list.*
@@ -412,23 +412,23 @@ have a route and one does not:
| The controller | It is what installs modules. Nothing can install it before it runs. | **Carried inside the installer** and published once there is a registry (ADR 0067) |
| The registry | It *is* where artifacts are delivered from. A store cannot be delivered through itself. | **Pulled from the public internet** — its image is an ordinary public one, never built ([`04-ISSUES/029`](../../04-ISSUES/029-the-artifact-store-cannot-be-delivered-by-the-artifact-store/00-report.md)) |
| The builder | It is what builds. Nothing builds it before it runs. | Built at genesis by the init builder (ADR 0070) |
| The catalog | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder (ADR 0070) |
| The catalogue | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder (ADR 0070) |
**How they arrive is settled and not yet built.** The installer carries an init builder, which
clones the source and builds the controller, the catalog and the builder before a mesh exists
clones the source and builds the controller, the catalogue and the builder before a mesh exists
to install anything. Two things about that are open and named in
ADR 0070: where the init builder
clones from, given the forge normally runs on the mesh it would be rebuilding, and what it
publishes into, given the registry is installed later in the order today.
**There is a fourth, and the list was closed too early.** The catalog owns the module graph
**There is a fourth, and the list was closed too early.** The catalogue owns the module graph
(ADR 0070), so it cannot be
installed by something that needs it in order to install anything — the same shape as the other
three. This section previously said the list was closed at three, which was written before the
catalog had an owner and is corrected here rather than left to be reasoned from.
catalogue had an owner and is corrected here rather than left to be reasoned from.
**And the answer for all four is now one mechanism, not four special cases.** Genesis carries an
*init builder* and builds the core modules on the machine — controller, catalog and builder —
*init builder* and builds the core modules on the machine — controller, catalogue and builder —
rather than carrying a finished image of any of them. So the question is no longer "how does this
one get here first?" asked once per component; it is answered once, by the thing that is carried
being a builder rather than a result.
@@ -102,7 +102,7 @@ anything on the machine that can talk to the runtime. So a secret reaches a proc
the file the node-engine wrote and points the program at it, and the mesh's own programs take a `_FILE`
twin for every variable that carries a credential. Software that reads only its environment is
not forbidden; it is **declared**, on the container, with a reason, so the manifest says which
secrets are exposed that way. **Checked** at composition: the catalog engine refuses a
secrets are exposed that way. **Checked** at composition: the catalogue engine refuses a
`${secret:…}` in a container's `env` outright, and a secret-carrying file named in `env-file`
unless the container carries the declaration.
+1 -1
View File
@@ -170,7 +170,7 @@ the one the mesh runs for its own: it offers a place to push, and claims that ro
machine.
**A rule was enforced only at the far end.** A module may not declare an action, and the node-engine
refused one correctly — but the controller accepted it into the catalog, resolved it and
refused one correctly — but the controller accepted it into the catalogue, resolved it and
pushed it, so the refusal arrived on a machine with nothing tying it back to the manifest. The
rule held; it was just unusable, which is the same shape as the network shape that cost five
failing tests before anyone read the node-engine's log. It is now refused where it is written.
+6 -6
View File
@@ -132,7 +132,7 @@ adopted as modules, and the machine's service is still reachable.
Genesis ends with a mesh of one that runs, and that is not the same as a mesh that works. What it
has is a controller, a store, a queue and a registry. What it cannot yet do is **produce
anything** — and almost every module in the catalog is waiting to be produced, because a manifest
anything** — and almost every module in the catalogue is waiting to be produced, because a manifest
names what its artifacts are and nothing has made them.
So installing continues:
@@ -172,7 +172,7 @@ need no registry. The gitea module's own server is an upstream image, so buildin
**The store genesis raises is the `postgres` module's server, so adopting it keeps it** (ADR 0338, the
store's twin of the bus's ADR 0333). The bundle raises the store under the name, on the image, in the data
directory and with the superuser password file that the catalog's `postgres` module declares, and every step
directory and with the superuser password file that the catalogue's `postgres` module declares, and every step
of the bundle and of the installer reaches it by that name. At the store step the module declares the
container the bundle raised, so the store is adopted where it runs and is not restarted. The module does not
change, so no running mesh's store moves. *How it is checked:* a test reads the module's manifest and holds
@@ -190,7 +190,7 @@ something it made rather than by a placeholder.
mesh running; building it from its own repository and path is what makes it upgradeable. The first
time that happens is the moment the mesh stops depending on the installer for anything.
**And then the catalog.** Every module with source of its own is built the same way. Until this
**And then the catalogue.** Every module with source of its own is built the same way. Until this
has happened a mesh can install only what is public or carried, which is the foundation and little
else.
@@ -233,7 +233,7 @@ was brought up the supported way, so the rule that it must be is, today, unenfor
**Nothing asks for the core modules yet.** Installing ends with a builder that works — a raised
machine will build the shared base and a module on top of it when asked — and nothing asks. The
paragraphs above describing the catalog being built are a thing somebody now types, rather than a
paragraphs above describing the catalogue being built are a thing somebody now types, rather than a
thing that cannot happen.
**A module's declaration still has to be copied onto the machine by hand.** The installer reads the
@@ -242,7 +242,7 @@ longer among them: it lives in the controller's own repository
(ADR 0069), the build the
installer runs reports it with the artifact resolved, and genesis registers that
([issue 072](../../04-ISSUES/072-the-controllers-manifest-exists-twice/00-report.md)). The
builder's manifest belongs in the same repository and is still read from the catalog; that is
builder's manifest belongs in the same repository and is still read from the catalogue; that is
the last of this.
**A machine has no account for a registry that asks for one.** The mesh grants a consumer a
@@ -275,7 +275,7 @@ issue tracker holds it as issue 602.
| An image is named exactly | A machine refuses a bundle naming an image by tag. The refusal is exercised, not assumed. |
| The installer is what installed this | **Nothing.** See above. |
| The builder can arrive on a fresh mesh | The installer carries it and installs it as its last step, and the genesis bed raises a machine by running the installer. A bed that raises one any other way fails its own acceptance check. |
| The builder is assigned before the package registry exists | The build-agent manifest's own test holds the requirement optional and the genesis document declared and issued outside the mesh; the controller's resolution test over the real catalog assigns the builder with no provider, lists the requirement unbound, and binds it once gitea is offered. The builder's tests refuse a package, an `.npmrc` image and an `@novox/` TypeScript build with neither binding nor genesis document, before anything is built, and build a Go artifact; they read the genesis document only without a binding. The installer's test seals the document at the packages step and gives the builder no `serves` setting and no `npm-password`. **Not yet proved live:** a genesis bed from bare passing the builder, packages, sdk and base steps. |
| The builder is assigned before the package registry exists | The build-agent manifest's own test holds the requirement optional and the genesis document declared and issued outside the mesh; the controller's resolution test over the real catalogue assigns the builder with no provider, lists the requirement unbound, and binds it once gitea is offered. The builder's tests refuse a package, an `.npmrc` image and an `@novox/` TypeScript build with neither binding nor genesis document, before anything is built, and build a Go artifact; they read the genesis document only without a binding. The installer's test seals the document at the packages step and gives the builder no `serves` setting and no `npm-password`. **Not yet proved live:** a genesis bed from bare passing the builder, packages, sdk and base steps. |
| The controller a mesh runs is one it built | The genesis bed asserts the running controller is pinned to a digest this mesh's own registry serves, for an image built from a named repository and commit — not one the installer carried. |
| A core module is built rather than only carried | The controller is rebuilt from its own repository and path, and the running mesh is upgraded to the result — the same path any module takes. |
| Installing produced a mesh that can produce | A module with source of its own is asked for and comes back pinned to a digest this mesh's registry assigned, not to a placeholder. **Done by hand on a raised machine, not yet by a bed** — it built the shared base and then a module naming that base. Nothing automated asserts it, which makes this the weakest check on this page. |
+9 -9
View File
@@ -43,7 +43,7 @@ Turning a module's source into artifacts the mesh can pin, publish and deliver
truthfully what was produced and what it was produced against.
Everything else is somebody else's: *what* to build is the controller's, *what a build means* is
the catalog's (ADR 0072), *where an artifact runs* is the
the catalogue's (ADR 0072), *where an artifact runs* is the
controller's again. The builder's whole responsibility is the middle.
## The language
@@ -83,7 +83,7 @@ output into a second stage; and sets an environment variable naming the compiled
module repeats it. Miss any step and the failure arrives somewhere else — as a crash loop, a
placeholder digest, a module that builds and does nothing.
The evidence that this is too hard is in the catalog: **most modules are not converted**, and the
The evidence that this is too hard is in the catalogue: **most modules are not converted**, and the
two converted during one session were each wrong twice before they were right, against a working
example sitting open in the next window.
@@ -163,8 +163,8 @@ makes the drift worse while hiding it.
| A module with its own code needs no Dockerfile | A module declaring only a language builds, and its artifact is pinned to a digest the mesh's registry assigned. |
| An archive is produced and delivered | A module declaring an archive is built, and the machine assigned it has the unpacked files — verified on the machine, not in the build's own output. |
| A Dockerfile still works | A module that declares one builds exactly as before, because software that needs a particular base has not stopped existing. |
| Nothing is announced that was not made | A build made to fail announces nothing, and the catalog's graph is unchanged afterwards. |
| A build keeps what it was told | A catalog started after a build asks for what it missed and receives the manifest and the artifacts stood on, not a summary. |
| Nothing is announced that was not made | A build made to fail announces nothing, and the catalogue's graph is unchanged afterwards. |
| A build keeps what it was told | A catalogue started after a build asks for what it missed and receives the manifest and the artifacts stood on, not a summary. |
| The toolchain is the mesh's, not the author's | Changing the toolchain makes every module built against it stale, and each names what moved. |
## The whole surface, in one place
@@ -207,7 +207,7 @@ declared in one of three ways, for the three things a path can be: the module's
or file resource, or where a secret, grant or contribution lands), the operator's (an `accesses`
entry), or the machine's (a facility a declared capability grants — `container-runtime` grants its
socket). *How it is checked:* the parser refuses an undeclared mount naming the path and the three
remedies, and a test parses every manifest in the catalog beside the checkout.
remedies, and a test parses every manifest in the catalogue beside the checkout.
### What it builds
@@ -247,7 +247,7 @@ counts as a copy and what as a base.
| `directory` | a directory with a mode and an owner, **placed by the mesh** under the node's root: `place: "."` is the assignment's own root, `place: "mesh"` the mesh's directory for the module, a pathless one sits beneath the root by its id; a stated path is the placement for data that must stay where it is, and may itself sit beneath a placed one (`${dir:<id>}/…`). Everything else names it as `${dir:<id>}` ([issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md), [174](../../04-ISSUES/174-the-meshs-own-files-for-a-module-are-placed-by-the-definition/00-report.md)) | ✅ |
| `file` | literal content, with `${bound:…}`, `${secret:…}`, `${dir:…}`, `${port:…}`, `${machine:…}` and `${setting:…}` filled in — the last an operator's value from the assignment's settings, refused by name when unset (ADR 0155) unless the manifest gives a preference a default (ADR 0262) | ✅ |
| `user` | a login | ✅ |
| `access` | a pre-existing path it may use and must not own, **named by id** and placed by the assignment (`accesses: {<id>: <path>}` on its settings); mounts say `${access:<id>}`; a path in the definition is the default an assignment replaces, tolerated while the catalog converts ([issue 153](../../04-ISSUES/153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md)) | ✅ |
| `access` | a pre-existing path it may use and must not own, **named by id** and placed by the assignment (`accesses: {<id>: <path>}` on its settings); mounts say `${access:<id>}`; a path in the definition is the default an assignment replaces, tolerated while the catalogue converts ([issue 153](../../04-ISSUES/153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md)) | ✅ |
| `archive` | files fetched by digest and unpacked | ✅ |
| `package` | a package that must be present | ✅ |
| `network` | a named container network | ✅ |
@@ -292,7 +292,7 @@ This is the bus's shared-work pattern, not a build-specific one: any module decl
`accepts` has its work shared by its holders the same way. Building is the first use.
*Built and proven live 2026-10-03.* `build-agent` holds `node-build-agent` on all four machines; the first
build taken by a workstation's agent was a catalog module at 09:35 UTC; the one-holder `builder` is
build taken by a workstation's agent was a catalogue module at 09:35 UTC; the one-holder `builder` is
retired. The switch found five gaps, each an issue: a worker whose type changed stranded its holder
([206](../../04-ISSUES/206-a-seats-worker-changing-type-strands-the-holder-and-the-build-that-would-fix-it/00-report.md)),
a re-made worker replayed the stream's history ([207](../../04-ISSUES/207-a-re-made-worker-replayed-every-ask-the-stream-kept/00-report.md)),
@@ -316,7 +316,7 @@ controller's seat verb through the mesh MCP server — reads it back afterwards.
id beside it, and `build` says the id it asked with. The mesh keeps no second copy: the stream is the
log. The mesh MCP server's `build` tool asks and answers at once with the id; the outcome is taken in — the
build recorded, the module registered with its source — by whoever hears it, the waiting command or
the daemon following the role's event, so a build nobody waited for still reaches the catalog
the daemon following the role's event, so a build nobody waited for still reaches the catalogue
([issue 176](../../04-ISSUES/176-the-consoles-build-tool-neither-waits-nor-registers/00-report.md)). A viewer of builds, when one is built, is a subscriber over these subjects and the outcome; the
builder needs nothing more for it.
@@ -383,7 +383,7 @@ than retried for ever. Live: the store's size before and after the first nightly
*2026-09-29 —
ADR 0142.*
The toolchain list was typescript and python, and only typescript had a base module in the catalog.
The toolchain list was typescript and python, and only typescript had a base module in the catalogue.
Meanwhile the controller — written in the language this project is mostly written in — was built as
an image from a hand-written Dockerfile, which is the per-repository incantation this whole mechanism
exists to abolish.
+1 -1
View File
@@ -171,7 +171,7 @@ knows. Putting the Plex client anywhere else is the shared-library disease with
- **Which private registry.** That there *is* one is settled —
ADR 0014 says each module consumes its dependencies
from the private registry, the mesh's own shared library included. Which software serves it is
not: the catalog holds `verdaccio`, and a git host usually serves package registries too.
not: the catalogue holds `verdaccio`, and a git host usually serves package registries too.
- **And the bootstrap does not have one.** ADR 0014 assumes a registry exists; on a fresh mesh
nothing has installed one when the first SDK is built. That is the same pivot as everything else
and it has not been designed.
@@ -65,14 +65,14 @@ and waits — which looks exactly like a builder with no work.
**Genesis ends with a mesh that runs, which is not the same as a mesh that works.** It has a controller, a store, a broker, a registry and a builder. It holds no module graph, has no private
network, no packet filter, and cannot resolve a name. Calling that "installed" is what let the
catalog be missing from a test for weeks without anything complaining.
catalogue be missing from a test for weeks without anything complaining.
| # | step | what happens | why it is here |
|---|---|---|---|
| 13 | the shared base is built | the toolchain and runtime every module with code of its own stands on | nothing else with code can be built until it exists |
| 14 | a store module is built and run | a database **provider**, which the foundation's store is not | the foundation's store is the controller's own memory, and offers nothing to anything |
| 15 | the catalog is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt |
| 16 | the catalog asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalog itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) |
| 15 | the catalogue is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt |
| 16 | the catalogue asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalogue itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) |
| 17 | the controller is rebuilt from its own repository | and rolled out through the module path | the moment the mesh stops depending on the installer for anything |
| 18 | `mesh-wireguard` is assigned **and the node placed** | a private network | assigning installs the module; placing says where this machine is on it. Both, or the machine has no peers. Assigned by its own name since ADR 0226 retired the `networking` bundle |
| 19 | the packet filter is assigned | rules generated from what modules declared | until this, every rule the mesh computes has never been applied to anything |
+2 -2
View File
@@ -44,7 +44,7 @@ after the phases that change its build path are in — not before.
**Why here.** The Go controller and the TypeScript SDK disagree about what a grant carries
(`consumer` is the module in one, the node in the other). That is exercised by the installer's own
provisioning — the catalog's database — so it belongs before more is built on it.
provisioning — the catalogue's database — so it belongs before more is built on it.
- [x] 1.1 the drift was not live — inspection showed the wire agrees (contributions file; envelope
required headers). The dead types that disagreed are removed, ADR 0074 and doc 19 corrected
@@ -91,7 +91,7 @@ a protocol that agrees, a registry to publish to. Issue 051.
the controller's records and the database of each module that asks for one. *(Adopted in place: the module's
server names the same container and the same pinned upstream image the foundation runs, so the
applier reconciles it rather than raising a second postgres. Proven 22/22 in the one-node lab —
the catalog, a `postgres-database` consumer, gets its database from it. Follow-up: the store
the catalogue, a `postgres-database` consumer, gets its database from it. Follow-up: the store
binds `0.0.0.0` from genesis so a mesh consumer can reach it, but the packet filter is
installed later — a brief pre-filter window where `mesh-store` is open before `from: mesh`
clamps it; bring the filter up earlier or bind narrower at genesis.)*
+1 -1
View File
@@ -11,7 +11,7 @@ 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.
**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):
+2 -2
View File
@@ -185,7 +185,7 @@ recovery addresses both alike.
### A provider with one credential
*Decided 2026-10-01 (ADR 0158); the controller's half built the same day (mesh-controller PR 184): the offer's word, the need carrying the shared secret's name, the vault's one value under one generation stamp, remade for every holder on a later binding or a rotation, the rotate command sending every holder. What remains is each provider's definition saying `credential` and `taken`, with a start that applies the file — the media catalog's work.*
*Decided 2026-10-01 (ADR 0158); the controller's half built the same day (mesh-controller PR 184): the offer's word, the need carrying the shared secret's name, the vault's one value under one generation stamp, remade for every holder on a later binding or a rotation, the rotate command sending every holder. What remains is each provider's definition saying `credential` and `taken`, with a start that applies the file — the media catalogue's work.*
Software that holds one credential — a download client's web password, an indexer's one API key —
cannot give each consumer a login, so ADR 0048's form does not fit it and its values were accepted
@@ -202,7 +202,7 @@ acceptance. *How it is checked:* the rows of ADR 0158's table; the controller's
### A credential the provider's software issued
*Decided 2026-10-12 (ADR 0305, issue 526), accepted on the operator's approval the same day; built on
branches in the controller and the media catalog.*
branches in the controller and the media catalogue.*
Some software is given its one credential rather than told it: a media server signs in to its vendor and
keeps the token the vendor issued. The mesh can neither make nor rotate such a value, and a copy typed for
+5 -5
View File
@@ -129,7 +129,7 @@ mesh already has a shape for that — a seat's `accept` subjects, on a work queu
holders, which is what a build queue shared by several machines *is*. Keeping a second mechanism for it
meant two things to reason about and two places for a permission to be wrong. A build's outcome is the
seat's own event — which is why the control branch loses its copy too: one publish reaches whoever
asked, the controller that records it and the catalog that places it — the fan-out a shared exchange gave for free, as a derived subject rather than a
asked, the controller that records it and the catalogue that places it — the fan-out a shared exchange gave for free, as a derived subject rather than a
configured topology.
**Revised 2026-09-26** ([design 29](32-what-a-module-declares.md)): a module's events and tools
@@ -179,7 +179,7 @@ Core NATS is at-most-once. Everything the mesh must not lose lives in a JetStrea
| EVENTS | `mesh.mod.*.event.>` and `mesh.seat.*.event.>`; and `mesh.again.>` (ADR 0264) | limits (age, size), durable consumer per subscribing module | a subscriber that was down catches up; after `max-deliver` attempts the advisory feeds `mesh.events.dead` (its own small stream). *2026-10-08, ADR 0264:* that stream is DEAD_LETTERS, below; each consumer also filters `mesh.again.<consumer>.>`, where a message it gave up on is delivered again to it alone. *2026-10-01:* a build's whole log is here too, as the build seat's `log.<build id>` events (ADR 0157) — one subject per build, a week of retention, read back by `builds --log <id>` with a consumer that is gone when the reading is done |
| DEAD_LETTER_NOTICES | the server's maximum-deliveries advisory | work queue, a week; one consumer (the controller), no `max-deliver` | the server's word that a consumer gave up on a message, kept whether or not a controller is listening, until the controller has copied the message (ADR 0264) |
| DEAD_LETTERS | `mesh.events.dead.<stream>.<consumer>` | limits: no age, 256 MiB, refuses new when full; a day's de-duplication | every message a consumer gave up on, with its consumer, subject, deliveries and when, until a person delivers it again or drops it with why (ADR 0264); a `max-deliveries` condition is open per consumer while it holds any |
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | the newest value per key — as many past values as the owner declared — no age unless the owner declared one; a value at most 256 KiB, a bucket at most 64 MiB | a module's state (ADR 0201): one per name in a manifest's `state`, created from the catalog on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data |
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | the newest value per key — as many past values as the owner declared — no age unless the owner declared one; a value at most 256 KiB, a bucket at most 64 MiB | a module's state (ADR 0201): one per name in a manifest's `state`, created from the catalogue on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data |
Tool calls and heartbeats stay on core NATS: a lost heartbeat is the next heartbeat; a lost tool
call is a timeout the caller already handles.
@@ -321,7 +321,7 @@ signing hierarchy for nothing.
## 5. The broker as a module
`nats` is a catalog module claiming the seat `mesh-broker`
`nats` is a catalogue module claiming the seat `mesh-broker`
(ADR 0079: the seat
is the server, and the server changes). It declares one container (a single binary; **JetStream on a host
directory bind, not a named volume** — revision, second review:
@@ -410,7 +410,7 @@ bridged. It is three things:
for anyone else — and nothing on control, nodes or builds. It is issued, sealed to the person's
own key, and revoked, like a module's.
2. **A client that speaks the bus**: a small program on the workstation that connects as that user
over TLS, lists tools by asking the catalog (`mesh.tools.mesh-catalog.catalog_tools`), and
over TLS, lists tools by asking the catalogue (`mesh.tools.mesh-catalog.catalog_tools`), and
turns each tool into a call — as an MCP server for an agent, and as a command line for a person.
It uses the sdk's `Broker` contract on the NATS runtime, so it is the same code path a module's
tools use, not a second protocol.
@@ -606,7 +606,7 @@ find what changed and why.
decides it.
- The heartbeat interval and the controller's "quiet" threshold on core NATS without persistence —
the same numbers as today are proposed.
- Whether the person's client is a catalog module (runs on an enrolled workstation node) or a
- Whether the person's client is a catalogue module (runs on an enrolled workstation node) or a
standalone program (runs anywhere with credentials). Both, in that order, is proposed.
- Leaf nodes: a NATS leaf per machine would make every module's connection local and survive the
hub's restart. Deliberately out of scope; noted so it is not forgotten.
+7 -7
View File
@@ -98,7 +98,7 @@ nobody argued for is an entry nobody can explain.
**What deserves one** (ADR 0161). A provision the
mesh's own code dereferences by name is delivered by a mesh seat its provider claims — the store, the
bus, the vault, the artifact store, the catalog. A provision a module merely offers may have several
bus, the vault, the artifact store, the catalogue. A provision a module merely offers may have several
providers, and a consumer with several is a person's choice. A fact of the shape *exactly one machine
is X* — the hub — is not a seat, because a seat is held by a module assignment and points at it; it is
a placement with a capacity of one, kept by the store, refused by name when a second is placed, and
@@ -114,7 +114,7 @@ capability is.
ADR 0110): a module
declares its own seats with their protocols, so the seats a mesh has are the mesh's own **plus
every registered module's**. The set is still closed — a seat named nowhere is refused — but it is
computed from the catalog rather than maintained by hand, which is the property 0110 actually
computed from the catalogue rather than maintained by hand, which is the property 0110 actually
needed and the table could not keep.
**And the mesh's own half is data, named for its scope.** Revision, 2026-09-27, reconciling two
@@ -125,7 +125,7 @@ the set out of compiled code into a table the controller owns, so a rename is on
rebuild of everything that names one.
So the set has two halves and neither is written out here: the mesh's own, which the controller holds
as rows, and every registered module's, which is computed from the catalog. What this document keeps
as rows, and every registered module's, which is computed from the catalogue. What this document keeps
is what a seat *is* — the rest would be a third copy, stale the first time somebody renamed one, which
is the fault ADR 0122 exists about.
@@ -142,7 +142,7 @@ convention, which later seats departed from.
| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus |
| `mesh-vault` | — | mesh | `secret` | the vault (in the seed since ADR 0161; the earlier *reserved* named an effect no rule produced) |
| `mesh-artifact-store` | `the-artifact-store` (renamed 2026-09-30, ADR 0156) | mesh | `artifact-store` | the artifact registry |
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalog |
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue |
| `mesh-npm-package-registry` | `npm-package-registry` | mesh | `npm-package-registry` | the forge |
| `mesh-git` | `git` | mesh | `git` | the forge |
| `mesh-build-machine` | `the-build-machine` | node | — | a builder |
@@ -158,7 +158,7 @@ convention, which later seats departed from.
The controller holds **the mesh's own** entries in code, and a test asserts their size and that
every one names the record that made it a seat. A module's seats are not here and never will be —
they are read from the catalog. **This table and
they are read from the catalogue. **This table and
ADR 0126 govern, and code that
disagrees is what is wrong.** The implementation in progress predates several
things here: seats held by assignments rather than claimed by definitions, the `mesh-vault` seat and
@@ -326,7 +326,7 @@ checked as their tables say:
| Rule | Checked by |
|---|---|
| The set is closed, and every mesh entry names its decision | 0118: a unit test on the mesh's own entries. **The refusal of an unknown seat is at registration, not in the parser** — correction of fact, 2026-09-27: a module may hold a seat *another* module declares, which is the point of naming the seat and not its provider, so whether a claimed name exists is a fact about the whole catalog and a manifest in isolation cannot be judged on it. Registration tests cover an invented name and a name another module declares; the parser still refuses a claim on the mesh's own `mesh-*`/`node-*` namespace and a scope that disagrees with a declaration in the same manifest. |
| The set is closed, and every mesh entry names its decision | 0118: a unit test on the mesh's own entries. **The refusal of an unknown seat is at registration, not in the parser** — correction of fact, 2026-09-27: a module may hold a seat *another* module declares, which is the point of naming the seat and not its provider, so whether a claimed name exists is a fact about the whole catalogue and a manifest in isolation cannot be judged on it. Registration tests cover an invented name and a name another module declares; the parser still refuses a claim on the mesh's own `mesh-*`/`node-*` namespace and a scope that disagrees with a declaration in the same manifest. |
| The set is derived, and enumerating it is a query | 0118: the overview lists the mesh's own plus every registered module's, asserted against a fixture mesh. |
| `mesh-*` is the mesh's, and a module may not declare one | 0118: a registration test refusing a manifest that declares any `mesh-*` seat, naming the prefix. |
| Two modules cannot declare the same seat | 0118: a registration test; the second is refused and the first untouched. |
@@ -339,7 +339,7 @@ checked as their tables say:
| `secret` has one provider, the holder of `mesh-vault` | 0161: a second claimant of the seat is refused by name (`CanHold`); *correction of fact, 2026-10-01: no parser rule ever reserved the word, the seat does the work*. |
| A singular fact about machines is a placement of capacity one, refused by name | 0161: the private network command's test for a second hub; the store's unique index. |
| A holder of `node-uplink` is the dialect the machine runs | 0161: the node-engine reports `uplink-<manager>` in its profile with every report; a resolution test refuses the other holder naming the capability. |
| A seat's holder has the seats it needs beside it: the resolver configuration is refused at `assign` without the uplink held on its node, and the uplink's last holder cannot be taken from beneath it | 0220: seat-dependency tests on the definition, on a synthetic claimant, at assign, at unassign and at composition, and one reading the catalog for the uplink's possible holders. |
| A seat's holder has the seats it needs beside it: the resolver configuration is refused at `assign` without the uplink held on its node, and the uplink's last holder cannot be taken from beneath it | 0220: seat-dependency tests on the definition, on a synthetic claimant, at assign, at unassign and at composition, and one reading the catalogue for the uplink's possible holders. |
| A replicated seat has every holder on record and composes on each; a seat held once still refuses a second holder, on record or not; `--add` is refused for it | 0223: resolution and composition tests with two holders on record and a third machine, with two claimants and nothing on record, and on `mesh-store`; a unit test that only `mesh-dns-resolver` is replicated, surviving the store's rows; store tests keeping several holders once each, a handover leaving one, and unassigning one taking only its row. |
| A retired seat leaves the set once nothing claims it, from the compiled set and the store's table both | 0220: the closed-set test's count, and the store migration that deletes the row. |
| A node seat names only literal absolute paths; every path has one seat naming it outright, and stepping back does not cycle | 0287: a unit test over the compiled set; registration tests refusing a module's seat naming a path at mesh scope, with `${`, or already named by another seat. |
@@ -47,7 +47,7 @@ A requirement has three parts:
**A contract is shared, not per module.** A database's contract is the database's, whoever requires
it. **The controller holds every contract**, one per provision name, declared where the provision is
defined in the catalog. Today contracts are implicit in each provider's served fields; the first
defined in the catalogue. Today contracts are implicit in each provider's served fields; the first
phase below makes them explicit, because nothing can be checked against a contract that is not
written down. A provider is checked against the contract it claims to answer. A module's own
specification may narrow a contract (a password of at least this length, a directory owned by this
@@ -213,13 +213,13 @@ a placed directory under the node's root. Each is this design's provider in the
placeholders have, not yet the one requirement form below; they are phase 1's first cases.
*Phase 3, in part (2026-09-30):* every definition's **own** data directory is placed; the conversion
moved no data, proven by resolving both catalogs with the controller's rule and comparing
moved no data, proven by resolving both catalogues with the controller's rule and comparing
([issue 119](../../04-ISSUES/119-a-module-definition-decides-where-its-files-live/00-report.md)).
What the mesh writes *for* a module was still placed by the definition
([issue 174](../../04-ISSUES/174-the-meshs-own-files-for-a-module-are-placed-by-the-definition/00-report.md)),
the gap this design answered on 2026-09-26; built later the same day: a directory saying
`place: "mesh"` is `<root>/mesh/<module>`, a directory beneath a placed one states its path as
`${dir:<id>}/<rest>` and moves with it, and the same proof — both catalogs resolved and compared —
`${dir:<id>}/<rest>` and moves with it, and the same proof — both catalogues resolved and compared —
shows forty-eight definitions naming the paths they named before.
*A preference has a default; the operator's own value has none (2026-10-08,
@@ -323,7 +323,7 @@ value in a container's environment is refused when the definition is parsed, wit
containers, login, broker account and settings are keyed by it, as today, and a login still fits the
backend that keeps it (ADR 0049):
the bound is the one the offer of each provision it requires states, a keyless provision states none,
and an overflow is refused by the catalog check before merge and left out of the provider's grants,
and an overflow is refused by the catalogue check before merge and left out of the provider's grants,
reported, at composition — never a refusal of the provider's machine
(ADR 0225).
@@ -371,7 +371,7 @@ not rotated by the vault, which cannot make its replacement: an operator deliver
**Each requirement says how its recipient takes a new value.** It either *applies* it, through a
provisioner (a provider setting a login's password, the broker's provisioner updating an account, a
store's provisioner changing its own superuser), or *reads it at start*. Every module in the catalog
store's provisioner changing its own superuser), or *reads it at start*. Every module in the catalogue
reads its secrets at start, and none watches them. The node-engine already recreates a container when a file
it read at creation changes, its env-files and files mounted into it directly
([issue 103](../../04-ISSUES/103-a-container-is-not-recreated-when-a-file-it-reads-changes/00-report.md)).
@@ -433,7 +433,7 @@ as waiting, and nothing is delivered until the answer arrives.
| paths in resources, mounts, bindings, secrets and received files | host directory requirements, placed by the assignment |
| literals carried in a definition | operator requirements with defaults |
Each is retired only once nothing uses it. Until then both are accepted, and a catalog test lists
Each is retired only once nothing uses it. Until then both are accepted, and a catalogue test lists
the definitions still using the old form. That list shrinks to empty, and then the old form is
removed from the parser.
@@ -453,7 +453,7 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
is fixed in the node-engine already. *Ends when* nothing outside the vault generates a shared secret after
genesis, a lab consumer of analytics receives its site id, and a database credential rotates over
its two credentials, the consumer recreated by derivation, never without a working login, and its data intact.
3. **Definitions move, and seats move to assignments.** Every catalog definition is rewritten, adopted
3. **Definitions move, and seats move to assignments.** Every catalogue definition is rewritten, adopted
and running assignments placed where their data already is, and each claim becomes a seat the
module can hold, held by the assignment that holds it today. *Ends when* the list of definitions
using an old form is empty, the old forms are removed, and the store module runs on two lab
@@ -464,7 +464,7 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
| Rule | Checked by |
|---|---|
| Every requirement has one of the four provider kinds | The parser refuses any other. |
| A definition names no host path, node or mesh | The catalog tests of ADR 0112. |
| A definition names no host path, node or mesh | The catalogue tests of ADR 0112. |
| A module provider is chosen by named seat, pin, co-location, only one, a person's choice | Resolution tests for each step: a requirement naming a seat served by its holder even with another provider on the consumer's node, and refused when the seat is unheld; several candidates and none local, where assignment lists them with the seat's holder first and records the choice as a pin, and refuses without one. |
| A provider answers within its contract | A controller test: an answer carrying a field its contract does not name, or missing one it does, is refused and not delivered. |
| A module narrows a contract and never widens it | The parser refuses a module specification that loosens a contract's field. |
@@ -484,9 +484,9 @@ Each phase ends at a check that holds, so none of them leaves a mechanism half-r
| The controller and a node's host take the same path | 0113's tests: the controller's definition declares requirements and no own secret; a node's bus account is made by the vault and delivered sealed to that node. |
| Moving the vault or the broker is break-glass | A resolution test: an ordinary assignment moving `mesh-vault` or `mesh-broker` is refused, naming the procedure. |
| A secret that cannot be rotated says so | A vault test: rotating a secret marked not rotatable by the mesh is refused, naming why. |
| Only the controller reads the seat placeholder | A catalog test, from phase 3: no definition uses the seat placeholder. |
| Only the controller reads the seat placeholder | A catalogue test, from phase 3: no definition uses the seat placeholder. |
| Refusal names everything at once | A resolution test with three unresolved requirements of different kinds: one refusal naming all three. |
| The old forms retire | The catalog test listing definitions still using one. It must be empty before a form is removed. |
| The old forms retire | The catalogue test listing definitions still using one. It must be empty before a form is removed. |
## Not settled here
+14 -14
View File
@@ -169,11 +169,11 @@ paper is wrong until there is a second mesh to find out.
The derivation refuses two things where they can still be named: two users with one name — the
server reads the file as one of them and which one depends on the order — and a module
assigned but absent from the catalog, which would compose a user with no authority and fail
assigned but absent from the catalogue, which would compose a user with no authority and fail
on its first publish with an authorisation error that says nothing about a missing manifest.
A user the mesh has minted no password for is *named* rather than dropped or written as a user
anybody is: an ordinary situation with an obvious remedy, and the caller decides whether a
partial file is worth writing. A seat's protocol is gathered across the whole catalog, not
partial file is worth writing. A seat's protocol is gathered across the whole catalogue, not
from one manifest, because a seat is declared by one module and held by another.
**Out, and what each needs.**
@@ -447,7 +447,7 @@ pays for itself furthest away.
derived to subjects, the three namespaces, permissions computed from a declaration, and a
manifest that contains no subject. Done in the controller's composer (permissions, streams,
consumers), in the runtime's client (subjects derived from the credential, never named by a
module), and as a catalog test asserting all 72 manifests hold no subject — because the
module), and as a catalogue test asserting all 72 manifests hold no subject — because the
rule held by construction, and a rule held by construction is one a later field breaks
quietly.
- [x] 3.9 **seats declared by modules** — the manifest now carries `seats` (name, scope,
@@ -470,7 +470,7 @@ pays for itself furthest away.
all: a holding is derived at resolution, never stored, so nothing recorded points at an old
name (recorded as a progressive insight on ADR 0126). A **kept** rename table tells a
manifest written against an old name what it became, because a module lives in its own
repository and may be registered long after the catalog stopped using one.
repository and may be registered long after the catalogue stopped using one.
**A seat and the interface it delivers are different names.** The `git` seat became
`mesh-git` while the `git` *provision* it delivers did not change, and the same for the
@@ -522,7 +522,7 @@ it, and the beds that need a mesh living on NATS can finally run.
(ADR 0129). Both sides
are behind a seam with an implementation per bus, and on the bus being built one publish does
what two did: the outcome is the role's own event, so the asker matches it by the id its
request carried, the controller records it and the catalog places it in the graph. A builder needs a reply queue for nothing and a grant over nobody's inbox.
request carried, the controller records it and the catalogue places it in the graph. A builder needs a reply queue for nothing and a grant over nobody's inbox.
Checked against a running server: the round trip; a third party on the role's event hearing
the same outcome the asker did, which is what the decision rests on; work leaving the queue
@@ -532,7 +532,7 @@ it, and the beds that need a mesh living on NATS can finally run.
The outcome carries the module name, because only the manifest says what was built and one
message now has three readers. A failed build names none: it produced no module version, and
the catalog would otherwise place something that was never made.
the catalogue would otherwise place something that was never made.
- [x] 4.3 an installation completes over the bus, with the same outcome as the path it replaces —
**the installer can raise it**: a foundation template that stands up the server, writes the
server's own settings and the mesh's first user list beside them, and starts a controller
@@ -573,7 +573,7 @@ it, and the beds that need a mesh living on NATS can finally run.
correct. It uses the client a module's runtime uses, so what a person may do is answered by the
same permission list that answers it for a module and an audit has nothing separate to read.
Three decisions in it worth keeping. It lists what the **catalog** has rather than what this
Three decisions in it worth keeping. It lists what the **catalogue** has rather than what this
credential may call: somebody seeing only their own tools cannot tell "not installed" from "not
yours", and those need different people to fix them. A failed call says which of three things
happened — nobody serves it, this credential may not, or the tool was slow — because the
@@ -597,10 +597,10 @@ it, and the beds that need a mesh living on NATS can finally run.
**The catch-up half needed nothing built, and that was the answer.** It existed because a queue on
the bus the mesh runs on today receives only what is published after it is bound, so everything
built before the catalog existed was announced to nobody — and on a fresh mesh that is always
the foundation, because those are the things the catalog needed in order to exist
built before the catalogue existed was announced to nobody — and on a fresh mesh that is always
the foundation, because those are the things the catalogue needed in order to exist
([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)). A
whole mechanism followed: the catalog asks, the controller re-publishes.
whole mechanism followed: the catalogue asks, the controller re-publishes.
A stream is a log and a consumer is a position in it. A consumer created later starts at the
beginning, so the builds are simply there — asked of a running server rather than assumed, since
@@ -636,8 +636,8 @@ left to be re-derived under time pressure:
| 1 | this one | prose; nothing deploys |
| 2 | the sdk | comments only, and no module rebuilds for it |
| 3 | the client library | it is what a module calls to emit, and it is where the subject is derived. Until it lands, a locally-named event is published under the local name itself |
| 4 | the catalog | every manifest and every module's code, renamed together. Safe only once the runtime derives |
| 5 | the controller | **it refuses an old-style event name outright**, so landing it before the catalog makes every unconverted module unregisterable |
| 4 | the catalogue | every manifest and every module's code, renamed together. Safe only once the runtime derives |
| 5 | the controller | **it refuses an old-style event name outright**, so landing it before the catalogue makes every unconverted module unregisterable |
| 6 | the node-engine | last, because nothing else waits on them |
Two properties make the sequence safe rather than merely ordered, and both are pinned by tests. A
@@ -682,11 +682,11 @@ healthy while reacting to nothing.
- [x] 5.4 **the old broker and everything that named AMQP leave the mesh** (ADR 0131,
superseding ADR 0127): the two modules that
required `amqp` are removed, the broker's module is unassigned and removed (**done 2026-09-28**; the predecessor's own tooling, which rode the same adopted broker, went dark with it, as ADR 0130 accepted), registration refuses
a manifest that provides or requires `amqp`, and a whole-catalog check asserts none does. Not
a manifest that provides or requires `amqp`, and a whole-catalogue check asserts none does. Not
a retirement condition — a decision, taken, with the operator's "I don't care if the predecessor
breaks" on record (ADR 0130).
Retiring with it: the build outcome's second announcement under the module's own name, which
existed only so a catalog deployed before the rename and one after both heard it.
existed only so a catalogue deployed before the rename and one after both heard it.
> **The remote tooling goes with it too.** The predecessor's own mesh talks over that broker, so
> shutting it down ends the path that reaches this installation's machines from a workstation.
@@ -174,7 +174,7 @@ the client/identity side and the CA are the open pieces.
`ssh-client` template can emit a `Host` block per node with the right login name. Composed
end-to-end in the controller's tests.
**Written but not shipped:** the `ssh-client` catalog module itself exists on a branch of the
**Written but not shipped:** the `ssh-client` catalogue module itself exists on a branch of the
module repository; its pull request was closed with a hold until this design is deployed, and
nothing has deployed it since. The predecessor's generator still writes every workstation's ssh
client blocks today — which is where [issue 172](../../04-ISSUES/172-the-ssh-client-block-matches-one-spelling-of-a-machine/00-report.md)
@@ -30,7 +30,7 @@ webhook".
seat or schema:
1. `module moved <module> <commit>` — tell the mesh its source advanced (the controller repo has no
trigger, so this is manual; the catalog's webhook does it automatically — see below).
trigger, so this is manual; the catalogue's webhook does it automatically — see below).
2. `build --behind` (or `build <repo> [--ref] [--path <subdir>]`) — the builder rebuilds and
records the new image.
3. The mesh **reconciles on its own**: the module's declaration now names the new image, the next
@@ -41,7 +41,7 @@ seat or schema:
**A breaking change** — a manifest schema the controller parses differently (a fact's shape, a
seat's name), where the new controller cannot read the manifests the old one stored:
4. Land the code (controller + catalog together — they are one change).
4. Land the code (controller + catalogue together — they are one change).
5. Rebuild + deploy the new controller (steps 1–3). **The moment it is live it refuses the
still-old-shape stored manifests, and composition freezes for every node that runs an affected
module.** Running services are untouched; only new declarations stop.
@@ -63,7 +63,7 @@ automating this must solve.
### 1. The trigger today is HAL's, not the mesh's
Build-on-push works for the catalog because its repository has a Gitea webhook pointing at
Build-on-push works for the catalogue because its repository has a Gitea webhook pointing at
`http://host.docker.internal:9877/webhook/gitea` — and **that receiver is `hal-gitea-tools.service`**
(`~/.hal/modules/hal/gitea/tools/server.js`), a *predecessor* component. The nox builder consumes
build work; it does not receive Git events. So the mesh's own build pipeline currently rides on a
@@ -78,12 +78,12 @@ HAL service, and:
seat — `mesh-forge-trigger` or folded into the git seat's holder) that receives Git events and turns
them into build work over the broker, for **every** repository including `mesh-controller`. Replacing
`hal-gitea-tools` is the concrete first build. Its logic already exists to copy: match the pushed
repository (and changed paths, for a monorepo like the catalog) against the build-context
repository (and changed paths, for a monorepo like the catalogue) against the build-context
repository of every registered module, and rebuild the matches.
### 2. The builder validates too — and a breaking change deadlocks it
The builder embeds the same catalog package the controller does, so **it validates a
The builder embeds the same catalogue package the controller does, so **it validates a
manifest against its own compiled-in seat/schema set**. A breaking change therefore couples *four*
things, not two: the controller, the **builder**, every affected manifest, and every node's host.
This session's seat rename rebuilt the controller but not the builder, and the stale builder then
@@ -126,7 +126,7 @@ automate the freeze.
Revision, ADR 0162. The
trigger exists: the forge announces a merge on the bus and the controller acts on it (ADR 0157 made
the build narrate; this makes the merge a plan). A module's dependencies are one relation in the
catalog — `depends-on` edges of four kinds: stands-on, packages, built-by, declared. A merge takes
catalogue — `depends-on` edges of four kinds: stands-on, packages, built-by, declared. A merge takes
what changed and everything reachable from it along those edges, sorts the set into tiers, writes the
plan to the store, asks the first tier and returns. Each outcome advances the plan; a tier whose
rolled-out modules a later tier is built by waits until the machines report them applied; a
+10 -10
View File
@@ -62,7 +62,7 @@ onto this in step 3 of ADR 0116.
This is the load-bearing rule.
ADR 0112 says a manifest names no node, mesh or path. A transport address is the same class of thing: if
manifests held literal subjects, reorganising the subject space would mean editing every module in
the catalog, and the mesh would have hundreds of copies of a decision it made once.
the catalogue, and the mesh would have hundreds of copies of a decision it made once.
| declared | derived |
|---|---|
@@ -94,7 +94,7 @@ lives under the emitting module's own name and the consumer carries that couplin
**How the rule is checked, because it was not.** *Added 2026-09-27, same source.* Two checks, because
the mistake happens at two scales. Per manifest, at registration: an event is a local name, and the
old bus's form is refused with the name to write instead. Across the whole catalog, as a test:
old bus's form is refused with the name to write instead. Across the whole catalogue, as a test:
where a consumed event's emitter is present, it must emit that event. The second cannot demand a live
emitter for everything — a module lives in its own repository and may be installed long before the
one whose events it wants — so it says nothing about an absent emitter and everything about a present
@@ -117,7 +117,7 @@ caller already handles). The kind token is what makes `mesh.mod.*.event.>` a saf
first draft of this table had no token, which reads better and cannot be implemented.
**The test this must pass: the manifest survives the wire changing.** Reorganise the subject space
and every manifest in the catalog is still correct. That is the property
and every manifest in the catalogue is still correct. That is the property
ADR 0039 gave the sdk, applied to
declarations.
@@ -172,7 +172,7 @@ can be wrong.
**And one publish reaches three audiences without anybody's inbox being opened.** A build's outcome is
the seat's own event: whoever asked matches it by the id their request carried, the controller records
it, the catalog places it in the graph. That is the fan-out a shared exchange gave for free, written
it, the catalogue places it in the graph. That is the fan-out a shared exchange gave for free, written
as a subject the mesh derived instead of a topology somebody configured — and it is why a holder needs
no permission to publish into an asker's inbox, which is the one grant design 25 §4 refuses by name.
@@ -249,7 +249,7 @@ registered for every machine, sent as an event, never reached a machine assigned
consumer did not exist yet when the event passed — and a licence binding sent as events replays a
week of rotations where only the latest matters. So a module names the state it **owns** with
`state`, and another module's it **reads** with `reads: <module>.<name>`. Each is a key-value bucket
the controller creates from the catalog, mesh-wide, existing from registration so a reader can
the controller creates from the catalogue, mesh-wide, existing from registration so a reader can
watch before the owner runs anywhere ([design 25](25-the-bus-on-nats.md) §3). Every instance of the
owner writes; a reader reads and watches. A key may name a machine by the module's own convention;
the mesh keeps one bucket per name, not one per machine, because "every server, for every machine"
@@ -326,7 +326,7 @@ Every shape above appears once, in order, and no step knows where the next one r
**A change lands.** The module holding `mesh-git` emits `pushed` — repository, ref, commit. An
event, because it is a fact about git and git's identity is the meaning.
**The change becomes work.** The controller consumes `pushed`, asks the catalog which modules
**The change becomes work.** The controller consumes `pushed`, asks the catalogue which modules
are built from that repository and path
(ADR 0069), and submits one
**job** per affected module to the `mesh-build-machine` seat. The builder stays simple: it builds
@@ -366,7 +366,7 @@ not to an address it was given at genesis. Held and retried while the store rest
chain above went dark at the moment it touched a machine: nothing said which version a machine now runs,
or that it refused to. The controller states those as facts under its own seat's namespace, when what
a machine runs changes rather than on every convergence pass, and anything that cares subscribes the way
the catalog subscribes to `built` (ADR 0134).
the catalogue subscribes to `built` (ADR 0134).
The facts are second-hand by design — one emitter, one ordering — and a machine that cannot reach the bus
produces none, so absence is not health.
@@ -482,7 +482,7 @@ addresses will believe a class of bug is fixed when it is untouched.
controller's address — recorded somewhere, at some moment, and wrong as soon as anything moved.
Now it publishes to `mesh.seat.mesh-controller.report` and the bus routes it to whoever holds the
seat. Nothing anywhere records where the controller is, so nothing can record it *wrongly*. The
same is true of the builder, the catalog, the telegram sender. This class is not mitigated; it
same is true of the builder, the catalogue, the telegram sender. This class is not mitigated; it
is gone, because the information is no longer stored.
**Something that is not on the bus: the address stays, exactly as before.** A module that requires
@@ -580,11 +580,11 @@ billing existing under that name.
## 12. How it is checked
- **A manifest holds no subject.** A catalog test: no manifest contains a string matching the
- **A manifest holds no subject.** A catalogue test: no manifest contains a string matching the
subject grammar. The rule is worthless if it is followed by convention.
- **A preparation is given what the module is given.** A composition test: what the preparation
entrypoint receives equals what the module's own code receives, asserted rather than written twice —
which is the drift a hand-written step invites, three times over in the catalog today.
which is the drift a hand-written step invites, three times over in the catalogue today.
- **A convergence that changed nothing says nothing.** Two identical reports, one emitted fact: what is
guarded against is a fact per minute per machine, which is a stream nobody reads.
- **Permissions are exactly the three namespaces.** A composition test per module: the derived
@@ -135,7 +135,7 @@ by side until nothing is bound to the old one.
*Amended 2026-10-07 (ADR 0246,
[issue 298](../../04-ISSUES/298-a-new-seat-verb-deadlocks-across-two-repositories/00-report.md)):*
**additive takes two steps when the seat and its holder are in different repositories.** That is the
case for every mesh seat whose holder is a catalog module. §3 makes every promised verb a condition of
case for every mesh seat whose holder is a catalogue module. §3 makes every promised verb a condition of
holding, and a claim may name no verb the seat does not promise. So a verb added to the seat in the
controller refuses today's holder, and a holder that serves it is refused by today's controller. Neither
change can land first. An added verb therefore lands in three steps, each its own pull request:
@@ -163,7 +163,7 @@ still a new version.
of the subject table.
- **An added verb lands in steps, and each step is checked (§7).** The controller's claim check refuses
a claim that serves a verb the seat does not promise, and a holder that misses a required verb. It runs
at registration, at handover, and in `module check`, which the catalog's merge check runs over every
at registration, at handover, and in `module check`, which the catalogue's merge check runs over every
manifest with the controller the mesh runs. So a step out of order fails the pull request that took
it. That an optional verb stays optional when read back from the store is held by mesh-controller's
`TestAnOptionalVerbStaysOptionalInASetReadFromTheStore` (#114), and that a seat's row gains a verb's
+5 -5
View File
@@ -28,7 +28,7 @@ it put it there (ADR 0152).
## 1. What it is
A module, `mesh-console`, in the catalog. Its image is the tool runner's own — the client that
A module, `mesh-console`, in the catalogue. Its image is the tool runner's own — the client that
already speaks the bus as a command line and as an MCP server — started in a mode that reads the
module's credential and listens on loopback. It has no state, no provision, no seat. What it needs is
the bus, which it gets the way every module does: a credential the mesh minted for `<node>.mesh-console`,
@@ -42,7 +42,7 @@ consumer is the operator's agent, [36 — The operator's agent on a machine](36-
a machine without the mesh MCP server refuses such a module by name. Nothing else above changes: no seat, no
state, no tools of its own.
Its manifest says three things nothing else in the catalog says together:
Its manifest says three things nothing else in the catalogue says together:
- `invokes: ["*"]` — it calls every tool on the mesh, and the bus grants exactly that publish side;
- `listens` on a port `from: machine` — the filter opens nothing for it, because loopback is not outside;
@@ -74,7 +74,7 @@ its own under that module's name, `mesh.mod.<module>.tool.tools`, answering the
descriptions and argument schemas — the definitions from the code that answers them, and from nowhere
else. A module may not name a tool of its own `tools`; the runtime refuses the collision at load.
**The mesh MCP server asks the catalog which modules the mesh holds, then asks each.** `catalog_modules`
**The mesh MCP server asks the catalogue which modules the mesh holds, then asks each.** `catalog_modules`
answers the roster; one `tools` request per module, in parallel, answers the list. The bus refuses at
once a request nothing serves, so a module that is not running costs nothing and is named in the answer
as not answering, rather than silently absent — *silence and success must never look alike*. The list is
@@ -106,7 +106,7 @@ answers is reached through them by its address: `<seat>.<verb>` for a seat held
`<node>/<seat>.<verb>` for one held per machine, `<node>/<module>.<tool>` for an assignment, and
`<module>.<tool>` as well for a module whose instances are interchangeable. A module that is not
interchangeable is called with its machine or refused with the machines it runs on. Each discovery
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalog
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalogue
stays reachable through the `mesh` client and a setting, unannounced.
**What exists is what announced itself** (ADR 0197):
@@ -159,7 +159,7 @@ machine with nothing else on it does; the mesh MCP server binds whatever it is g
Two things shipped bent, both stated in [`00-as-is/13-the-console.md`](../00-as-is/13-the-console.md):
the person's client through the mesh MCP server (`--console`) exists and was exercised in the test suite, not
on the live mesh; and a module registered from the catalog by hand recorded its source as a URL rather
on the live mesh; and a module registered from the catalogue by hand recorded its source as a URL rather
than as a path on the git seat, because `--self` takes the forge path form — the rebuild-on-merge still
matched it by URL.
@@ -132,7 +132,7 @@ a hypothesis (ADR 0153);
the mesh is asked and changed through the controller seat's verbs; the forge through the forge module's
tools; a licence through the `anthropic-licence-manager` seat's verbs, never by editing a file. The hard
rules in new words: a file the mesh manages is changed through the verb that owns it or through the
catalog, never on disk; a store's database is never written by hand; main is never pushed; the mesh
catalogue, never on disk; a store's database is never written by hand; main is never pushed; the mesh
creates no symlinks and nobody else does; a package is declared, not installed by hand. The glossary's
words, none of the predecessor's.
@@ -231,7 +231,7 @@ All four nodes carry one since 2026-10-03. **Per node:** the role. **Per mesh or
extra tool servers, and the operator's managed-settings keys (§2). The controller's verb replaces a
setting layer whole, so a layer set for one of these keeps the others it already held. **Prerequisite:** the manager holds its seat and has adopted the licences.
**Order:** the manager assigned and a refresh observed; the mesh MCP server's provision in the catalog; this
**Order:** the manager assigned and a refresh observed; the mesh MCP server's provision in the catalogue; this
module on one workstation; the six predecessor files and the hand-made mesh MCP server entry removed there; a
new session read to confirm it sees the mesh's instruction file, the mesh MCP server's five tools under `mesh`, and
its licence; then the rest.
@@ -19,7 +19,7 @@ decisions:
# 37 — The operator's machine
**Every configurable thing on a node is a module, the home included, and the same catalog serves
**Every configurable thing on a node is a module, the home included, and the same catalogue serves
a server and a laptop.** One default configuration per module, varied per node by a setting or a
kept region; roles a machine has once as node-scoped seats with tool contracts; one tool runner
per node serving every module's tools outside any container
@@ -117,7 +117,7 @@ resolver is asked by the second graphical module; the display server itself is g
all four today. Nothing home-scoped composes before it.
2. **The tool runner** — `mesh-host` supervises it; mesh-tools serves bundles from memberships
and reloads; mesh-controller composes the bundle into the declaration and the memberships to one
runtime per node; the catalog renames the mesh MCP server. Proven when the packet-filter verbs answer
runtime per node; the catalogue renames the mesh MCP server. Proven when the packet-filter verbs answer
from it and its container is gone.
3. **`zsh`**, the first environment module: seat, `user` shape, home files, `execute`. Proven on a
server first, then every node.
@@ -52,7 +52,7 @@ as design 28's: nothing here is new ground; every package reshapes something sta
| the controller's bus principals | Go: one principal per module per node, grants from what it declares | 132 lines | gains one principal per node for the runtime |
| the controller's memberships | Go: one per assignment, the subjects a runtime serves | 143 lines | **unchanged** in shape; the runtime reads several |
| the controller's declaration composer | Go, one file | 2 053 lines | gains the runtime's process, the bundles' archives, two env words |
| the catalog | 35 manifests build a per-module tool container on the runtime's base image | — | none do; the runtime is a module of its own |
| the catalogue | 35 manifests build a per-module tool container on the runtime's base image | — | none do; the runtime is a module of its own |
**Two measurements decide the shape.** The node-engine needs no change: a `process` and an `archive` are
what the runtime and a bundle are, and both are applied today. And the runtime already does
@@ -140,11 +140,11 @@ with the shortcut off answers the same.
a module whose own code is an image artifact is refused, whatever image it is built on.
*Amended 2026-10-02, at WP3.* The gate refuses the pattern **spreading**, not standing: a module
new to the catalog in that shape, or one that had already moved to a bundle and returns to it,
is refused; a module the catalog already holds in that shape — judged from the manifest it
new to the catalogue in that shape, or one that had already moved to a bundle and returns to it,
is refused; a module the catalogue already holds in that shape — judged from the manifest it
holds and what that module's newest build stood on — is rebuilt without complaint. The day the
runtime arrives some thirty such modules stand, each moves in its own change from WP4 on, and a
gate refusing every rebuild in the meantime would stop the catalog's pipeline to make a point
gate refusing every rebuild in the meantime would stop the catalogue's pipeline to make a point
this record already makes.
**Proof.** Composition tests: a node with three assigned modules, one holding a seat, yields one
@@ -159,7 +159,7 @@ the runtime is registered.
**What changes.** mesh-tools gains a `bundle` artifact of itself beside its images, and its manifest
becomes the `node-tools` module: a package for the interpreter, the loopback listener the mesh MCP server
declared, `invokes: *`, and nothing else — the process is the controller's to compose (WP2). In the
catalog, `mesh-console` is retired as a module and `node-tools` assigned where it was. The
catalogue, `mesh-console` is retired as a module and `node-tools` assigned where it was. The
runtime's `serve` keeps answering MCP on loopback; the person's end of it keeps the name "console"
([glossary](../../00-META/glossary.md)).
@@ -178,7 +178,7 @@ runtime accepts. *Built 2026-10-02* (mesh-tools `c46f950`, mesh-controller `ca7e
`729a5f9`). *Proven live 2026-10-02/03, on all four machines*: the mesh MCP server's container is gone,
`node-tools` runs as a unit the node-engine wrote, as the operator's account, `tools/list` on each loopback
answers with the same 219 tools as before, and the controller's verbs answer through it; `mesh-console`
retired from the catalog. Three things the step found are issues
retired from the catalogue. Three things the step found are issues
[203](../../04-ISSUES/203-a-fresh-assignment-is-pushed-before-its-credential-exists/00-report.md),
[204](../../04-ISSUES/204-a-controller-handover-re-sent-every-node-a-stale-declaration/00-report.md) and
[205](../../04-ISSUES/205-a-package-resource-fails-against-a-stale-package-database/00-report.md).
@@ -224,7 +224,7 @@ the node-engine re-creates the runtime's process on every reconcile (resolved th
credential and state directory gone, the client through `sudo` since the daemon's socket is root's;
proven on all four machines — `status`, `banned` and the module's own `fail2ban_settings` answer from
the runtime, no `mesh-fail2ban` container, the runtime serving both bundles. Two holders moved; of the
thirty-three tool containers the catalog held, thirty-one remain, and all but these two carried their
thirty-three tool containers the catalogue held, thirty-one remain, and all but these two carried their
module's configuration and secrets in the container's environment, which a bundle does not have — the
question research research 020 opened
and ADR 0192
@@ -235,7 +235,7 @@ container's, the runtime hands each bundle its own. That is WP4b below.
*mesh-controller, mesh-tools, mesh-catalog. One day. The rest of ADR 0175, under ADR 0192.*
**What changes**, in order: the manifest's tools artifact gains `env` and the catalog check
**What changes**, in order: the manifest's tools artifact gains `env` and the catalogue check
refuses a secret's content in it; the composer resolves a bundle's `env` per machine and carries it
beside the bundle's archive, `restart-on` included; the runtime hands each bundle its own
environment — the contributor's argument for an imported bundle, the child's environment for a
@@ -60,8 +60,8 @@ Measured 2026-10-03 on the four machines and in the repositories.
| a bundle's words | paths and constants written with `${dir:…}` and `${port:…}` only; a fact the mesh knows reaches a bundle as a file whose path is a word | the agent module's facts file and settings file |
| a bundle calling a tool | `mesh/ask` through the runtime that launched it (ADR 0198); no bundle holds a bus credential | how the manager visits every node |
| a module's own long-running code | a bundle the runtime launches and restarts (ADR 0198); the runtime's subscription and grants built, the modules moving in design 38 WP4c's waves | the manager's daemon (WP3, WP4) |
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalog, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalog; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
| the vendor's refresh, the sealed box, the grant file | `anthropic-manager` in the catalogue, built on the controller placement ADR 0183 moved away from; assigned to nothing | its client ported into the manager; the module retired (WP6) |
| the credentials write, the strip, the identity read | `anthropic-consumer` in the catalogue; tested; assigned to nothing | ported into the agent module with its tests; the module retired (WP6) |
| the predecessor's manager and consumer | the lease per licence, the expiry floor, the lineage comparison, the identity guard, three touchpoints, cooldowns | ported as logic with its tests |
## The order the work allows
@@ -99,7 +99,7 @@ resolves it. The runtime's port stays what its code fixes; assigning it is
second question and not this package's.
**Proof.** The plan for a workstation carrying a consumer of `mcp-endpoint` shows it bound to the
runtime's port; the controller's tests and the catalog's checks pass.
runtime's port; the controller's tests and the catalogue's checks pass.
## WP2 — The agent module
@@ -130,7 +130,7 @@ runtime's port; the controller's tests and the catalog's checks pass.
**Proof, before anything runs live.** Unit tests: the renderer writes the mesh's keys and nothing else;
it writes nothing when nothing changed; the credentials write strips a refresh token and is atomic; the
lineage cases from the predecessor; a sealed hand-over opens only with the module's key; no tool's answer
contains a token. The catalog's checks pass.
contains a token. The catalogue's checks pass.
**Proof, live, on one workstation, configuration only.** Assign the module; set the node's role; push.
The agent's managed directory holds the two files; everything under the person's agent directory is
@@ -188,11 +188,11 @@ credentials file is left as it was and the agent authenticates through the key-h
*The live mesh and mesh-catalog. One day.* The module on every node, the predecessor's files removed on
the second workstation; a login under a licence's account collected and adopted, and one under the wrong
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalog;
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalogue;
designs 36 and 39 set to `implemented` with the as-is written
([playbook 02](../../00-META/process/02-graduation.md)).
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalog
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalogue
has no module built on the old placement.
## The home, personal rules and lessons — WP7 to WP10
@@ -259,7 +259,7 @@ WP10 the rest of the nodes, and the as-is (the live mesh)
neither list, the watched entries and the pending requests.
**Proof, before anything runs live.** The module's tests of design 36 "How it is checked" for ADR 0273 §1,
§2, §3 and §4, each failing on the commit before. The catalog's checks pass.
§2, §3 and §4, each failing on the commit before. The catalogue's checks pass.
### WP8 — Personal rules and the operator's items, live
@@ -146,7 +146,7 @@ against WP2's controller before anything is published.
the node. The rendering is ADR 0203's and ADR 0204's: module order, a naming line per contribution,
`PATH` entries added only when missing. `${machine:…}` in a contributed value is resolved first.
- **Refusals.** A variable set by two modules on one node is refused, naming both. A placeholder in a
module that does not claim the matching seat is refused, both at the catalog check and at
module that does not claim the matching seat is refused, both at the catalogue check and at
composition.
**Proof.** The controller's tests:
@@ -157,7 +157,7 @@ against WP2's controller before anything is published.
- each refusal, by name;
- the seat table carries both seats and refuses a module declaring either.
The catalog check over the whole catalog passes.
The catalogue check over the whole catalogue passes.
## WP3 — The modules
@@ -188,7 +188,7 @@ The catalog check over the whole catalog passes.
- **`zsh-autosuggestions` and `zsh-syntax-highlighting`, new.** Each declares its package and
contributes its loader from the distribution's path, in the `normal` and `last` slots.
**Proof.** The controller's catalog check over the whole catalog passes. The modules' tests pass.
**Proof.** The controller's catalogue check over the whole catalogue passes. The modules' tests pass.
A rehearsal composition for a node holding all five shows:
- the `.zshrc` block with the prompt in `normal` and highlighting in `last`;
@@ -227,7 +227,7 @@ The user-scoped units of `mesh-host` #72 stay to-be 38's WP6.
1. Merge WP1 and roll the node-engine.
2. Merge WP2, and push the controller.
3. Merge WP3 and WP4, and build the new modules by hand: a new catalog module's first build is asked
3. Merge WP3 and WP4, and build the new modules by hand: a new catalogue module's first build is asked
for, not automatic.
4. On one server, assign `node-env`, `zsh`, `zsh-autosuggestions` and `zsh-syntax-highlighting`, and
push. Then check:
@@ -328,7 +328,7 @@ control-node withholds it until a call to it needs a person's approval. The othe
writes it into, and a word naming that file for its tools bundle. The bundle registers `execute` only
when the file says `serve`. `zsh_config` also answers whether `execute` is served on the node, and why.
**Merge order.** The controller first, then the catalog. Either order composes. With the catalog
**Merge order.** The controller first, then the catalogue. Either order composes. With the catalogue
alone and the control-node's value set, the self-check would say the holder there is silent.
**Proof.** The tests ADR 0268 lists under *how it is checked*. Live, after both are merged and rolled
@@ -382,7 +382,7 @@ move, write or delete a file, and a cleanup needed a shell on the machine. Most
project folders, downloads, logs, scratch.
- **A node seat, `node-files`, on every node**, held by a module of its own, `mesh-files`, beside `mesh-reads`
in the catalog; the controller holds the seat's definition beside the other node seats. It has a verb for each file action: list,
in the catalogue; the controller holds the seat's definition beside the other node seats. It has a verb for each file action: list,
stat, readlink, read, search, write, copy, move, delete, make a folder, change mode, change owner, account and group
lookup, and the trash's list and restore. Each says what it replaces (ADR 0245): `ls`, `stat`, `readlink`, `cat`,
`grep`, `cp`, `mv`, `rm`, `mkdir`, `chmod`, `chown`, `getent passwd`, `getent group`, `id`, and `ssh <node> cp` for a
@@ -50,7 +50,7 @@ not before. Modules that need none go ahead now.
## How every module moves
1. Written in the catalog, with its tools and their tests, and checked by the controller's catalog
1. Written in the catalogue, with its tools and their tests, and checked by the controller's catalogue
check.
2. Merged, which builds it.
3. Assigned to **the first workstation, the proving machine**, and pushed. Its tools and files are
@@ -199,11 +199,11 @@ both places of both bars once. The bar writes no block that depends on the hardw
the power module's state `power.draw`, one value per machine, which its bundle puts from its own root
sampler. The block `shows: state`; the bar's bundle watches it under a read the controller grants the
bar for what the modules on its machine offer, and the bar names no module. The battery block is the
bar's own, reading the kernel. The catalog's `checks/paths` refuses a module that names another
bar's own, reading the kernel. The catalogue's `checks/paths` refuses a module that names another
module's path under `/run` or `/var/lib`.
The node-engine is delivered first, so the machines report the two capabilities. The controller comes
next, because it parses `data`, `if-capability` and `renders`. The catalog comes last. Left open: the
next, because it parses `data`, `if-capability` and `renders`. The catalogue comes last. Left open: the
draw at the wall (a metered plug or a UPS), a GPU-load block from a generic module, and the headsets.
## Phase 3 — one machine model
@@ -213,6 +213,6 @@ and `memory-pressure` (research 027/03).
## How it is checked
Each module's own tests and the catalog check, at merge. On the proving machine, each tool answered
Each module's own tests and the catalogue check, at merge. On the proving machine, each tool answered
through the mesh and each owned file checked in place, before any other machine is assigned. This
document's tables are updated as each module lands.
@@ -146,8 +146,8 @@ a manifest. Not off-site: a lost machine loses its backups with its data, by the
## How it is checked
The catalog check (`module check`, and the catalog-wide test beside it) refuses a provider that
The catalogue check (`module check`, and the catalogue-wide test beside it) refuses a provider that
grants without saying what it keeps for its consumers, an irreplaceable item with neither a backup nor a
redundancy, and any backup line written by hand (ADR 0233). The store rule this design stated before
was held only by a controller test over the catalog checked out beside it, never by `module check`. The weekly
was held only by a controller test over the catalogue checked out beside it, never by `module check`. The weekly
restore test above, and the 48-hour status line, are the running checks.
@@ -68,7 +68,7 @@ Owning repositories: **mesh-controller** (the condition store, watchdogs, `docto
calls, the hand-act log, the facts snapshot), **`mesh-host`** (the node-engine: the apply queue, report
order, epoch refusal, the `report` verb, rollback witnessing), **mesh-tools** (the tool runner and the
mesh MCP server: their heartbeat, passing every argument, health answers), **mesh-catalog** (the
`operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalog's
`operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalogue's
composition check), **mesh-sdk** (the TypeScript providers' loop and its retirement), **mesh-lab** (the
replays and the induced-failure scenarios).
@@ -680,7 +680,7 @@ runs, and a restore that builds a new store beside the live one for a person to
**The facts snapshot.** The controller exports daily, and after any change of machines, assignments or
seats: every machine (a stable pseudonym of the same length as its name, its role, operating system,
C library, architecture, node-engine and tool runner builds), assignments, settings keys and their
non-secret values, seats and their holders, the catalog commit, and the versions the mesh runs of
non-secret values, seats and their holders, the catalogue commit, and the versions the mesh runs of
the bus server, the store and the node-engine. No secret and no address: an address is replaced by one
from a documentation range. It is kept in the artifact store as `facts/latest`, where the build seat
reads it.
@@ -768,7 +768,7 @@ replay covers what must not be done to it on purpose, and every rule keeps a liv
- **It runs on the build seat**, asked by the controller when the forge's announcer says a pull request's
head moved: the repository's own `merge-check.sh` in the mesh's Go toolchain with no container runtime
socket, beside a throwaway store and bus of the versions the snapshot says run, then mesh-lab's replays.
The verdict is the head commit's status `mesh/merge-gate`, an error never a pass. A catalog change is
The verdict is the head commit's status `mesh/merge-gate`, an error never a pass. A catalogue change is
judged by the controller the mesh runs; a node-engine change by it built with the change's validator.
- **Replays live where their incident is**: a component's logic in its repository as `TestReplay<issue>`,
what the mesh runs (the bus's release, the resolver under musl and glibc) in mesh-lab's `replays/`, all
@@ -868,7 +868,7 @@ the `data` verb; `cleanup` extended to a module's own retired data, deleted by t
holder after a last restore point; the controller's grant names `node-backup.backed-up`. Built on
branches in mesh-controller (migration 0072), `mesh-host` (the installer's user list; the kept-directory
report), mesh-catalog (the holder's measuring and deletion, every module's data, the providers' held
sizes), the media catalog and the photo application, not yet merged. **Not yet:** the TypeScript
sizes), the media catalogue and the photo application, not yet merged. **Not yet:** the TypeScript
providers saying their consumers' sizes (until then a consumer at two of them is `data-held-twice`, a
warning, not compared), and SMART read under an array.
@@ -932,7 +932,7 @@ and "the commit is the build at hand"), on branches, not yet merged:
| Repository | Delivers |
|---|---|
| mesh-controller | the planner's one mapping of a changed file onto modules and its one answer of a merge's reach, asked by the merge handler, the what-if, the gate and the check; a file no build reads touches nothing (issue 280's *left open*); every pull request answered — the gate when it reaches a module, the repository's own check for the mesh's repositories, a pass that says so otherwise; the gate run by the build seat with its judge chosen from the graph, composing the plan's definitions only, failing only a manifest problem the change brings; the delivery plan computed and carried with the verdict; a build of a commit off its module's trunk recorded and never registered; its own manifest naming every verb of its seat |
| mesh-catalog | the forge's announcer saying, with every pull request, the directories holding a module at its head, the files it deletes and whether it has a `merge-check.sh`; both statuses and the delivery plan on the pull request; `gitea_branch_protection_get` and `gitea_branch_protection_set`; the catalog's own check |
| mesh-catalog | the forge's announcer saying, with every pull request, the directories holding a module at its head, the files it deletes and whether it has a `merge-check.sh`; both statuses and the delivery plan on the pull request; `gitea_branch_protection_get` and `gitea_branch_protection_set`; the catalogue's own check |
| `mesh-host`, mesh-tools, mesh-sdk, mesh-lab, mesh-media-catalog, and the applications built from their own repositories | each its own `merge-check.sh`; mesh-tools' tests each on a bus of their own |
| mesh-tools-go, mesh-tools | a C compiler and Python in the Go toolchain, git in the TypeScript one |
| hq | its own `merge-check.sh`, running `records.py`, `index.py` and `cycle.py` |
@@ -696,7 +696,7 @@ Each phase ends at its own *done when*. Owning repositories from [`repos.md`](..
|---|---|---|---|
| **1 — Fix the first holder in place** | D1–D4 in the output seat's holder and the watcher, then D5–D10; no change of shape. The operator then makes the two bots and the mesh is given their values through the controller | mesh-catalog | the holder's tests for D1–D4 pass; a live test message reaches the phone from both the holder and the watcher |
| **2 — The desk's actions** | `node-notifier.send` gains actions; the dunst holder emits the chosen one; the launcher's prompt returns typed text; the screen-lock holder emits lock and idle changes | mesh-catalog | a notification with two actions returns the chosen token as an event; locking emits an event |
| **3 — The seats, and the router** | the kinded bench in the seat set; `kind` and `capabilities` on a claim; `channel-capabilities/1` and its contract tests; publishing on a seat's event subjects and its grant; `channel` and `intake` seats; the output seat's holder becomes the router and holds `channel/desktop` and `intake/desktop`; presence as current state; the operator's identity list and identity check in the controller, the router's `trusted` stamping and untrusted input (§4); the content rule allowing machine names, references and `detail`, refusals naming the offending part (§9) | mesh-controller (seat set, claim fields, registration refusals, grants, the identity list and check); mesh-sdk and mesh-tools (seat-event publishing in the shared library and the tool runner); mesh-catalog (the seats' definitions, the router) | the catalog refuses an unknown capability and a second holder of one kind; a message is routed to the desk by capability and context; an envelope from an identity not on the list is re-emitted untrusted; a path is refused to its sender by name, and travels as a reference the desk opens |
| **3 — The seats, and the router** | the kinded bench in the seat set; `kind` and `capabilities` on a claim; `channel-capabilities/1` and its contract tests; publishing on a seat's event subjects and its grant; `channel` and `intake` seats; the output seat's holder becomes the router and holds `channel/desktop` and `intake/desktop`; presence as current state; the operator's identity list and identity check in the controller, the router's `trusted` stamping and untrusted input (§4); the content rule allowing machine names, references and `detail`, refusals naming the offending part (§9) | mesh-controller (seat set, claim fields, registration refusals, grants, the identity list and check); mesh-sdk and mesh-tools (seat-event publishing in the shared library and the tool runner); mesh-catalog (the seats' definitions, the router) | the catalogue refuses an unknown capability and a second holder of one kind; a message is routed to the desk by capability and context; an envelope from an identity not on the list is re-emitted untrusted; a path is refused to its sender by name, and travels as a reference the desk opens |
| **4 — Asks, and operator messages** | `ask`, `ask cancel`, `asks`, `answer`; the events; kinds, life, limits, batching, history; escalation; addressing, the register of agents with kept messages, the responder; the untrusted-input rule in every agent module's instructions (§5) | mesh-catalog | the router tests of ADR 0234 pass; live: an agent's question answered at the desk, and with the desk locked, on the phone; `@mesh status` answered; a message to a stopped agent kept and said |
| **5a — Asks on the bus** (ADR 0259) | the kinded bench, caller-named verbs, `proof` subjects and the records users read (controller); a bundle's seat traffic, compare-and-set state and its own account (tool runner); the asker's and channel's types (shared library); the router's asks, tokens, identities, judging, warrants, expiry and linking with its hour (messenger); `telegram` and `desk-channel`; the controller asking for its conditions' actions and acting on warrants | mesh-controller, mesh-tools, mesh-sdk, mesh-catalog | a held delivery released from the phone; the hand-act reads "the operator, via telegram, user id verified"; a desk Silence goes through an ask |
| **5 — Authorise, with TOTP** | `authorises` in the verb table; the router's checks for the code; direct calls refused, break-glass; `retire approve` takes `expect`; the hand-act fields; the TOTP seed as the router's state, enrolled at a terminal with ten recovery codes; `factor recover`, `factor status`, the local-only break-glass enrolment; no P2 tier before recovery codes exist; optional key enrolment; the self-check probe for the away channel; agents' grants lose the authorising verbs | mesh-controller; mesh-catalog (agents' grants, the desk's code prompt) | one controller test per refusal passes; at the desk, approve needs a code; a recovery drill re-enrols with a recovery code, and is announced |
@@ -2,15 +2,8 @@
layer: to-be
status: in-progress
code: [mesh-controller, mesh-catalog modules/mesh-delivery-gate, mesh-delivery, mesh-host, mesh-sdk, mesh-vault, hq 00-META/checks]
updated: 2026-10-29
updated: 2026-10-27
decisions:
- "ADR 0572"
- "ADR 0571"
- "ADR 0570"
- "ADR 0567"
- "ADR 0565"
- "ADR 0564"
- "ADR 0563"
- "ADR 0559"
- "ADR 0555"
- "ADR 0551"
@@ -136,8 +129,14 @@ source revision is derived from the keys: every **want** equals its module's **f
- **The forge bridge** turns the forge's events into stream messages and is the only part that calls the
forge.
- **The indexer** computes each module's fingerprint at the merge preview and writes the candidates of each
group and the modules of the trunk. It reads no placement: placement is not in any repository, and the forge
bridge carries nothing of placement (ADR 0567).
group and the modules of the trunk. At each trunk merge of the catalogue it also reads the catalogue's
**placement files**, one per node, into its state `placement`: one key `placement.<node>` with that node's
modules, their pins, host ports and bindings, and the source revision. It adds the nodes to `modules.<module>`
and says `placement-changed` only for a node whose key differs (ADR 0549 §1, §2). While a catalogue pull request
that changes a placement file is open, the forge bridge carries its placement files at the head and the indexer
keeps, at each head, each node's file that differs from the trunk's as a **placement candidate** in a state of its
own, `placement-candidates`, one key `<group>.<node>`, deleting the group's other keys, and all of them when the
pull request merges or closes; nothing that composes what runs reads it (ADR 0559 §2, §3).
- **The builder** claims a build by creating its key, once per fingerprint, and orders it as an ask on the stream of the artifact's
language; it makes no artifact's bytes since R4e and chooses no node, because the build modules pull the asks (ADR 0498 §1, ADR 0525 §1, §4). From R4g it
delivers its own module, the build modules and the controller from its keys, and nothing falls back to build-agent, which is removed
@@ -166,15 +165,21 @@ source revision is derived from the keys: every **want** equals its module's **f
the artifact's digest, the source revision and whether the module holds data; the assigner also writes the
node's own want `want.<node>.node`, and never deletes a want (ADR 0408 §4). A move a person must allow is judged
by the node agent, which waits; the assigner raises the ask and on a yes writes the want again naming it
(ADR 0547 §3). It reads which modules run on which node from its own state `placement`, held as the seat
`mesh-placement` (ADR 0567); until ADR 0547's third milestone it also reads the controller's assignments, writes no want for
a module on a node that the two do not both name, and raises a condition naming the pair while they differ.
(ADR 0547 §3). It reads which modules run on which node from the indexer's `placement`, on its watch
(ADR 0549 §3); until ADR 0547's third milestone it also reads the controller's assignments, writes no want for
a module on a node that the two do not both name, and raises a condition naming the pair while they differ
(ADR 0549 §5).
It also runs the **composition check**: whether the machines a change touches still compose with it, read
from the keys the indexer and the builder hold and from the machines, settings and grants it reads, with
mesh-sdk's composition, the one every node agent composes with (ADR 0446, ADR 0547 §2). Its module was made in
R4g with release 5; from release 6 it writes no difference (ADR 0524 §4, ADR 0528, ADR 0539 §8). The check composes
no placement and waits for no node's answer: a pull request changes no placement, since placement is bus state
changed by a proposal (ADR 0567; ADR 0446 §3 read as written). Its own composition stands in for every sealed value, so only a node's answer to a proposal can say that
R4g with release 5; from release 6 it writes no difference (ADR 0524 §4, ADR 0528, ADR 0539 §8). For a
change that carries placement candidates it composes the change with them laid over the trunk's placement,
counts every node whose placement changes among the machines it touches (ADR 0446 §3, as ADR 0559 §5 changes it), and names each refusal; then it reads each
placed node's answer to the candidate and fails on a module that differs where the change does not intend it (on
a node the trunk does not place yet, as at the seed, no difference is intended), on
a refusal, and on a node that has not answered within 10 minutes, named, so a laptop asleep is never passed;
that node's later answer at the same head asks the check again on its event, as for any part of the mesh not
heard (ADR 0548 §1, ADR 0559 §1, §5). Its own composition stands in for every sealed value, so only a node's answer can say that
what the node would run is the same.
- **The node agent**, the node-engine's own process, follows its node's wants, **composes each wanted module for
its own node** with mesh-sdk's composition from the manifest by digest, the module's resolved values from
@@ -186,9 +191,8 @@ source revision is derived from the keys: every **want** equals its module's **f
list, alone and with no want (*The bus's user list*, below).
- **The grants part** (module `mesh-grants`, holding the seat `mesh-grants`) composes who may say what on the bus
with mesh-sdk's bus composer: each module's membership, the buckets, logs and streams a manifest declares, the
bus's user list, and `grants.<module>.<fingerprint>`, written from L7's delivery once the list the bus loaded
carries that build's accounts (ADR 0547 §5, §6; ADR 0571 §9). It keeps each module's membership in its own state
`memberships`, which the runtime reads, and publishes memberships from the first milestone (ADR 0571 §10); until the operator's `take-over` it keeps the bus's user list, the
bus's user list, and from ADR 0547's third milestone `grants.<module>.<fingerprint>` (ADR 0547 §5, §6). It publishes
memberships from the first milestone; until the operator's `take-over` it keeps the bus's user list, the
assertions and the grants in shadow, composing and comparing them with what the bus loaded and writing nothing
of them that takes effect; its tools read all it holds and redo all it does.
- **The watchman** turns the bus's connections into presence and mirrors every key whose next move a machine
@@ -224,12 +228,9 @@ there is three milestones, one issuer of each key at every moment (ADR 0547 §11
elsewhere but the bus's user list, which the bus node keeps able to follow until the third milestone so `hand-back` has a list to return to.
3. **Nothing is left with the controller** (issue 774; what remains of issue 1126, the homes of the controller's
other jobs, carries into it by a note on 1126 and is not one of its issues): the vault mints bus accounts, the grants part
writes grants, the try is the checker's own verb, placement is read from the seat `mesh-placement` alone (ADR 0567; ADR 0549 §5,
§6), the vault signs with the mesh's signing key (ADR 0550 §2), each node-engine holds the node seat `node-engine`
and asks the operator itself for a hand-over or an access act (ADR 0563), and the controller's lease is handed to a
holder that is no controller once every other job it keeps has a home by its own decision (research 072): health
(L4, ADR 0564), the control records (L5, ADR 0565), provisioning (L6, ADR 0570), and the history and records
(L7, ADR 0571). The lease write waits until all four are delivered.
writes grants, the try is the checker's own verb, placement is read from the catalogue alone (ADR 0549 §5,
§6), the vault signs with the mesh's signing key (ADR 0550 §2), and the controller's lease is handed to a holder that is no controller once every other job
it keeps has a home by its own decision (research 072).
No machine is enrolled before the third milestone: until the vault mints enrolment tokens and node accounts,
those are the controller's alone (ADR 0550 §6).
@@ -250,7 +251,7 @@ The target (ADR 0547):
```
forge ──forge messages──► forge bridge ──► the bus ◄── done keys ── builder, build modules (build seats)
│ ▲
gate: the merge, the source revision ◄┤ ├── indexer: fingerprints, manifests
gate: the merge, the source revision ◄┤ ├── indexer: fingerprints, manifests, placement
grants part: memberships, assertions,◄┤ ├── assigner: each node's wants, by six rules
the user list, grants │ ├── watchman: presence, pulses, stuck keys
vault: secrets, signing, accounts ◄┤ └── status view: delivered, moving, settled, stuck
@@ -266,14 +267,9 @@ The target (ADR 0547):
plan, never sends to a machine and writes no composition (ADR 0539 §17).
- **The controller** is no part of build and delivery (ADR 0547 §1). The jobs it still keeps outside them
(nodes and adoption, identities, secrets and given values, licences, config moves, conditions and facts, ADR
0429 §9) each move by a decision of their own under research 072 before it loses its lease. All are decided:
placement lives on the bus, held by the seat `mesh-placement` (ADR 0567); the mesh's signing key in the vault
(ADR 0550 §2); a hand-over, an access act and the root-free judgement on each node's own seat `node-engine`
(ADR 0563); health with the condition keeper, the watchman, the self-check and the healers (ADR 0564); the
control records with mesh-config, the node agents, the assigner and the hand-act log's seat `mesh-hand-acts`
(ADR 0565); provisioning with the seat `mesh-provisioner` (ADR 0570); and build history with the builder's
build log, the artifact sweep with the seat `mesh-artifact-sweep`, disk readings with disk-load, and who holds
which seat with the seat `mesh-placement`'s state `seat-holders` (ADR 0571).
0429 §9) each move by a decision of their own under research 072 before it loses its lease. Two are decided, by
the operator's answers of 2026-10-26: placement lives in the catalogue (ADR 0549), and the mesh's signing key in
the vault (ADR 0550 §2).
- **The vault** keeps every secret the mesh keeps (ADR 0550 §1): the mesh's signing key, licence grants and
tokens, bus account passwords and module secrets; a holder gets a value only sealed to its own key, or asks the
vault to act with it. It makes the mesh's signing key and signs enrolments and node certificates on request, by
@@ -414,7 +410,7 @@ default yielded so the node-engine and the controller shared one tier (ADR 0539
**The SDK by version.** A Go module names the SDK by its released version and fetches it from the mesh's forge,
checked against its recorded checksum; vendored copies go. Inside a group, a member may name the SDK member's
head: its merge check resolves that commit from the forge and builds against it, and the head stays fetchable
after the group merges because the forge keeps every pull request's head. The catalog's check accepts an SDK
after the group merges because the forge keeps every pull request's head. The catalogue's check accepts an SDK
commit on the SDK's trunk or the head of an SDK pull request the forge holds, open or merged (ADR 0402 §2 to §4).
## Checks
@@ -517,7 +513,7 @@ read the setting: it reads `turns`, finds none, and claims its checks at once.
bucket in the state `running` names, among its trunk heads, a repository the merge goes into; the verdict of
the merging delivery's own heads excepted. The gate already watches that bucket, so the verdict leaving
`running` (a pass, a failure, its key deleted) gives the waiting merge its turn at once, with no timer and no
new event. Siblings a check reads beside are not held: the catalog is read beside nearly every check. A merge
new event. Siblings a check reads beside are not held: the catalogue is read beside nearly every check. A merge
made at the forge by hand is not held; the check it lands under ends on the commit it read and is then carried
(ADR 0548 §2).
- **The merge turn.** Before it reads the checker's verdicts for an approved merge, the gate writes one key per
@@ -695,26 +691,22 @@ backup holder backs up nothing, and the have says so.
- **What a node composes from, and who writes it.** One composer, mesh-sdk's, has three callers: the node agent for
its own node, the assigner's composition check for every node, and the grants part. Each fills the composition's
input from the bus with its own credential and nothing else (ADR 0547 §2, ADR 0549 §3):
- **placement**, the state `placement` of the seat `mesh-placement`, one key `placement.<node>`, written by the
assigner's credential alone on an approved proposal and said by `placement-changed` (ADR 0567);
- **placement**, the indexer's state `placement`, one key `placement.<node>` read at each catalogue merge from
the node's placement file and said by `placement-changed` (ADR 0549 §2);
- **the node records**, each in its node's own have key `<node>.node`, written by that node alone and heard as
the key's change (ADR 0555 §3);
- **the manifests**, the indexer's `modules.<module>` and its manifests by digest, or the digest the composing
node's want names where it runs another build; the private network's manifest comes with mesh-sdk;
- **the config values**, mesh-config's resolved state, the composing node's slice alone for a node agent; a module
in the settings form (its manifest declares no config, so mesh-config holds none for it) has its values in the
placement key instead;
placement file instead;
- **its own secrets and credentials**, from the vault's verb `own`, sealed to the asking node's key and answered to
that node's engine alone, and composed again when the vault says `secret-changed` (ADR 0550 §5).
While both sources stand (ADR 0549 §5), mesh-config resolves values for a module either the placement or the
controller's assignments name on a node, and the grants part composes both, holding a seat held once for the mesh
where the placement says.
- **How placement is changed.** The seat `mesh-placement`, held once for the mesh by the assigner, keeps three
states: `placement`, one key `placement.<node>` per node; `proposals`; and `candidates`. Only the holder's
credential writes them, and the contract (the states' names among it) is mesh-sdk's. No repository holds
placement and none names a node: what differs between nodes is bus state, and a build module's capacity and
build hours are values on each node's layer of mesh-config. A key holds what a person decides of the node and
nothing a machine observes:
- **The placement file.** One file per node in the catalogue, changed only by a pull request and checked before
it merges by the nodes it places (ADR 0549 §1, ADR 0555, ADR 0559). It holds what a person decides of the node and nothing a machine observes:
- the node's decided facts: its endpoint and site, whether it is the private network's hub, its address on the
private network (a decision written once at enrolment; nothing allocates one at run time), the domain its
routed names are composed under, its operator and agent logins by name with their homes, whether it is
@@ -722,61 +714,38 @@ backup holder backs up nothing, and the have says so.
over), and on the hub the names given to that tunnel's peers. No secret, and keys only as public halves;
- its modules in the order the composition reads them, each with its host ports by the port the software
listens on, its bindings by provision, and its config placement (`places`, `accesses`, `networks`,
`endpoints`, `expose`, `reach`, `ports`) as two layers: the mesh-wide one, repeated in every node's key so a
`endpoints`, `expose`, `reach`, `ports`) as two layers: the mesh-wide one, repeated in every node's file so a
node reads its own key alone, and the node's own, with a mark when the node has a layer that holds no placement,
since an empty layer still carries the module's config values; and, for a module in the settings form only,
the config values a person set in each of those layers;
the config values a person set in each of those layers (on the day of the seed the media modules of one node);
- its pins by provision, and the seats its modules hold.
A change is made only by a **proposal** through the seat's verb `propose`, which takes the reason and a list of
changes (place a module, take one off, pin a provider, hold a seat, set a decided field of a node, set a
module's config placement or values); who proposes is the caller as the runtime proves it. The holder lays the
changes over the current keys, names the revision of each, and refuses at once, naming every finding, what
mesh-sdk's placement check refuses: a module the indexer does not hold or a node not enrolled, two modules of one
node on one host port, a pin, binding or holding naming a provider not placed where it says, a seat held once
for the mesh held twice, two nodes with one address and more than one hub. Otherwise it writes the proposal and
one candidate for each node whose key it changes, and the nodes answer. `withdraw` deletes both. Until the lease
write only assignments and pins are proposable, since the controller composes ports, bindings and holdings from
its own and the holder calls its `assign`, `unassign`, `pin` and `unpin` after each approved proposal to keep its
assignments equal; while they disagree with the placement, the assigner withholds the wants of each module that
differs and raises a condition naming it. The state `placement` keeps 64 revisions of each key and the seat's
stream keeps its events, each carrying the proposal whole, the verdict and the warrant, so the history of a node
or a module is answered from the seat's verb `placement` (ADR 0567).
- **A node answers a proposal before it is approved.** The node agent watches the seat's candidates for its node.
For each open proposal that touches it, it composes its own node in shadow, from the current placement with the
candidate laid over it and its own inputs, the sealed values and vault boxes it already holds among them, and
applies nothing. It compares the result with what it runs, module by module by composition digest, the bus's
user-list resource left out, and writes the answer into its own have key `<node>.node` under `candidates`, by
proposal: the modules the same, each module that differs with both digests, each module added or removed, and
The catalogue's check refuses a placement naming a module it does not hold or a node not enrolled, two modules
of one node on one host port, a pin, binding or holding naming a provider not placed where it says, a seat held
once for the mesh held twice, two nodes with one address and more than one hub, each named in one answer.
- **A node answers a placement change before it merges.** The node agent watches every placement candidate. For
each open group it composes its own node in shadow, from the trunk's placement with the group's candidates laid
over it and its own inputs, the sealed values and vault boxes it already holds among them, and applies nothing.
It compares the result with what it runs, module by module by composition digest, the bus's user-list resource
left out, and writes the answer into its own have key `<node>.node` under `candidates`, by group: the head
answered, the modules the same, each module that differs with both digests, each module added or removed, and
each refusal. A module the candidate adds is answered as added, naming the values not yet issued for it, since
its credential and boxes are made only once it is placed; a refusal is a value missing for a module the node
runs. It writes only when the answer changes, removes it when the candidate goes, and answers whether or not the
node is placed yet, so before the first placement it answers against what it runs from the controller's
runs. It writes only when the answer changes, removes it when the candidates go, and answers
whether or not the node is placed yet, so before the seed it answers against what it runs from the controller's
compositions. The candidate's key is the node's event, the answer is a key the node alone writes, and the
holder hears that key's change; nothing polls. The nodes that must answer are each node whose key the proposal
changes and each node whose composition the holder finds changed with the current inputs on both sides, not every
placed node, so a node gone for good or asleep blocks only a proposal that touches it. The contract (the state,
its key, the answer, the composition with a given placement and the comparison) is mesh-sdk's (ADR 0567).
- **The holder judges the answers and the operator approves.** The holder computes, by node, the modules the
proposal changes and reads every answer. A proposal names no difference of its own, and the verdict fails on no
difference: each difference, and each one the nodes answered that the holder did not compute, is shown by node in
the brief of the ask beside the proposal's reason, and the operator approves what the brief shows. The verdict
fails only when a node that must answer answers a refusal, or has not answered within 10 minutes of the
candidate's write, naming the node; a time-limited key per node that must answer carries that bound, so no part
keeps a timer, and the node's later answer judges again. On a passing verdict the holder raises an authorising ask
under its own name, offered on a channel that proves who answered, bound to the proposal and the digest of its
changes. On the operator's Approve it writes each changed key by compare-and-set at the revision the proposal was
laid over, records the warrant, deletes the proposal's candidates and says `placement-changed` for each node. A
key that moved since is not overwritten: the keys already written stand, and the rest is proposed again over the
new keys, judged and asked again. A declined or expired ask ends the proposal and says `placement-withdrawn`.
composition check hears that key's change; nothing polls. `indexer_placement`, served by the status view, also
shows each open group's candidates and every node's answer, and the indexer's `indexer_placement_reread` reads
placement and the candidates again now, writing only what differs. The contract (the state, its key, the answer,
the composition with a given placement and the comparison) is mesh-sdk's (ADR 0559 §4, §6, §7, §8).
- **The node record.** What the machine observes of itself: its tunnel's public key, its capabilities, the
public halves of its sealing and serving keys, the certificate issued for its internal name, its outward links
and networks, and its node-engine's contract. Written at start and when an observed fact changes, only when it
differs (ADR 0555 §3). Whether the hub took its found tunnel over, and which node enrolled under a carried
peer's key, are read from the seat's placement and the records together, never written.
peer's key, are read from the placement and the records together, never written.
- **What a node's account reads to compose.** The grants part's composer gives every node's account these reads,
each read only, and the controller's frozen copy none of them (ADR 0555):
- every node's `<node>.node` of the node-engine's have bucket: the other nodes' records;
- every key of the state `placement` of the seat `mesh-placement`: where modules run and what a person decided of each node;
- every key of the indexer's `placement`: where modules run and what a person decided of each node;
- every key of the indexer's `modules` and `manifests`: the manifests it composes from;
- every key of the builder's `builds`: the manifest of a build its want names when that is not the trunk's;
- mesh-config's `resolved` values of its own node alone, `<node>.>`.
@@ -784,9 +753,9 @@ backup holder backs up nothing, and the have says so.
holds, without these reads, and nodes keep applying while composing their own fails loudly until the next
`take-over`. mesh-sdk's composer test holds the copy, user for user, to every grant of the controller's golden
plus exactly these reads and the bucket-creation advisories of issue 1790.
- **A node composes its own once the seat places it.** Its agent reads its key `placement.<node>` at start:
- **A node composes its own once the catalogue places it.** Its agent reads its key `placement.<node>` at start:
with one it composes its own, without one it applies the controller's compositions as before, so nothing changes
on a node before the first placement is written; it follows the key, and when it appears the agent begins
on a node before the seeded placement file reaches it; it follows the key, and when it appears the agent begins
again composing its own, with no restart. A node never applies a stand-in: a composition that stands
in for a value its input lacks (an own secret, a credential, a vault box, the certificate) is refused whole, said,
and the node keeps running what it runs; the bus's user list alone is held out of the apply, since the bus node
@@ -794,19 +763,14 @@ backup holder backs up nothing, and the have says so.
the node composing it keeps what runs until the vault holds it. Likewise a consumer of `model-access` answered by a
licence record, placed before the vault serves licences (ADR 0550 §3, issue 569), is refused by the same guard and
its node keeps what runs; none is placed today.
- **The order a node takes to composing its own.** One delivery group carries every part: the seat `mesh-placement`
on the assigner, the controller's verb `placement-export` (as it is) and the controller's pin only, the node-engine
(its record and its own composer), the vault's `own`, the grants part's reads, and mesh-config's reading of
placement. The grants part's new reads reach the nodes before the node-engine watches the seat's states. Nothing
changes on a node by that delivery alone, since no placement key exists yet: the node records appear, and the
grants part's list gives the nodes their reads. Then the take-over: the seat's verb `adopt` asks the controller's
`placement-export` and proposes its answer whole as the first proposal. Every node composes it in shadow and
answers that what it would run is the same, because a node with no key is judged against what it runs from the
controller's compositions; the operator approves once, and the holder writes every node's key. Before it writes, it
asks the export again and refuses the write if the answer changed. Each node agent then begins composing its own,
and the assigner writes its wants as the build alone. Until the lease write the operator's `hand-back` deletes
every key, the history kept, and every node returns to the controller's compositions; after it, it is answered
as gone (ADR 0567).
- **The order a node takes to composing its own.** One delivery group carries every part: the controller's verb
`placement-export`, the node-engine (its record and its own composer), the vault's `own`, the grants part's reads,
mesh-config's and the assigner's reading of placement, and the indexer writing it. Nothing changes on a node by
that delivery alone, since no placement file exists yet: the node records appear, and the grants part's list gives
the nodes their reads. Then the seed: one catalogue pull request writes each node's placement file from the
controller's export, and its composition check passes only when every node it places has composed it in shadow
and answered that what it would run is the same (ADR 0559 §1); only then is it merged. The indexer writes `placement`, each node begins composing its own, and the assigner
writes its wants as the build alone.
- **Composed again on events, never on a timer.** A changed input at the same fingerprint (a setting or given
value, a secret rotated or moved, a credential issued, a provider's contribution, a peer added to the hub) is
composed again when its event is heard, and applied as a revision of what runs (ADR 0547 §2).
@@ -825,16 +789,14 @@ backup holder backs up nothing, and the have says so.
seconds, so a list written and not loaded within a minute is stuck (ADR 0547 §7). Until the operator's
`take-over` the grants part runs in shadow, and the bus node takes the list from the bus module's composition
the running system writes for it, the user-list resource alone (ADR 0547 §8).
- **Memberships and assertions.** The grants part keeps each module's membership in its state `memberships`, one
key `<node>.<module>` written only when it differs, where the runtime reads it from L7's delivery; it also
publishes it on the assignments stream, read by no one from then, until the lease write (ADR 0571 §10). It
asserts every bucket, log and stream a manifest declares when a new manifest digest is registered, with its
limits; memberships are published from the first milestone
- **Memberships and assertions.** The grants part publishes each module's membership on the assignments stream
only when it differs from the one last published, and asserts every bucket, log and stream a manifest declares
when a new manifest digest is registered, with its limits; memberships are published from the first milestone
(the operator's one grant, 22:31 on 2026-10-25), while before `take-over` assertions are composed and compared
and nothing is asserted (ADR 0547 §5).
- **The want names the build.** A want `want.<node>.<module>` names the fingerprint, the artifact's digest, the
source revision and whether the module holds data; the node's own want `want.<node>.node` names the node
(ADR 0547 §2). A want to a node the catalog places says so: it carries `self` and names no composition,
(ADR 0547 §2). A want to a node the catalogue places says so: it carries `self` and names no composition,
since the node composes it; every other want still names the one composition it allows, so nothing written
before (the controller's bus upgrade among it) reads a want differently. A person's yes to a move the node
judged is written into the want as `yes`, the digest of the composition allowed (ADR 0547 §3).
@@ -918,9 +880,9 @@ and `delivered` events.
*ADR 0408, ADR 0429 §17, ADR 0539 §4, §12, §13.*
From release 6, module, source revision, build, want and have are one chain, each link answerable from the next
(ADR 0429 §17). Placement is bus state of the seat `mesh-placement`, held by the assigner, and no repository holds it (ADR 0567); the controller's
assignments bucket is kept equal to it by the holder until ADR 0547's third milestone and is a source of nothing from then
(ADR 0549 §6). The rollouts bucket is kept read-only with its history: no rollout is written after release
(ADR 0429 §17). Placement is the catalogue's, read onto the bus by the indexer (ADR 0549 §1, §2); the controller's
assignments bucket is kept equal to it until ADR 0547's third milestone and is a source of nothing from then
(ADR 0549 §5, §6). The rollouts bucket is kept read-only with its history: no rollout is written after release
5's last plan (ADR 0539 §12).
| Link | Names | Written by | Bucket |
@@ -928,10 +890,9 @@ assignments bucket is kept equal to it by the holder until ADR 0547's third mile
| build | its module, repository and commit, its reuse key, what it published: an artifact by digest or a package release by name and version | the controller | `mesh-controller_builds` |
| want | the fingerprint a node should run of a module, and the digest of the one composition it allows; the node's own want names its node composition | the assigner | `mesh-delivery-assigner_wants` |
| have | what the node runs of a module: the fingerprint, the composition's digest, the phase and the health | the node agent | `node-engine_have` |
| placement | each node's modules with their pins, host ports and bindings, and what a person decided of the node, as one key per node | the assigner, the holder of the seat `mesh-placement`, on the operator's Approve of a proposal (ADR 0567) | the seat's state `placement` |
| placement proposal | while a change is open: its changes, the revision of each key it was laid over, the nodes' answers, the verdict and the ask, beside the chain and never composed into what runs | the assigner | the seat's state `proposals` |
| placement candidate | while a proposal is open: one node's placement as the proposal lays it, which that node composes in shadow and answers | the assigner | the seat's state `candidates` |
| assignment | until ADR 0547's third milestone: the module and node, pinned or not, ended or not, written by `assign` and `unassign` after an approved placement change, so that it equals the placement (ADR 0567) | the controller, called by the holder | `mesh-controller_assignments` |
| placement | each node's modules with their pins, host ports and bindings, from the catalogue's placement file of that node at each trunk merge of the catalogue | the indexer | the indexer's state `placement` |
| placement candidate | while a catalogue pull request that changes placement is open: one node's placement file at its head, beside the chain and never composed into what runs (ADR 0559 §3) | the indexer | the indexer's state `placement-candidates` |
| assignment | until ADR 0547's third milestone: the module and node, pinned or not, ended or not, written by `assign` and `unassign` after a merged placement change, so that it equals the catalogue (ADR 0549 §5) | the controller | `mesh-controller_assignments` |
| rollout | read-only from release 6: each plan of releases 1 to 5, its tiers, the builds it carried and the assignments it changed | nobody from release 6 | `mesh-controller_rollouts` |
Beside the chain, on the bus too: deliveries and groups (the gate's `deliveries` and `groups`, ADR 0239 §37), one
@@ -947,9 +908,9 @@ at its first start of release 6 (ADR 0539 §13).
controller's store holds builds, rollouts, batches or assignments, so no record mirror,
audit or healer exists for them; the nodes, settings, declared-config and predefined-keys mirrors stay as
ADR 0289 and ADR 0356 have them. A module reads a bucket by naming it in `reads`. The assigner, the grants part,
mesh-config and the node agents read placement from the seat `mesh-placement`'s state, on its watch; from ADR 0547's second
mesh-config and the node agents read placement from the indexer's state, on its watch; from ADR 0547's second
milestone no part but the controller reads the assignments bucket, and the assigner until the third
(ADR 0567).
(ADR 0549 §3).
- **No key of the chain is removed.** Each bucket keeps up to 64 earlier revisions of each key as its history. A
value is never cut to a summary and stays under the bus's largest message. The bus's storage is declared as data
protected by a backup, so it is backed up nightly and before a delivery moves the bus.
@@ -1063,7 +1024,7 @@ way is written down before the switch (ADR 0526, which ADR 0522 §6 asked of R4g
What the build proves before this design is implemented (research 055's direction):
1. A feature across the controller and the catalog with a new seat verb is delivered as one group, with no
1. A feature across the controller and the catalogue with a new seat verb is delivered as one group, with no
`after:` line, one composed check and one Approve: its merges move one source revision, each module it changes
is built once, and every want naming one of those fingerprints is written by the assigner's six rules, with
no order written by anyone.
@@ -1090,9 +1051,9 @@ What the build proves before this design is implemented (research 055's directio
newer fingerprint once its rules hold, a fingerprint nobody wants any more is never applied, and nothing is
released by a person or held in the gate.
12. No table in the controller's store holds builds, deliveries or assignments: builds are the builder's keys,
wants the assigner's, haves the node agents', and placement the seat `mesh-placement`'s, held by the assigner,
with the controller's assignments bucket kept equal to it by the holder until ADR 0547's third milestone
(ADR 0567).
wants the assigner's, haves the node agents', and placement the catalogue's placement files, read onto the bus
by the indexer, with the controller's assignments bucket kept equal to them until ADR 0547's third milestone
(ADR 0549).
Tests 1 to 12 are stated as they hold from release 6. Tests 1, 3, 7, 8, 9, 10 and 11 were first proved by
milestones 1 to 5 in the shape of their time, the controller's plans and what it sent (the milestones table keeps
@@ -1151,7 +1112,7 @@ development, the check's share about a minute):
after one failed package runs that package alone; a check whose inputs equal an earlier one's runs nothing
and takes every kept verdict. Measured on the status's line: the tests run and kept at each level, and the
failed test it names.
23. A library or catalog change's composition check composes only the machines it touches, and clones no
23. A library or catalogue change's composition check composes only the machines it touches, and clones no
repository but at most the change's own. Measured in the check's report: the machines composed, against the
machines that run or could run a module the change moves or adds, and the repositories cloned.
24. A pull request carries one required status, and a delivery group one verdict on every member's head, its
@@ -1183,8 +1144,7 @@ From ADR 0548 and ADR 0551 (issue 568):
(ADR 0395 §1, §3, as ADR 0548 §1 and §5 change them): a running check ends only at a new head, and a trunk
move carries a verdict by its package digests.
- Placement written only by the controller's `assign` and `unassign` (ADR 0539 §12, as ADR 0549 §1 changes it):
from ADR 0547's third milestone its tables and verbs are a source of nothing (ADR 0549 §6). The seat
`mesh-placement` is the only writer of placement (ADR 0567).
from ADR 0547's third milestone its tables and verbs are a source of nothing (ADR 0549 §6).
- A group's order: `after:` lines, the ranked rules and the order of the repositories' names (ADR 0419, which
supersedes ADR 0249).
- Delivery member by member, a stop of a member after a failed one, and the own path's separate batch for a
@@ -1227,7 +1187,7 @@ release reports the controller's code lines against research 063's baseline (acc
| 1 | A merge during a running delivery is never dropped and needs no person's release; the merge window lives on the bus; a rollout that shares nothing with an open one runs beside it | 8, 9, 11 | mesh-controller | 973 (with the rest of 525) |
| 2 | A ready mark is acted on within seconds, and each pull request has at most one waiting check | 5, 6, 7 | mesh-controller, mesh-catalog (mesh-delivery, gitea) | 974, 975, 976 |
| 2b | build-agent and route-proxy live whole in mesh-controller (ADR 0420, accepted 2026-10-17): the controller accepts a reviewed move, mesh-delivery announces each merge with its Approve, and the two modules move; built as two releases before the rule that a milestone is one release | — | mesh-controller, mesh-catalog (mesh-delivery, build-agent, route-proxy) | release 1: 1025; release 2: 1019 |
| 3 | A feature with a new seat verb across the controller and the catalog is one group, one batch, one delivery plan and one Approve: no order lines, no ready mark, each module built and sent once | 1, 6 | mesh-controller, mesh-catalog (mesh-delivery, gitea) | 977, 978, 1022, 895 (with 1046 folded into it: unit and integration tests split, each level within its bound); 605 (1021 closed as its duplicate) is resolved by this build (ADR 0419 §7) |
| 3 | A feature with a new seat verb across the controller and the catalogue is one group, one batch, one delivery plan and one Approve: no order lines, no ready mark, each module built and sent once | 1, 6 | mesh-controller, mesh-catalog (mesh-delivery, gitea) | 977, 978, 1022, 895 (with 1046 folded into it: unit and integration tests split, each level within its bound); 605 (1021 closed as its duplicate) is resolved by this build (ADR 0419 §7) |
| 4 | A delivery backs up the data it moves first, and a failed backup holds it and is said | 3 | mesh-controller, mesh-catalog (mesh-delivery, the backup seat's holder) | 980, split into 1065, 1066, 1067, 1068 |
| 3 fix | A group approved together merges in one pass and goes out in one delivery plan, also when its members' tests read each other's repositories | 1 | mesh-catalog (mesh-delivery, gitea) | 1082, 1049, 1047, 1083, 1079 (1084 closed as its duplicate) |
| 5 | One read of a module on a node names the commit, build and rollout that put it there | 10, 12 | mesh-controller, mesh-catalog (mesh-config, nats) | 981 |
@@ -1245,8 +1205,8 @@ release reports the controller's code lines against research 063's baseline (acc
| R3c | The forge bridge, the indexer and the teller run beside today's mesh-delivery and write their keys; the view shows modules against the controller's source fingerprints; nothing is merged by them yet (ADR 0437) | 15 in part | mesh-delivery (forge bridge, indexer, teller, view), mesh-catalog (the forge module) | 1235 (the forge bridge writes pull messages on FORGE), 1236 (its merged message, with the trees of the merge commit), 1237 (its messages for the pull requests open at its start and each reconnect), 1238 (the indexer writes the candidate groups), 1239 (the indexer writes the modules' fingerprints at a trunk merge, and 1209: none for an image that compiles a Go program from a context), 1240 (the teller runs and carries nothing yet), 1241 (the view shows the candidate groups and each module's fingerprint against the controller's); in the forge module: 1234 (a merge commit's trees in one call, and the head branch in the open pull requests); one delivery group, mesh-catalog first. Its prepare step found three orders: the forge had no tool for a commit's trees, so the forge module joins the milestone as a member of its group; and mesh-sdk shipped two layouts before it, the controller's build record (1223) and a build's directory and a pull request's module directories (1233). It also found that the bridge's FORGE is a stream the others hear by `consumes`, not a bucket they `read`; that the broker test of the three credentials goes into R3d with the gate's (its issue is R3d's); and that the parts hand-write their bus connection until mesh-sdk gives one (1242): facts for ADR 0437's next insight, with R3d's |
| R3d | The gate's keys can carry what the forge needs and the controller answers a what-if as data, so the gate has what it reads before it runs; a failed delivery plan asks the operator Retry on the phone, and a seat whose holder is assigned nowhere passes to its only claimant; the operator sees only the Retry notice (ADR 0450, ADR 0449) | — | mesh-sdk, mesh-controller | in mesh-sdk: 1321 (a merge key carries the Approve's heads), 1322 (an owed act carries a status's target, a commit without a pull request, two comments), 1323 (a name a key cannot hold is escaped); in mesh-controller: 1324 (what-if as data), 1325 (the build seat reads the gate's pull keys), 1326 (the gate's buckets writable by its runner only), 1243 (the three parts' credentials); and, delivered first as feature no-terminal (ADR 0449): 1317 (the seat passes to its only claimant), 1314 (a failed delivery plan raises a condition), 1315 (Retry offered and performed), 1316 (a refused answer is told). Its prepare step found four things the gate reads that ship before it, which cut the former R3d in two (ADR 0450), and two steps on the path that needed the operator's terminal (ADR 0449) |
| R3e | A group reaches the mesh as one source revision written by the gate; the merge's Approve is asked by the teller and answered on the phone until issue 953; the view shows the open asks; today's module mesh-delivery is carried over into the gate when it first holds the seat and ends; no step needs the operator's terminal (ADR 0437, ADR 0450) | 15 | mesh-delivery (gate's parts: forge bridge, teller, indexer, view), mesh-catalog (the gate, mesh-delivery removed, mesh-issues), mesh-tools (the tool runner's waits) | opened when it is prepared, from R3d's survey; the controller's deletions it makes possible go to release 4 (their issue is R4g's) |
| R4a | The shared layouts of release 4 are on mesh-sdk's main before their users pin them: the catalog, the validator and the composition over a plain input, the fingerprint's new form, and the key layouts of the builder, the checker, the kept verdicts, the manifests and the candidates; the node-engine reports the user namespaces capability (ADR 0453 clauses 1, 2, 3) | — (2 needs it) | mesh-sdk, the node-engine (`mesh-host`) | in mesh-sdk: 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1298 (the composition over a plain input); in the node-engine: 1308 (the user namespaces capability) |
| R4b | mesh-controller and the node-engine use the catalog, the validator and the composition from mesh-sdk and delete their copies; the controller composes every node both ways on the live facts and the two agree before this is delivered; it writes the grants and credential bindings the assigner reads; its unit level meets its bound (ADR 0453 clauses 1, 3) | — | mesh-controller, the node-engine (`mesh-host`) | 1375 (to be split when it is prepared), 1337 (the unit level's bound) |
| R4a | The shared layouts of release 4 are on mesh-sdk's main before their users pin them: the catalogue, the validator and the composition over a plain input, the fingerprint's new form, and the key layouts of the builder, the checker, the kept verdicts, the manifests and the candidates; the node-engine reports the user namespaces capability (ADR 0453 clauses 1, 2, 3) | — (2 needs it) | mesh-sdk, the node-engine (`mesh-host`) | in mesh-sdk: 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1298 (the composition over a plain input); in the node-engine: 1308 (the user namespaces capability) |
| R4b | mesh-controller and the node-engine use the catalogue, the validator and the composition from mesh-sdk and delete their copies; the controller composes every node both ways on the live facts and the two agree before this is delivered; it writes the grants and credential bindings the assigner reads; its unit level meets its bound (ADR 0453 clauses 1, 3) | — | mesh-controller, the node-engine (`mesh-host`) | 1375 (to be split when it is prepared), 1337 (the unit level's bound) |
| R4c | The new parts run dark beside today's: the indexer writes the manifests by digest and the fingerprints in the new form beside today's (its own, first delivery), then the build modules (the Go build module, the Node and TypeScript build module: toolchain, sandbox, fetch, digest, test, build), the builder, the checker writing `mesh/check` as a status the forge does not require, and the assigner's composition check writing into the checker's report; the build seat's accounts are decided by a record before the build modules' pull requests are opened (ADR 0453 clauses 4, 6, 7, 8, 11, 12, 16; ADR 0465 §4); the builder is ADR 0478's. It is delivered in ADR 0465 §5's order: mesh-sdk's layouts; the forge bridge and the indexer with the controller's facts in format 2; the build modules; the builder; the checker; the bus user list's composer moved into mesh-sdk; the assigner's composition check; the fetch and run steps. The build modules each run as an account of their own with a forge key known read-only on one forge user (ADR 0466); each builds with the node's installed toolchain at the agreed version and does not refuse on a node with another (ADR 0477, issue 1572), answers a long step with its id and finishes it on a key, is called on a named machine, and runs on the four nodes that run the build agent today (ADR 0469) | 22, 23 (measured on the dark run, proved in R4f) | mesh-delivery (indexer, builder, checker, assigner's composition check), mesh-catalog (build modules, ssh-client, gitea), mesh-controller (the build agent's whole-check tests, the script's steps as tests), mesh-sdk | 971 (its record first), the assigner's composition check (its issue is R4g's), 1309 (the fetch and run steps), 1287, 1288, 1289, 1290, 1291, 1292 (the checker; the build modules run the tests), 1251; the steps of 1293, and of R4g's issue on the old checks, that are no test become tests in R4c to R4e; the rest opened when it is prepared |
| R4d | Done (2026-10-21 to 2026-10-23): three modules stopped being built from a Dockerfile, each its own release step: route-proxy, nats and mail's autoconfiguration module (automx, now a Go program) run a pinned stock image with their Go program mounted as an archive (ADR 0480); the image kind is allowed again, built rootless by the image build module (ADR 0487); route-proxy and nats were one delivery group, nats upgraded once on the bus at the operator's moment after a verified snapshot, automx went first alone (ADR 0497). n8n left R4d by the operator's word and keeps its own Dockerfile until R4g (ADR 0506) | — | mesh-catalog, mesh-controller (route-proxy), the node-engine (`mesh-host`: the container groups field, kept unused) | issues 1632 (automx), 1633 to 1635 (route-proxy), 1636 to 1638 (the container groups field), 1640 to 1642 (nats), all resolved and delivered; 1639 and 1755 (n8n) wontfix |
| R4e | The controller asks no build: a build is keyed by its fingerprint, the new form becomes the only one as one group, and a merge builds nothing its check built; builders fetch over SSH, so an SDK change ships with its users; the vendored copies leave mesh-controller and mesh-sdk; delivered only once the builder's artifact digests equal build-agent's (ADR 0453 clauses 2, 4, 5, 6) | 16, 2, 4 in part | mesh-controller (builds are no longer asked), mesh-delivery (builder), mesh-sdk | the installed toolchains (their issue is R4g's), 1304, the controller asking no build (opened when it is prepared) |
@@ -1254,13 +1214,9 @@ release reports the controller's code lines against research 063's baseline (acc
| R4g | The last milestone of release 4 and release 5 as one, with one switch-over and no rehearsal (ADR 0524 §1): builds and checks are asks on streams that each build node pulls when it has a free slot, deliveries first, with nothing holding checks and no controller queue (ADR 0525); the assigner is made with its composition check, writes each node's wants compared with what the controller sends and every difference, and the view answers the asks that need no proof of who answered (ADR 0528, ADR 0437 §10); no module's fingerprint takes the builder (ADR 0530); the scripts of the repositories a module is built from, build-agent with its socket and Dockerfile code, the toolchain images and the controller's build code go, and the builder delivers its own module, the build modules and the controller from its keys (ADR 0453 clauses 5, 8, 10; ADR 0524 §5, §7); the ways to deliver without the builder are recorded (ADR 0526); hq's four checks are Go tests (ADR 0527); each repository's script goes (ADR 0529); the word for the composition check's predecessor is struck. Its pull requests are opened only after five readings, the revert path read, not shown on a test merge (ADR 0524 §3) | 4 (from R4g mesh-build-image, rootless, is the only part that builds an image: ADR 0487 §7), 17, 24 (no `merge-check.sh` in a repository a module is built from) | mesh-sdk, mesh-controller (build-agent removed), mesh-delivery, mesh-catalog, mesh-tools, mesh-tools-go, the node-engine (`mesh-host`), mesh-media-catalog, okioki, hq | 1887 (the flow), 1297 (the assigner), 1295 (`merge-check.sh` goes), 979 (closed with the toolchain images), 1296 and 1303 (hq's check, the old word struck), 1299, 1305, 1294, 1318 (the controller's deletions) |
| R6 | Each node applies from its wants the compositions its own node agent composes (ADR 0547 §2; as released it applied the controller's signed ones), a new version reaches the module's canary first and the rest once it is healthy there, a move a person must allow is asked before its want, the view says what is moving, settled, stuck and delivered, and the plans are deleted from the code; switched over by release 5's last plan (ADR 0539) | 18, 19, 20 | mesh-sdk, the node-engine (`mesh-host`: the node agent), mesh-controller (plans deleted; as released also compositions and credentials, which ADR 0547 moves to the node agents, the grants part and the vault), mesh-delivery (assigner, watchman, view), mesh-catalog (the gate, the messenger, the issue tracker, the backup holder), hq | 1942 (the planner puts the node-engine and the controller in one tier for the switch; delivered first), the bus's user list granting what release 6 uses (delivered first, under L1's issue), 1943 (mesh-sdk: the composition's layout and signature, the want's digest, `mesh-hand-compose`, the verb tables without the plan verbs), 1944 (the node agent), 1946 (the gate keeps the merge's states; the messenger and the issue tracker hear the view), 1906 (the assigner's seventh rule and its asks, the watchman's pulses for wants, the view's wants and events), 1130 (hq) |
| L1 | The controller leaves build and delivery, first milestone: the bus node follows the user list alone, the grants part composes in shadow and the vault holds the bus accounts' hashes, so the operator's take-over can make the grants part's list the one loaded (ADR 0547 §8, §11; ADR 0550 §4) | — | mesh-controller, mesh-delivery, mesh-catalog (the vault, the grants part), the node-engine (`mesh-host`), mesh-sdk | 1945 |
| L2 | Nodes compose: each node agent composes its own modules, and no node applies a composition made elsewhere but the bus's user list (ADR 0547 §11); where modules run is bus state held by the seat `mesh-placement` on the assigner, changed by a proposal the nodes answer and the operator approves, and no repository names a node or holds what one installation decides (ADR 0567) | — | the node-engine (`mesh-host`), mesh-sdk, mesh-delivery (the assigner as the seat's holder), mesh-catalog, mesh-controller (its pin only) | 1126 |
| L3 | Nothing is left with the controller: the vault mints bus accounts and signs, the grants part writes grants, the try is the checker's own verb, placement is read from the seat `mesh-placement` alone, each node-engine holds the node seat `node-engine`, judges `root-free` for its node and asks the operator itself for a hand-over or an access act, acting on the warrant with nothing signed (ADR 0563), and, once L4 to L7 are delivered, the lease write passes the lease to a holder that is no controller, made right after the delivery that drops every listed read of the controller but the three ADR 0571 §13 keeps to the write itself (ADR 0547 §11, ADR 0549 §5, §6, ADR 0550 §2, ADR 0571 §13); the rest of the controller's code lines is the issue of L2 | — | mesh-controller, mesh-sdk, the node-engine (`mesh-host`), mesh-catalog (the vault, the grants part, the messenger), mesh-delivery | 774 |
| L2 | Nodes compose: each node agent composes its own modules, and no node applies a composition made elsewhere but the bus's user list (ADR 0547 §11) | — | the node-engine (`mesh-host`), mesh-sdk, mesh-controller | 1126 |
| L3 | Nothing is left with the controller: the vault mints bus accounts and signs, the grants part writes grants, the try is the checker's own verb, placement is read from the catalogue alone, and the lease passes to a holder that is no controller (ADR 0547 §11, ADR 0549 §5, §6, ADR 0550 §2); the rest of the controller's code lines is the issue of L2 | — | mesh-controller, mesh-catalog (the vault, the grants part), mesh-delivery | 774 |
| M1 | A milestone is a record of the issue tracker: each row of a design's milestone table is a milestone record its issues are linked to by `issue-tracker.link`, an issue to at most one, and pull requests and commits to issues the same way; the knowledge base's `progress` reads the milestones from the tracker, not from this table (ADR 0493 §1 to §4) | — (ADR 0493 §13 in part) | hq, mesh-catalog (mesh-issues, mesh-knowledge) | 1781 |
| L4 | Health leaves the controller (ADR 0564): the condition keeper holds the seat `mesh-conditions` and keeps every open condition from the observers' findings, saying today's condition events; the watchman holds `mesh-watchdogs`, its watchdogs pulses, reads each node-engine's node health report, and keeps the dead letters from the lease write; `mesh-self-check` runs the probes, `root-free` among them from L7 (ADR 0571 §11), and says `checked`; of the probes still deferred, D13, D8, D-fill and DA are built and the rest are not, their kinds ending with the controller at the lease write (ADR 0572); `mesh-healers` heals on events and acts only once the lease names no controller; `status` is the view's derivation; built dark, compared, then switched by the operator's take-over; the bus module gains a tool that removes one named consumer, which the lease write uses (ADR 0547 §11) | — | mesh-sdk, mesh-catalog (mesh-conditions, mesh-self-check, mesh-healers, the router, mesh-watcher, nats), mesh-delivery (watchman, view, the grants part) | 1952 |
| L5 | Control records leave the controller, and the controller is not changed (ADR 0565): mesh-config keeps each module's declared config in its state `declared` and publishes the predefined keys, which the indexer reads from the forge bridge's messages with each open pull request's config candidate; config switches to that source only on the operator's take-over, refused while `compare` shows a difference; each node agent writes its node's declared packages (the node seat `node-engine`, verb `packages`); the assigner holds the new seat `mesh-wants` with the upgrade policy a person chose and the bus step; the hand-act log (`hand-act`, `hand-acts`, `warranted`) is the stream of the new seat `mesh-hand-acts`, held by a new module in mesh-catalog, which every recorder calls from this delivery with no switch; the checker and the assigner's composition check read the held values of their pull requests from mesh-config's state `kept`; from the lease write a trusted config value is set only on the operator's Approve; `settings`, `move-config` and `move-placement` end with the lease and nothing replaces them. each person's six policy choices are set through `mesh-wants.upgrade` and recorded as hand acts, `upgrade compare` and the assigner's finding `policy-split` show any split, and the lease write waits for `compare` to be empty (ADR 0565 §8). One delivery group, built before the lease write of L3, which waits for the group delivered, the six choices set, mesh-config's `compare` empty and its take-over done, `mesh-wants.upgrade compare` empty, and the readings of ADR 0565 §12 (ADR 0565 §14; ADR 0348 §9, ADR 0502 §2, ADR 0539 §14, §15, ADR 0547 §11) | — | mesh-sdk, mesh-catalog (mesh-config, mesh-hand-acts, pacman, the forge module, claude-code, mesh-knowledge, mesh-conditions, mesh-healers), mesh-delivery (indexer, assigner, checker, grants part, forge bridge), the node-engine (`mesh-host`); mesh-controller is not a member | 1953 |
| L6 | Provisioning leaves the controller (ADR 0570): every provision is one key of the seat `mesh-provisioner`, held by mesh-provisioner, holding a reference and never a value, and each placed module's provisioning plan a key of its state `plans`, both composed from placement, the wants and the manifests on change events; each provider acts once all a provision depends on is provided; the assigner moves a want only once its build's provisions are provided; a provision no want needs is retiring, and its account or credential is removed only on the operator's Approve, its data never; the vault mints each provision's secret sealed to the consumer's and the provider's node, and the consumer's node-engine writes it; a wait for a person is a provision's state, which the watchman reads; bus permissions stay with the grants part (ADR 0349, ADR 0230, ADR 0547 §11) | — | mesh-sdk, the node-engine (`mesh-host`), mesh-tools (the tool runner's provisioner client), mesh-catalog (mesh-provisioner, the vault, the provisioner client, postgres, keycloak, gitea, route-adapter), mesh-delivery (assigner, watchman) | 1954 |
| L7 | History and records leave the controller (ADR 0571): the builder keeps its build log, one entry at a build's start and one at its end, from which build history and durations are read and the module mesh-catalog asks the builds it missed; the artifact sweep is the seat `mesh-artifact-sweep`, held by artifact-store-tools beside the artifact store, its keep set from the store's own listing, the wants and the build log, sweeping at each build's end from the lease write and recording each deletion in the hand-act log; disk readings are disk-load's own state on every node; who holds which seat is the state `seat-holders` of the seat `mesh-placement`, and tools are found by the tool runners' announcements; what a node runs is its want and its have; the grants part writes `grants.<module>.<fingerprint>` and keeps memberships in its own state; root-free is a probe of the self-check; no manifest but the controller's reads, invokes or consumes `mesh-controller`, but a named list kept to the lease write, so the lease write's check passes (ADR 0189, ADR 0547 §11, ADR 0549 §6) | — | mesh-sdk, mesh-controller (its pin only), mesh-delivery (builder, indexer, assigner, grants part, view), mesh-catalog (artifact-store-tools, disk-load, the backup seat's holder, mounts, nfs-server, minio, mesh-watcher, the messenger, the self-check, the module mesh-catalog, docker), mesh-tools (the tool runner) | 1955 |
An issue is in one row only (ADR 0493 §2): where a row named an issue another row names too, the last row keeps it and the
earlier row says so in words (the operator, 2026-10-26). Milestone M1 makes each row a record of the issue tracker
@@ -1270,14 +1226,7 @@ reads them, and this table keeps its rows without the Issues column (the operato
Milestones L1 to L3 are the three of "The controller leaves build and delivery" above (ADR 0547 §11), given rows on
2026-10-26 so that a reader of this table, the knowledge base's `progress` among them (issue 1947), sees the work that
runs; 1945 is L1's and 1126 L2's only, as an issue belongs to at most one milestone (ADR 0493 §2); R6, delivered,
names the parts of 1945 it carried in words. Milestones L4 to L7 are the homes research 072 proposes for the controller's
other jobs, made milestones after L3 by the operator's yes on 2026-10-28, so their design can start beside L3's work:
L3's lease write waits until all four are delivered (ADR 0547 §11), while the rest of L3 is delivered before them with
its switches off. They are ordered health first, since every later part says its faults through the conditions; then the
control records, provisioning, and the history and records, whose last step is the check that no reader is left. Each
starts with its decision record (issues 1952 to 1955), and all four are decided: ADR 0564, ADR 0565, ADR 0570 and
ADR 0571. Adoption's record is L3's (ADR 0561 §5), and the licences' move
into the vault is decided by ADR 0550 §3 under issue 1126. Release 5 (the assigner's wants, compared only) is not a milestone of its own: ADR 0524 §1 folds it into R4g, whose row above holds
names the parts of 1945 it carried in words. Release 5 (the assigner's wants, compared only) is not a milestone of its own: ADR 0524 §1 folds it into R4g, whose row above holds
it. Release 6 is milestone R6, prepared on 2026-10-24 with ADR 0539. Its prepare step found six repositories where its row first
named three: the gate's code is in mesh-catalog, mesh-sdk holds the composition's layout and the seat verb tables, and hq the design
and the glossary.
@@ -257,7 +257,7 @@ health needs a record of its own.
- **`module check`** refuses a `health` field naming an endpoint the module does not declare under
`listens`, a port or an address, an interval, timeout, count or grace outside its bounds, a tool the module
does not serve, or a `tool` kind with no other kind beside it on the module.
- **The bed.** mesh-lab provides a throwaway container runtime that the catalog's check, run on the build
- **The bed.** mesh-lab provides a throwaway container runtime that the catalogue's check, run on the build
seat (ADR 0237),
uses to start every resource whose declaration or image changed, alone, and read its check. It must say
healthy within its grace. A check that names a provision in `needs` gets no provider on the bed, so there it
@@ -273,7 +273,7 @@ health needs a record of its own.
nothing long-lived and need no declaration.
1. **Liveness first, with no declarations** (Phase A). From then every long-running resource is judged. The
catalog's count of long-running resources without `health` is written down and starts.
catalogue's count of long-running resources without `health` is written down and starts.
2. **The seven modules whose images ship checks** adopt them by name; for the two that were wrong in the
mesh's configuration, the corrected check is what the bed proves. 19 containers are covered at once.
3. **The other container modules, and the containers without an image check in three of the seven,** declare
@@ -384,11 +384,11 @@ health of this design, not yet through the machine's own judgement as §10 does
|---|---|---|---|
| A — liveness and the statement | `mesh-host` (node-engine) | liveness for every long-running resource; restarts counted and kept; the state per resource in the report; the change event and its repetition; nothing restarted on health | the node-engine tests of ADR 0240 rules 1 and 6 pass; every machine's report carries a state for every long-running resource |
| | mesh-controller | the last state per machine; `module.<module>.<machine>.unhealthy` on the second statement, cleared on the first that does not say it; the gate reading the stated health; `node show` and `status` | the controller tests of rule 4 pass; mesh-lab's replay of the crash loop fails its gate within the bound; a module stopped on purpose on the live mesh is raised and cleared |
| B — the field | mesh-controller | `health` parsed on every long-running resource; `module check`'s refusals; the catalog-wide parse | a test per refusal; the whole catalog passes `module check` |
| B — the field | mesh-controller | `health` parsed on every long-running resource; `module check`'s refusals; the catalogue-wide parse | a test per refusal; the whole catalogue passes `module check` |
| | `mesh-host` (node-engine) | the scheduler and the kinds: http, tcp, unit itself; exec and runtime as the container's check; tool through the tool runner | the rule 3 tests pass; the replay of issue 145 raises the consumer within two looks |
| C — the provider hold | mesh-controller | `needs` read against the provider composed for the consumer; the consumer's finding held under the provider's condition; the consumer's gate waiting | the rule 5 test (one provider, three consumers, one condition) passes |
| D — the proof | mesh-lab, mesh-catalog | the bed; the catalog's check starting every changed resource on it; adopted image checks proved | the replay of the studio's false *unhealthy* fails the bed, not a machine |
| E — the migration | mesh-catalog, mesh-controller | the declarations of §9 steps 2–5; the count kept in the catalog and its test; `module check` refusing after the date | the count is zero, or the date has passed and `module check` refuses |
| D — the proof | mesh-lab, mesh-catalog | the bed; the catalogue's check starting every changed resource on it; adopted image checks proved | the replay of the studio's false *unhealthy* fails the bed, not a machine |
| E — the migration | mesh-catalog, mesh-controller | the declarations of §9 steps 2–5; the count kept in the catalogue and its test; `module check` refusing after the date | the count is zero, or the date has passed and `module check` refuses |
| F — the machine's network | `mesh-host` (node-engine), `mesh-controller`, `mesh-catalog` | §10: the five parts judged on two looks and stated; the three conditions said once; the gate waiting on another's; `node show` | the tests of ADR 0241 pass; the drill's replay raises and clears; a VPN connect on the laptop is raised naming its writer and cleared by the reconcile |
| G — the machine's hardware | `mesh-catalog` | §11: the `sensors` module, its tools, the drive reader and the fan watch, both checks declared | its tests pass; `module check` accepts its health; on the first machine both checks are stated healthy |
@@ -448,7 +448,7 @@ open:
composes the field — the endpoint as the port the machine published it on, the defaults written out — only
for a machine whose newest statement says so, and strips it for an older engine, which parses strictly
and would refuse the whole declaration. So the engine rolls out first, then the controller, and the
catalog declares only once that controller runs: until then the composition check refuses a declaration, which
catalogue declares only once that controller runs: until then the composition check refuses a declaration, which
is the protection working.
- **The engine's looks.** http and tcp from the machine to the published port; a redirect is an answer and
is not followed; a certificate is not judged. A unit's readiness is read from the show liveness already
@@ -481,8 +481,8 @@ machines: one condition, urgent, listing all three; their gates wait; after reco
failing is raised), which fails with four conditions when the hold is taken out.
**Phase D — the bed.** In mesh-lab's replays, which every merge check of a module of the graph already runs
from the lab's trunk with the runtime and the change's catalog — so nothing else had to change for the
catalog's check to start it. It proves the resources whose declared check or image the change touches
from the lab's trunk with the runtime and the change's catalogue — so nothing else had to change for the
catalogue's check to start it. It proves the resources whose declared check or image the change touches
(every declared one when it cannot tell), each alone: its literal environment, the files its module writes
in full, an empty place for every other mount, no secret and no provider. A program that stays up while its
check does not see it fails the change; one that does not stay up alone is said and left to the first
@@ -490,15 +490,15 @@ machine's gate; a mesh-built image and a tool or unit check are said. The replay
bound to the address its hostname names, and an image check asking the loopback — fails the bed, and passes
once the hostname is set to every address.
**Phase E — the migration.** The catalog keeps the count in a file of its own, and its merge check fails
**Phase E — the migration.** The catalogue keeps the count in a file of its own, and its merge check fails
a change that raises it and one that lowers it without writing the new number. It starts at 93 long-running
resources. The first batch takes it to 70: the image checks of the seven modules that ship them, adopted where
they read healthy on the live mesh — except the mail antivirus, whose six-minute start is past the bound,
and the mail cache, whose image ships none — and tcp or http on endpoints four services already declare.
Still to do for these phases' "done when": each read on the live mesh once rolled out in order — the
node-engine, then the controller, then the catalog's declarations; the count's first live reading; and
the bed's first run on a catalog change at the build seat.
node-engine, then the controller, then the catalogue's declarations; the count's first live reading; and
the bed's first run on a catalogue change at the build seat.
## As built — Phase F
+4 -4
View File
@@ -29,7 +29,7 @@ shape — what each is for, where its records live, and how they depend on one a
| Domain | Purpose | Its records live in |
|---|---|---|
| **Module** | to say what one module is, in the manifest every other domain reads | the catalog, one manifest per module |
| **Module** | to say what one module is, in the manifest every other domain reads | the catalogue, one manifest per module |
| **Core** | to keep the controller, the bus, the store and every node's engine running and agreed | the controller and the store |
| **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 |
@@ -134,7 +134,7 @@ rule stands with the new word: where a domain's records live in the controller,
on a head word defined twice or also retired. Decision records are never checked. A document that cannot
be reworded at once is named with a date in the check's allowance list; a graduated research effort may
be kept there, as a record of what was said.
- **In the catalog**, its own repository check reads a copy of the words retired for the tools and fails
- **In the catalogue**, its own repository check reads a copy of the words retired for the tools and fails
when what a module's tools show an agent uses one. The copy and the glossary are compared whenever both
are at hand; a change retiring a tools word changes both repositories.
- **Homonyms** are checked by review: the reviewer looks for the bare word in the diff.
@@ -145,8 +145,8 @@ rule stands with the new word: where a domain's records live in the controller,
`node-build-agent`, the licence manager's `release`, the controller's `plan`, `doctor` and queue verbs,
the packet filter module's tool — each in its own repository's change.
- **The descriptions served by the core's own repositories** (the controller's, the node-engine's and the
tool runner's verbs) are outside the catalog and so outside its check.
- **A probe of the served descriptions** in the self-check, once the catalog's check has run a while.
tool runner's verbs) are outside the catalogue and so outside its check.
- **A probe of the served descriptions** in the self-check, once the catalogue's check has run a while.
- **A domain on each decision record**, which would let the reading order be printed per domain; a change
to the record schema, not made here.
- **The reverse rule** — a design defining a word must find it in the glossary — proposed by research 034
@@ -48,7 +48,7 @@ another. A *routing domain* is a domain whose names a resolver sends to one link
|---|---|---|
| the seat `node-resolver` | the controller's set | a node seat, its verbs `routes`, `route`, `unroute`, required of every holder |
| the stepping back | the controller's composition | where a module holds `node-resolver` and renders `/etc/resolv.conf`, the uplink holder's rendering of it is left out |
| `systemd-resolved` | the catalog | holds the seat, provides `split-dns` at the machine's reach, writes the file and resolved's drop-in, runs the guard |
| `systemd-resolved` | the catalogue | holds the seat, provides `split-dns` at the machine's reach, writes the file and resolved's drop-in, runs the guard |
| the guard | `systemd-resolved`'s process, as root | keeps the file the module's, keeps an outside write, serves the verbs on the machine's socket |
| the adapter | `forticlient`'s process, as root | reads the client's write, routes its domains over its tunnel, takes the write, takes the route away when the tunnel goes |
+2 -2
View File
@@ -56,8 +56,8 @@ The glossary holds the three words.
| part | where | does |
|---|---|---|
| seats `node-nfs-server` and `node-mounts` | the controller's closed set | node seats, capacity 1, with their verbs |
| `nfs-server` | the catalog | holds `node-nfs-server`, provides the provision, writes its export file, runs the service, opens 2049 to the private network |
| `mounts` | the catalog | holds `node-mounts`, writes a unit pair per share, keeps each share's state, serves the tools |
| `nfs-server` | the catalogue | holds `node-nfs-server`, provides the provision, writes its export file, runs the service, opens 2049 to the private network |
| `mounts` | the catalogue | holds `node-mounts`, writes a unit pair per share, keeps each share's state, serves the tools |
| the binding | the controller | gives a consumer the server's name; the consumer asks the server's seat for each share's path and access over the bus |
| the credential | the vault | per SMB source, the `mounts` module's own secret `smb-password-<source>` (a member of the family `smb-password-*`), issued outside the mesh and given with `secret accept` or typed at the desk prompt, unsealed on the node as a file; the username is the user part of the source's entry in the setting `sources` |
+2 -2
View File
@@ -115,7 +115,7 @@ the issue's worker, who works on it, as the worker states it
kind `depends-on` (§ 10).
**The registered list of owners** is a setting of the module, changed through the controller's settings verb like any
other module setting. Slice 1 starts it with the repositories in hq's `00-META/repos.md` and the modules the catalog
other module setting. Slice 1 starts it with the repositories in hq's `00-META/repos.md` and the modules the catalogue
registers.
**An issue that has a folder in hq** kept its status in that folder's frontmatter until the import of slice 3 ran;
@@ -171,7 +171,7 @@ event carries a secret, and an issue holds none.
| any other status | no `by` and no `take-over`; the move ends the work |
| `located` | `located-in` names at least one registered owner |
| `resolved` | the issue is at `fixing` (or already `resolved`, to correct what it holds); `located-in`, and `fixed-by` saying what fixed it |
| `resolved`, for a core issue (its `located-in` names the controller, the node-engine, the tool runner, the SDK, or the catalog's bus, forge or build agent) | `replay` (an id from mesh-lab's replays register) or `replay-none` saying why none is possible (ADR 0237) |
| `resolved`, for a core issue (its `located-in` names the controller, the node-engine, the tool runner, the SDK, or the catalogue's bus, forge or build agent) | `replay` (an id from mesh-lab's replays register) or `replay-none` saying why none is possible (ADR 0237) |
| `wontfix` | a reason |
A refused move changes nothing and says which rule refused it. The rows for `diagnosing`, `fixing` and `resolved` are
+40 -415
View File
@@ -2,47 +2,34 @@
layer: to-be
status: in-progress
code: [mesh-catalog, hq]
updated: 2026-11-02
updated: 2026-10-18
decisions:
- "ADR 0290"
- "ADR 0321"
- "ADR 0423"
- "ADR 0424"
- "ADR 0592"
- "ADR 0593"
---
# 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.
**Amended, 2026-11-02** (ADR 0592, on the operator's word: "our knowledge base must be nats-native"; "don't use
file references or local files anymore"). The records leave the store for the bus: each record's head in the
module's state, its versions on a stream, its sections' bodies once each in an object bucket by digest. A record is
written one section at a time in a draft, and reviewed one section at a time. A section is a part with a title,
never a file. § 1, § 2, § 4, § 5, § 8.1 and the fifth slice of § 9 say how; until it is built, the module keeps its
records in the store as before.
**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 in titled sections, with every version kept, all of it on the
bus. A record is written section by section, checked when it is submitted, reviewed section by section by the
knowledge base's own judgement and accepted there; the operator's Approve is asked when a record needs it. Agents
ask what holds, and why, in one bounded call.**
(ADR 0290 and ADR 0592,
its clauses with their own status, and its reasoning, with every version kept. A record is proposed, checked when it
is written, reviewed by the knowledge base's own judgement and accepted there; the operator's Approve is asked when a record needs
it. Agents ask what holds, and why, in one bounded call.**
(ADR 0290,
from research 045.)
A **clause** is one numbered statement a decision record makes, with its own status. A **section** is one titled
part of a record's reasoning. The **knowledge base** is the module that holds the records; the seat it holds is
`knowledge-base`. The glossary holds these words.
A **clause** is one numbered statement a decision record makes, with its own status. The **knowledge base** is the
module that holds the records; the seat it holds is `knowledge-base`. The glossary holds both words.
**Acceptance.** A session started in any folder on any node asks "what holds about the merge window, and why" and
gets, in one call under 4 KiB, the clauses in force, the records they come from, what changed them since, and how to
read the reasoning behind each. A session writes a research effort of more than 100 KiB one section at a time, a
session on another node takes the draft over and submits it, and every section is judged.
read the reasoning behind each.
This document designs the first slice in detail, tracked by issue 425, and the fifth, the records on the bus
(ADR 0592). The slices after the first are in
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).
@@ -53,32 +40,17 @@ and the knowledge base works out each design's state (ADR 0423, ADR 0424).
mesh-knowledge (seat knowledge-base, held once for the mesh)
├ its process, launched by the node runtime
├ on the bus, written by it alone (§ 2)
│ state heads one key per record: its fields, clauses, links, sections as title and digest
│ state drafts one key per record being written: its author and its sections as title and digest
│ state asked one key per part asked of the judge and not yet answered: ticket, node, when
│ state names per digest: how many versions and drafts name it, and since when none has
│ state findings the whole-set findings standing now, and the told marks (told.<topic>)
│ stream versions one message per version: who, when, why, its mark, the head as it stood
│ stream reviews one message per part reviewed: the digest judged, the verdict, the findings
│ objects sections every section's body, once, named by its sha256 digest
├ in its memory the search index, built from the bus at start and kept by watching heads
├ verbs ask, get, list, holds, links, history, findings, progress,
│ reserve, open, write, remove, order, submit, drop, take-over, propose,
│ withdraw, retract, import, return
├ a database in the store records, versions, clauses, links; ranked full-text search
├ verbs ask, get, holds, links, history, reserve, propose, import, return
│ served by the control-node's tool runner
├ events reserved, opened, written, removed, ordered, submitted, dropped, taken-over, proposed,
│ part-reviewed, reviewed, accepted, changed, swept, moved, checked, formed,
│ imported, drafted, confirmed, returned
├ 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 claude-code's judged: the judge's answer to a part (§ 5)
└ consumes the delivery module's delivery-done announcement: a design's state (§ 9)
mesh-issues (control-node) the counters for decision and research numbers (§ 6);
the issues' design links, read for a design's state (§ 9)
mesh-delivery (its seat) announces each delivery done, on the bus (§ 9)
claude-code (same node) claude_code_judge: review a section or a head (§ 5), draft and confirm clauses (§ 7);
emits judged with the answer of each ticket (§ 5, a dependency of the fifth slice)
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
@@ -87,86 +59,20 @@ and the knowledge base works out each design's state (ADR 0423, ADR 0424).
```
The module is written in Go against the SDK. It holds no checkout of this repository; the import reads it through
the records module, as the issue tracker's import does. Until the move of § 8.1, its records are in a database in
the store; from the move on, it requires no database (ADR 0592 §1, changing ADR 0290 §7).
**A write is whole or refused, and `submit` commits when its version is stored** (ADR 0592 §10). Every change to
`heads`, `drafts`, `asked`, `names` and `findings` is a compare-and-set on the key's revision, retried at most three
times and then refused with the reason. The commit point of `submit` is the version message stored on `versions`: a
failure before it is stored refuses the call, and nothing is taken. Once it is stored, the call answers that the
version is taken; the head's update against its revision and the event are then completed at once, retried, and at
the next start for any head behind its last stored version. So nothing an author was told was refused takes effect
later. A reservation is a head created only if the key is absent, so two sessions reserving at once get two numbers
or one refusal.
**The search index** is a full-text index in the module's own memory, written with the pure Go library bleve. At
start the module reads every head and the bodies of every current version into it, then keeps it current by watching
`heads`, noting the last revision of `heads` it took in. `ask` and `holds` answer from it, ranked by title, clauses
and sections as before, and refuse with "index not built" while it is built; `get`, `list` and `propose` answer from
`heads` at once (ADR 0592 §11). The integration test measures the start; the estimate is 20 to 40 seconds and about
100 MB of memory for today's 736 records. Nothing but the module writes it, and nothing outside it reads it.
### 1.1 Its tools, health and events
Every piece of state has a tool that reads it, and every act the module does by itself can be done by hand
(ADR 0592 §32):
| Tool | Reads or does |
|---|---|
| read a head | one key of `heads`, whole |
| read a version | one message of `versions`, with its sections' titles, digests and authors |
| read a section | one object of `sections`, by its digest, checked against it |
| read a draft | one key of `drafts`, with its author and since when |
| read a review | the part verdicts of one version, from `reviews` |
| in-flight parts | the keys of `asked`: each part waiting on the judge, its ticket and since when; ask one again |
| told marks | the `told.<topic>` keys; clear one, so it is told again |
| fill | each bucket's and stream's size and count against its cap |
| rebuild the index | the search index, built again from the bus |
| review again | asks one part's review again, for a version still proposed |
| sweep | lists the objects no version or draft names and since when; sweeps them now; dry run |
| integrity | checks every named digest has its object; restores a body from given text once its digest matches |
| form-only | lists, dry-runs and runs the form-only versions of § 2.2 |
| move, and check the move | § 8.1, each with a dry run |
**Health** (ADR 0592 §33) is unhealthy, naming the cause, when a bucket or stream does not answer, when the index is
behind the latest revision of `heads`, when a digest some version names has no object (counted, each named), and
when a bucket or stream is at its cap. It names the source it serves from (the database or the bus) and the result
of the move's last check, and counts drafts idle more than a week, records the form-only check refused, and parts
waiting more than 30 minutes. A part waiting more than 30 minutes is asked of the operator once through the operator
channel, and the ask clears when the part is judged; the module stays healthy meanwhile, because a record waiting on
its review is not the module failing (issue 508). The condition at 75 percent of a cap is the grants part's (§ 2.1).
**Events** (ADR 0592 §34) are kept 30 days; what a later reader needs is also in the version message. Each carries:
| Event | Carries |
|---|---|
| `opened` | the record, the version it starts from, the author |
| `written`, `removed` | the record, the section's title and digest, the author |
| `ordered` | the record, the titles in order, the author |
| `submitted` | the record, the new version, its section digests, the author |
| `dropped` | the record, the author, the reason (asked, or idle 14 days) |
| `taken-over` | the record, the previous author, the new author, the reason |
| `part-reviewed` | the record, the version, the part, its digest, the verdict |
| `reviewed` | the record, the version, every part's verdict: one event per version, as before |
| `swept` | the digest removed, since when nothing named it |
| `moved` | the record, its counts on the bus |
| `checked` | the move's check: records compared, every difference |
| `formed` | the record, the version, the names changed |
None is named `drafted`, which stays the event of a clause the import's judgement wrote.
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`;
- its **reasoning**, in **sections**, each a plain title and its text in markdown: for a decision, Context,
Considered Options, Consequences, How it is checked, References; for a research effort, its overview and each of its
parts by title; for a design, its `to-be` section and, where something of it runs, its `as-is` section (§ 9);
- 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;
- 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
and gives the difference between any two. A progressive insight is a version marked as one, dated, saying what stood
before. A form-only version (§ 2.2) is marked `form`.
before.
| Link | From → to |
|---|---|
@@ -178,90 +84,6 @@ None is named `drafted`, which stays the event of a clause the import's judgemen
| `touches` | a research effort → a record, issue or design it bears on (by link), or an area (as text) |
| `cites` | any record → any record or issue it names |
### 2.1 Where it is kept
A record is kept in three of the bus's own forms, so that a new version costs only what changed (ADR 0592 §1 to §9):
```
heads (state) versions (stream) sections (object bucket)
┌─────────────────────┐ ┌──────────────────────────┐ ┌────────────────────────┐
│ decision.0290 │ │ decision.0290 version 7 │ │ 3f9a… "Context" text │
│ title, status, … │ │ who, when, why, mark │ │ 81c2… "Options" text │
│ clauses, links │ │ the head as it stood: │──────▶ │ 0d77… "Context" text, │
│ current: 8 │ │ Context 3f9a… │ │ as changed in 8 │
│ sections: │ ├──────────────────────────┤ │ … │
│ Context 0d77… │──────▶│ decision.0290 version 8 │──────▶ └────────────────────────┘
│ Options 81c2… │ │ Context 0d77… │ each body once, named by
└─────────────────────┘ │ Options 81c2… │ its sha256 digest
└──────────────────────────┘
```
- **The head** is one key in the state `heads`, `<kind>.<number>`: the record's fields, its clauses with their status
and source, its links, the operator's ask and warrant, its current and pending version, the latest verdict of each
part's review (§ 5), and its sections in order, each a title and a digest. A key's value stays under 256 KiB
(ADR 0201). A reserved number is a head at `reserved`, with who reserved it, when, for what and when it expires.
- **A version** is one message on the stream `versions`: who wrote it (the caller the bus proved), when, why, its
mark, who wrote each section, the reason of any take-over since the version before, and the head as it stood. It never copies a section's body. The stream keeps its history: every message,
with no age limit, and when it reaches its cap it refuses a new message rather than dropping its oldest, with a
condition raised at 75 percent of the cap. A module's stream may declare that retention; before, a stream was kept at
most 30 days (ADR 0592 §6, changing ADR 0363 §1). The stream `reviews` keeps its history the same way. The
condition at 75 percent is raised by the grants part (mesh-grants), in one place, for every history stream and
object bucket of every module (ADR 0593 §10, changing ADR 0592 §16).
- **A section's body** is one object in the bucket `sections`, named by the sha256 digest of the body: a section
unchanged in a new version costs nothing. The module checks that a body hashes to its name each time it reads one.
The state `names` keeps, per digest, how many versions and drafts name it and since when none has; an object no
version and no draft names is removed a day after the last stopped naming it, each removal the event `swept`. The bucket is storage of the bus, as the object bucket of ADR 0486 is. Any module
may declare an object bucket of its own in its manifest, with a local name and a cap, created as its state buckets
are, kept with no age limit and refusing a new object when full (ADR 0592 §5).
- **A draft** is a key in the state `drafts` (§ 4). **A part's verdict** is a message on the stream `reviews`, and a
part asked of the judge and not yet answered a key in the state `asked` (§ 5). **The whole-set findings** standing
now are keys in the state `findings`, and what was told to the operator once is a key `told.<topic>` there.
**Caps** (ADR 0592 §25). Each is set from the move's dry run, within its form's bound: a stream at most 8192 MiB
(ADR 0363), a state at most 64 MiB (ADR 0201), an object bucket at most 8192 MiB (ADR 0592 §5). The starting figures, from
today's 83 MB for 736 records with every version copying every section, are:
| Bucket or stream | Cap |
|---|---|
| `versions` (stream) | 1 GiB |
| `reviews` (stream) | 512 MiB |
| `sections` (object bucket) | 1 GiB |
| `drafts` (state) | 64 MiB |
| `heads`, `asked`, `names`, `findings` (state) | 64 MiB, ADR 0201's bound |
A write refused at a cap names the cap, and the module's health turns unhealthy naming it.
**Who may read each** follows the mesh's own forms, read-only (ADR 0592 §8, §9). `versions` and `reviews` are
restricted streams (ADR 0363): a module hears them only through its own `consumes` and a `grant` in mesh-knowledge's
trusted config value `readers`, which names the web page that shows records once it exists and nothing today. The
object bucket `sections` is read the same way, through a `reads` entry and a `grant` of `readers`: ADR 0592 extends
ADR 0363's grant to a module's object bucket, and the grants part composes a read-only grant on that one bucket
(ADR 0593 §10).
`heads` is read by a module that names it in `reads`; `drafts`, `asked`, `names` and `findings` by mesh-knowledge
alone. No secret is kept in any of them. A grant cannot limit a reader to some objects of a bucket
(ADR 0488), which is why reading is announced per bucket and never per record.
### 2.2 A section is a part with a title
A section's name is a plain title, unique in its record: "Context", "Direction", "The move". It is never a file's
name: a name with an extension, a number prefix such as `00-`, or a slash is refused, and so is a text that holds a
local path (in a person's home folder or the temporary folder), a reference to a file and line, or a link whose
target is a relative path or a file (ADR 0592 §29).
**A reference has one form** (ADR 0592 §30): the record, a section's title, then a row or clause inside that
section's text, joined by ›, such as "research 081 › Direction › D3"; a clause as its record and number,
"ADR 0290 §7". Sections never nest: `get` resolves a record and a section's title, and the row is an anchor in the
section's text, which a web page links.
**The records imported with file-named sections** (the research efforts' `00-overview.md` and the like) get titles in
one form-only version per record, written after the move by the move's tool alone; no verb writes one. It changes only section names and the
targets of links, each link to a record's file becoming a reference in the form above. It is marked `form`, lists the
names it changed, and is accepted without a judgement because a check proves only form changed: each section's text,
with every link replaced by its label, is the same before and after, and each new link target resolves to the record
the old name named. `get` accepts the old section names as aliases, recorded in that version. A part's review carries
over to it, its meaning being unchanged. It is not a change of meaning; `history` shows it as a change of form
(ADR 0592 §31).
## 3. Clauses
A clause holds its record's number and its own (`0276 §4`), its wording, a `status` and a `source`:
@@ -281,47 +103,9 @@ A clause holds its record's number and its own (`0276 §4`), its wording, a `sta
## 4. Writing a record
`reserve` gives a number (§ 6). A record is then written in a **draft**, one section per call, and submitted as a
version (ADR 0592 §17):
| Verb | Does |
|---|---|
| `open` | opens a draft of a reserved or existing record, with its fields, clauses and links; called again by its author, changes them |
| `write` | writes one section: its title and its text; one call per section |
| `remove` | removes one section from the draft |
| `order` | sets the sections' order |
| `submit` | makes the draft a version: every rule below is checked, every reason of a refusal named at once, and the review asked (§ 5) |
| `drop` | discards the draft |
| `take-over` | makes the caller the author of a draft or a proposal, with a reason |
| `propose` | `open`, a `write` of each section and `submit` in one call, for a record that fits one bus message |
```
open ──▶ write ──▶ write ──▶ … ──▶ submit ──▶ proposed, reviewed by part (§ 5)
▲ │ │
└─ remove, order └── refused: every reason named, the draft kept as it was
drop: the draft is gone take-over: another session becomes its author, with a reason
```
A draft is a key in the state `drafts`: its author, when it was opened, the version it starts from, the record's
fields and clauses, and its sections in order as title and digest. Each `write` stores the section's body in the
object bucket `sections` and its digest in the draft, so the knowledge base holds the only copy of a draft and a
session on any node can read it. A draft holds at most 64 sections and 1 MiB; one untouched for a week is counted in
the health, and one idle for 14 days is dropped and its author told by `dropped`.
**A draft and a proposal have one author** (ADR 0592 §19). Only the author writes a draft or revises a proposal;
another caller is refused, naming the author. `take-over` makes the caller the author, keeps the reason on the
record, and tells the previous author by the event `taken-over`. It answers a proposal that only the machine that
wrote it could revise (issue 1102) and one left proposed for ever by a version from an unproven writer (issue 465).
A proposal waiting for the operator's Approve cannot be taken over: its author withdraws it first. The reason of a
take-over is carried in the next version's message.
**Only a proven caller writes** (ADR 0592 §20, §21): `open`, `write`, `submit`, `propose` and `take-over` refuse a caller
the runtime could not prove. The operator's Approve binds the section digests of the version shown; a version with
any other section is asked again.
`submit` and `propose` are refused, with every reason at once, when one of the rules that judge one record or its
prose fails, among them:
`reserve` gives a number (§ 6). `propose` takes the record whole: its fields, its clauses, its reasoning in sections,
its links, and for each `changes` the clauses it changes. It is refused, with every reason at once, when one of the
29 rules of the hq checks that judge one record or its prose fails, among them:
- a link names a record or clause that does not exist;
- a decision has no clause, or a clause has no bold lead;
@@ -329,62 +113,21 @@ prose fails, among them:
that replaced it;
- its topic is not one of the reading order's;
- a research effort graduates without naming what it became;
- its text uses a retired word of the glossary, or a daemon's command line;
- a section is over 40 KiB (it says: split it), or the clauses together are;
- a section's name looks like a file, or its text holds a local path, a file and line, or a link to a file (§ 2.2).
`write` refuses a section over 40 KiB, or one whose name or text breaks § 2.2, at once, so a session learns it on the
section it just wrote.
- its text uses a retired word of the glossary, or a daemon's command line.
A proposed record can be revised by its author as new versions until it is accepted. Nothing proposed is ever served
as in force.
## 5. Review and acceptance
When a version is submitted (by `submit` or `propose`, a new version of a proposed record included), the knowledge
base reviews it itself, **in parts** (ADR 0592 §22 to §28, changing ADR 0290 §4): one judgement per part, each in a
separate call, with no tools, that answers `approve` or `changes` with its findings, the way the issue tracker's
triage judges an issue. The judgement goes through the agent module's judge (`claude_code_judge`) today, and through
the mesh's inference layer once one exists; nothing in this design depends on which.
- **The head** is one part: the title, the brief, the clauses (at most 40 KiB together) and the links, judged
against the clauses of the records it links, in what room is left, those not shown named.
- **Each changed section** is a part of its own, judged with **the frame** of at most 16 KiB: the title, the links,
and only what the section names (rows of other sections, clauses, the clauses of linked records), with the digest
of the Context section (the Overview, for a research effort) it is read beside. What does not fit is cut, and the
question says what was cut. A section is at most 40 KiB, so each question stays under the judge's 60 KiB.
- **What is judged again.** A section is judged again only when its own text or what it names changes; the Context
only when it changes. So a usual revision judges only the sections it changed.
- **At most two judge calls of one version run at once.** A revision of eight sections takes a few minutes, and
about four times the tokens of one whole judgement.
- **The rules on the whole record** (§ 4) stay code, run at `submit`; no judgement is asked to find a link to nothing.
```
version 3 of research 081
├ head title, brief, clauses, links ── judged ──▶ approve
├ Overview unchanged since version 2, same frame ── kept ──▶ approve (version 2's)
├ Direction changed ── judged ──▶ changes: "D3 contradicts ADR 0290 §7"
└ Findings changed ── judged ──▶ approve
version 3 stays proposed, with the findings of Direction
```
**The parts wait on the bus** (ADR 0592 §23). Each part asked of the judge is a key in the state `asked` (the part,
the judge's ticket, the node, when it was asked), collected again at start, so a restart loses no review. The judge's
answer comes as the event `judged`, which the claude-code module emits for each ticket and the knowledge base
consumes; until claude-code emits it, the knowledge base polls the ticket, which is not NATS-native and is tracked by
issue 1956. The judge is a tool of the claude-code module, not a seat's verb: a seat for judgements (held by the
mesh's inference layer once one exists) is a note on that issue. A part waiting more than 30 minutes asks the
operator once through the operator channel, and the ask clears when the part is judged.
**A part's verdict** is the event `part-reviewed` and a message on the stream `reviews`: the record, its version, the
part, the digest judged, the verdict, the findings, who judged and when; the head keeps the latest verdict of each
part. **`reviewed` keeps its meaning**: one event per version, once every part is judged, carrying each part's
verdict, so every wait on it keeps working. A part's review is kept for a new version when what it was judged with is
unchanged, and for a form-only version (§ 2.2); any other part is judged again. **A version is accepted when every
part's review approves.** On `changes`, the record stays proposed, the findings are kept with that version and the
part they name, and its author revises it with a new version. A part that cannot be judged (the judge unreachable,
or its answer unreadable) leaves the version proposed and says which; it is never taken as an approval. When every
part approves:
When `propose` takes a record (or a new version of a proposed one), the knowledge base reviews it itself: it asks for
a judgement in a separate call, with no tools, that sees only the record, the records it links and the rules, and
answers `approve` or `changes` with its findings, the way the issue tracker's triage judges an issue. The judgement
goes through the agent module's judge (`claude_code_judge`) today, and through the mesh's inference layer once one
exists; nothing in this design depends on which. On `changes`, the record stays proposed, the findings are kept with
that version, and its author revises it with a new version, which is reviewed again. A judgement that cannot be had
(the judge unreachable, or its answer unreadable) leaves the record proposed and says so; it is never taken as an
approval. On `approve`:
1. a record that does not need the operator is accepted at once;
2. a record whose author marked it `needs operator` gets an ask on the router with Approve and Decline, shown on a
@@ -395,18 +138,18 @@ On acceptance the record and its clauses move to `accepted` and `in force`, ever
`supersedes` name moves to `changed` or `superseded`, and `accepted` and `changed` events are emitted. Then the 4
rules that judge the whole record set run (an accepted decision cited by nothing, and the like). A failure does not
undo the acceptance. It is a fault of the records, which their authors repair, not of the module: it is told to the
operator once, as a warning naming the rule and the records, kept in the state `findings`, and cleared once the rule
holds again. The module's health names it and stays healthy, because health says only whether the module works, and
a finding never holds a rollout at the module's first-node gate (the operator's decision of 2026-10-12 on issue 504,
as issue 508 decided for a proposal never reviewed).
operator once, as a warning naming the rule and the records, and cleared once the rule holds again. The module's
health names it and stays healthy, because health says only whether the module works, and a finding never holds a
rollout at the module's first-node gate (the operator's decision of 2026-10-12 on issue 504, as issue 508 decided for a proposal never
reviewed).
The rule `cited` is also judged ahead, in the review, before the judgement: a decision that names no home and that no
accepted record links is not accepted. Its review, by the knowledge base itself, asks changes naming the rule (issue
504). It is not refused at `submit`, because no record can link a number only reserved: a record that cites a new
504). It is not refused at `propose`, because no record can link a number only reserved: a record that cites a new
decision is proposed after it and accepted, and the new decision is proposed again.
An accepted record is changed only by a new record, by a progressive insight (a new version marked as one, which is
reviewed like a proposal), or by a form-only version of § 2.2, which a check, not a judgement, accepts.
An accepted record is changed only by a new record, or by a progressive insight: a new version marked as one, which
is reviewed like a proposal.
## 6. Numbers
@@ -451,41 +194,6 @@ reporting nothing created (issue 488).
Before the records leave this repository (issue 426), every record is compared with its file at the import's commit,
field by field and section by section, and the result is written on issue 426.
### 8.1 The move off the store
The records leave the store once, and the database is dropped only after a check and the operator's yes
(ADR 0592 §12 to §16):
1. **Order.** Two deliveries, in order (ADR 0593 §10, changing ADR 0592 §16): first the mesh-sdk release sdk1
(issue 1585), which carries the manifest's field types for `objects` and `retention: history`, their checks in
the indexer's judge, and in the grants part the creation of the object buckets and history streams, the
read-only grants of `readers` on them and the condition at 75 percent for every such bucket and stream in one
place; then mesh-knowledge. An indexer whose judge predates these keys refuses a manifest that has them, so
mesh-knowledge cannot be delivered before the release. The claude-code module's `judged` event is
delivered beside, and is not needed for the move (§ 5).
2. **Copy.** The move copies everything the database holds onto the bus, every record, version, clause, link,
review, finding, ask with its warrant, reservation and told mark: each version's sections into `sections` by
digest, each version as a message on `versions`, each record's head into `heads`, each standing finding into
`findings`, each told mark as `told.<topic>`. A clause keeps its number, status and source as the database holds
them (ADR 0321). It emits `moved` per record, writes nothing twice, and a second run changes nothing and says so.
A dry run lists what it would write and the size each bucket and stream will need, from which the caps are set.
3. **The window.** From the move's final copy through the check to the switch, every writing verb is refused with
its reason ("the knowledge base is moving"). Reading verbs answer throughout.
4. **Check.** A tool compares the database and the bus record by record: for each record the count of each of
those, the digest of every section of every version, computed from the database's text and read from the bus,
that each body hashes to its name, and a digest of every review's, finding's and ask's content. It emits
`checked` with every difference, and the health shows its last result.
5. **Switch.** Only once the check shows no difference, the module answers from the bus. The switch is a key on the
bus, so a restart never falls back to the database. A difference is told to the operator, and the module stays on
the database.
6. **Rollback.** Until the database is dropped, the module writes each change to the database too, so a rollback of
the delivery reads a current database. A rollback is safe only until the drop.
7. **Tell and ask.** Once the check is clean, the operator is told its result and asked, once, whether the database
may be dropped, because dropping it removes data.
8. **Drop.** On the operator's yes, the delivery after it removes the module's requirement of a database, and the
database is dropped by the store's module, through its tool for it, which is built there first if it has none.
9. **Titles.** The move's tool then writes the form-only versions of § 2.2, one per record with file-named sections.
## 9. The slices after the first
| Slice | Delivers | Done when the operator sees | Tracked by |
@@ -494,7 +202,6 @@ The records leave the store once, and the database is dropped only after a check
| 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 |
| 4 | meeting notes, drawings and blog posts | not decided; needs its own decision | issue 429 |
| 5 | the records on the bus, written and reviewed by section, the move off the store and the titles (§ 1, § 2, § 4, § 5, § 8.1; ADR 0592), delivered as the mesh-sdk release sdk1, then mesh-knowledge, with claude-code's `judged` event beside | the acceptance tests of the fifth slice in § 10, live; the knowledge base requires no database | the issues its hand-off opens (playbook 04) |
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
@@ -521,7 +228,7 @@ the page as the authority on words (ADR 0423, changing ADR 0244).
|---|---|
| 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 catalog'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 |
| 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.
@@ -592,45 +299,6 @@ milestones and nothing of how a delivery runs; it only listens for the announcem
| An answer stays within its bound | the module's test over the full import of `main` at a recorded commit: every `ask`, `get` and `holds` of a fixed question list |
| 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 |
| A record is kept in the bus's forms, a section once by its digest, and read only as announced | one integration test that starts the module whole against a real bus server: a record of three sections and a second version changing one give one head, two version messages and four objects; a module with no `grant` in `readers` is refused the streams and the object bucket |
| Search answers from the index as it did from the store | a unit test over a fixed set of records and a fixed question list; the integration test restarts the module and asks again |
| A draft is written by section and has one author, and a take-over tells the previous one | unit tests of each verb of § 4, of a refusal naming the author, and of `take-over` emitting `taken-over`; a take-over refused while the operator's Approve is waited for |
| A stream that keeps its history, and an object bucket of a module's own | in the integration test: a history stream at its cap refuses a new message and keeps its oldest; an object no version or draft names is removed after a day, one still named is kept; unit tests of registration refusing a name already taken |
| A long record is judged whole, one part at a time | unit tests: the frame never passes 16 KiB and says what it cut, with twenty clauses of 2000 bytes and a 40 KiB Context; every question stays under 60 KiB with a 40 KiB section and that frame, and the head part with 40 KiB of clauses; a section over 40 KiB is refused saying split it; an unchanged section keeps its review, a changed frame asks it again |
**The fifth slice's tests, one per failure path** (ADR 0592; the review by architectural domain's testing ruling). Unit tests are
table-driven; one integration test starts the module whole against the real bus server. The module's count today is
1 integration and 104 unit tests; these add about 20 table cases and stay within the budget of 50 and 500.
| Failure path | Test |
|---|---|
| two writes race on one draft | unit: the second compare-and-set loses, is retried, and is refused with the reason past its tries |
| `submit` races `take-over` | unit: one wins; the other is refused naming the new author or the new version |
| two reservations race | unit: two numbers, or one refused; never one number twice |
| a write cut between the stream and the head | unit: the call answered that the version is taken; a start completes the head from the stored version and sends the event once |
| a failure before the version is stored | unit: the call is refused, and no head, event or version follows later |
| a change through the bus during the move | unit: read back from the database, so a rollback of the delivery reads a current database (ADR 0592 §14) |
| a section changed after the operator's Approve | unit: the record is asked again; a version with every section unchanged is not (ADR 0592 §21) |
| a named digest has no object | unit: health unhealthy, naming it; the integrity tool restores it from given text |
| a body does not hash to its name | unit: the read refuses it, naming the digest |
| a full object bucket | integration: a write refused naming the cap; health unhealthy |
| a full history stream | integration: `submit` refused with the head unchanged |
| a judge timeout for one part | unit: that part stays asked and the version proposed, saying which; the others keep their verdicts |
| a restart with parts asked | integration: the parts in `asked` are collected again and judged |
| `reviewed` once per version | unit: one `reviewed` after the last `part-reviewed`, carrying every verdict |
| told once | unit: a whole-set finding told once across a restart, again only after its mark is cleared |
| a second run of the move | unit: changes nothing and says so |
| the move's check | unit: a changed review, a missing finding and a missing ask, each named |
| writes during the switch | unit: every writing verb refused with its reason; reading verbs answer |
| an older judge | the indexer's judge test (release sdk1): a manifest with `objects` or `retention: history` refused by a judge that does not know them, accepted by one that does |
| the grants part's forms | the grants part's test (release sdk1), against a real bus server: an object bucket and a history stream created for their module; a read-only grant on that one bucket for a module named in `readers`, removed at the next composition once withdrawn; the condition at 75 percent raised and cleared below |
| a reference | unit: "record › title › row" resolves to the section; a nested title is refused |
| a form-only version | unit: accepted when only names and links changed and each target resolves; refused otherwise |
| an unproven caller | unit: `open`, `write`, `submit`, `propose` and `take-over` refuse it |
| the index behind | unit: health unhealthy while the index's revision is behind `heads`; `ask` refuses "index not built" |
| the start | integration: the time to a built index measured and reported |
| A section is a part with a title | a table test, one case per refused name and text of § 2.2; a form-only version whose text changed beyond its links is refused |
| Every bucket has a tool, and health says the index | the manifest test compares tools and events with the code; a unit test of the health before and after the index is built |
**The third slice's acceptance tests** (§ 9.1 to § 9.3; research 062's direction), what its build proves before it
is done:
@@ -650,52 +318,9 @@ is done:
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 catalog or the instruction text pointing at a removed path.
**The fifth slice's acceptance tests** (§ 1, § 2, § 4, § 5, § 8.1; ADR 0592), what its build proves before it is
done:
1. The move's check, run live, names no difference between the database and the bus: 656 decision records and 80
research efforts (the counts of 2026-11-02, or the counts on the day), every version, clause and link, and the
digest of every section of every version.
2. The operator is told that result and asked before the database is dropped; after the yes and the delivery, the
module's manifest requires no database and the store holds none for it.
3. A session writes a research effort of more than 100 KiB one section at a time from one node; a session on another
node takes the draft over with a reason, the first session hears `taken-over`, and the second submits it.
4. Every changed section of that effort is judged on its own, each question under 60 KiB, and the effort is
accepted once every part approves.
5. A section named like a file, a text holding a local path, and a section over 40 KiB are each refused, the last
saying split it.
6. After the titles' form-only versions, no record holds a section named like a file, `history` shows each as a
change of form, and `get` of "research 062 › Direction" answers that section.
7. The fill tool shows each bucket and stream against its cap; the second version of a record with one changed
section adds one object, not one per section.
8. During the move's window a `write` is refused saying the knowledge base is moving; after the switch a restart
still serves from the bus; the health names the source and the check's result.
9. A part's review waiting on the judge survives a restart of the module, and one past 30 minutes reaches the
operator once.
10. Every test of the table above passes in the one check of the pull requests, within the test budget.
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 (its reading of the buckets is announced, ADR 0592); line comments on a proposal (the judgement's findings name the section and
Semantic or vector search; a browser view; line comments on a proposal (the judgement's findings name the section and
clause instead); and the rules of how-we-build, which are not decision records. Each needs a decision of its own.
## Reviewed by architectural domain
The fifth slice was reviewed by architectural domain on 2026-11-02 (four reviewers, two architectural domains each; the knowledge base's own
review of ADR 0592's fourth version beside), and the lead ruled on every finding. What was found, and what answers it:
| Architectural domain | Found | Answered by |
|---|---|---|
| security | unproven callers could write or take over; an Approve did not bind the sections shown; readers were named outside ADR 0363's grant (the knowledge base's review of version 6); a proposal waiting on the operator could be taken over; readers' grants and a body's match with its name were unsaid; a draft had no bound | ADR 0592 §4, §8, §9, §17, §19, §20, §21; § 2.1, § 4 |
| seams | asked parts lived only in memory; told marks and reservations had no form on the bus; the judge's ticket is polled; who creates buckets and grants was unsaid; the judge is a module's tool, not a seat | ADR 0592 §2, §7, §16, §23; § 1, § 5 (the judge's seat a note on issue 1956), § 8.1 |
| running qualities | three writes could stop halfway; caps had no figures; a usual revision judged every section again, with no limit on parallel calls; start time unknown | ADR 0592 §10, §22, §25; § 1, § 2.1 (caps), § 5 |
| health | a stale index, a missing body, a full bucket and the move's state were not visible; waiting reviews and idle drafts were not counted | ADR 0592 §25, §33; § 1.1 |
| events and logs | per-part verdicts would have broken every wait on `reviewed`; events did not say what they touched; a take-over's reason and each section's author were lost after 30 days | ADR 0592 §3, §27, §34; § 1.1 (the events table) |
| testing | each failure path lacked a test; the budget was not counted | ADR 0592's How it is checked; § 10 (the failure-path table) |
| upgrade | writes during the switch, a rollback after it, and the order of the three deliveries were unsaid; the form-only check did not prove new link targets | ADR 0592 §13 to §16, §31; § 8.1, § 2.2 |
| tools | unnamed objects, missing bodies, form-only versions, asked parts and told marks had no tool | ADR 0592 §32; § 1.1 (the tools table) |
The knowledge base's review of the fourth version found that the reference form read as nested sections: ADR 0592
§30 and § 2.2 say sections never nest.
@@ -88,7 +88,7 @@ and may not be copied to anyone else. The vendor serves only its newest version,
file (a `file://` URL), with `updates_disabled`. Betterbird installs it into every profile, and the person
cannot remove it.
Owl declares the range of Betterbird versions it supports. The catalog's check refuses a manifest whose
Owl declares the range of Betterbird versions it supports. The catalogue's check refuses a manifest whose
Betterbird pin lies outside the range of its Owl pin, so a Betterbird upgrade that Owl does not support yet
waits for Owl.
@@ -205,7 +205,7 @@ module needs.
**The preferences are one entry each.** The setting `prefs` maps a preference's name to its value, so a node's
layer overrides one preference, or takes one out with null, and the operator's setup lives in the mesh-wide
layer, not in the catalog. The module ships only its policy's locks and the names it keeps at Betterbird's own
layer, not in the catalogue. The module ships only its policy's locks and the names it keeps at Betterbird's own
default (issue 586). `prefs` refuses a credential's preference, an account's (§4), and the four prefixes the
settings below own: `calendar.registry.`, `calendar.list.`, `ldap_2.servers.` and `mailnews.tags.`, so each value
has one owner.
@@ -254,11 +254,11 @@ own layers. With `propose` it proposes the next layer for the operator's Approve
each node's that needs one, each only after the one before it was set or declined, against the layer as it then
stands and carrying that layer whole (ADR 0277 keeps one open ask per module and machine). It proposes nothing
while a desktop node does not answer, because a layer derived without it would put on every node what only some
hold. The first mesh-wide proposal also carries the operator's setup of `prefs`, which the catalog held before.
hold. The first mesh-wide proposal also carries the operator's setup of `prefs`, which the catalogue held before.
## How it is checked
- **The catalog's tests:** both pins carry a URL, a version and a sha256; a Betterbird pin outside Owl's range
- **The catalogue's tests:** both pins carry a URL, a version and a sha256; a Betterbird pin outside Owl's range
is refused; the generated policy file names Owl as `force_installed` from the placed file with
`updates_disabled`, and sets `DisableAppUpdate`; `prefs` refuses the account prefixes and the four of §8;
`accounts` refuses a password-like field; a declared account's user.js holds only the module's own keys; every
@@ -38,7 +38,7 @@ request, and the five, sharing their branch's name, are one delivery group (ADR
future state. The five are merged within one merge window, so they are one batch and one rollout (ADR 0276
§1, §2), in an order declared with `after:` lines in each pull request (ADR 0239 §11): mesh-sdk, then
`mesh-host`, then mesh-controller, then mesh-catalog, then hq. The release holds every package but T6, R1 and R2:
the media catalog and the applications live in repositories of their own and move after it, and only
the media catalogue and the applications live in repositories of their own and move after it, and only
then can the old forms be refused (R1) and the record say what runs (R2).
**The moves of data run after the delivery, in this order, each a verb that says what it did:**
@@ -58,7 +58,7 @@ the backlog rollout once it is heard from (ADR 0360 §10).
**Nothing breaks on the way.** ADR 0349 clause 17: each file is filled the old way or the new way, never
both, by the form of its manifest, and the old forms stay accepted until the last module has moved.
Inside the release this still holds: the media catalog and the applications kept in their own
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
@@ -169,9 +169,9 @@ as T2.
**T5 (issue 658) — The ten modules without a build.** *mesh-catalog. A day.* ca-trust, de-spiegel, invoicing,
local-model-consumer, matrix, node-env, only-office, sshd, step-ca and supabase gain an archive build for
their templates. **Proof:** as T2; afterwards no manifest in the catalog carries file text.
their templates. **Proof:** as T2; afterwards no manifest in the catalogue carries file text.
**T6 (issue 659) — The media catalog and the applications.** *mesh-media-catalog and each application's
**T6 (issue 659) — The media catalogue and the applications.** *mesh-media-catalog and each application's
repository. Two days.* **Proof:** as T2, per repository.
## 2 — Config (ADR 0348)
@@ -185,10 +185,10 @@ object or list not trusted; a predefined key not on the list or of another type
value falling back to a trusted key; a value neither in a template nor read at runtime; a placeholder
naming an undeclared value; a `reload-on` or `restart-on` naming an undeclared file; a moved module's
mergeable file not naming its keys; a mesh word or `puid`/`pgid` in `takes-config`. **Proof:** the registration refusal tests, one per
rule; every manifest in the catalog still registers.
rule; every manifest in the catalogue still registers.
**K2 (issue 661) — The list of predefined keys.** *mesh-controller and mesh-catalog. A day.* `predefined.json` at the
catalog's root, with `font-size` and `agent-managed-settings` first; the build validates it and every
catalogue's root, with `font-size` and `agent-managed-settings` first; the build validates it and every
manifest naming a key; the merge check refuses removing a key still named or holding a value; the newest
trunk build's list is published. **Proof:** the merge check refuses a planted removal; an older build's
list does not replace a newer one.
@@ -214,9 +214,9 @@ the controller reads `mesh-config_resolved` where it read its layers (ADR 0349 c
an exception to ADR 0348 §9 that R1 removes), and `settings` answers by forwarding to `config`. **Proof:** composition of
every node before and after is byte-identical; `settings` and `config` list the same values.
**K6 (issue 665) — The catalog says config.** *mesh-catalog. A day.* The 15 modules using `${setting:}`, the 9
**K6 (issue 665) — The catalogue says config.** *mesh-catalog. A day.* The 15 modules using `${setting:}`, the 9
declaring settings and the 4 with `takes-settings` move to the new names and the full declaration.
**Proof:** the catalog's check finds no old name in a moved module.
**Proof:** the catalogue's check finds no old name in a moved module.
**K7 (issue 666) — The operator's acceptance on dunst.** *live. An hour.* With every level cleared the first
workstation's dunstrc says 10; a mesh font size, then that node's font size, then dunst's own value on
@@ -326,7 +326,7 @@ ADR 0349; claude-code reads its managed settings through it.
## 8 — The old forms go (ADR 0349 clause 17)
**R1 (issue 682) — Refused and removed.** *mesh-controller, `mesh-host`, mesh-catalog. Three days.* Once every module of
every catalog has moved: the catalog's check refuses inline text, `settings`, `${setting:}` and the
every catalogue has moved: the catalogue's check refuses inline text, `settings`, `${setting:}` and the
old contribution forms; the controller's filling passes but placement, its settings, secret and binding
code and tables (a new migration) go; the node-engine keeps opening sealed values for the core's files
only; the controller stops reading `mesh-config_resolved`. **Proof:** the controller's test that its
@@ -345,7 +345,7 @@ each makes true. Whether a milestone's issues are delivered is read from the iss
| # | 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 catalog and the applications carry their file text as templates | T6 | 659 |
| 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
@@ -128,7 +128,7 @@ identity present on a machine into that machine's declaration, and the node-engi
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 catalog's check refuses a manifest that
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
@@ -204,8 +204,8 @@ Checked on 2026-10-17 (playbook 02, step 1); no conflict was found.
## 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:
**reads**, **acts** or **dangerous**. The catalog's check refuses an unmarked tool, once every module already in
the catalog is marked. A dangerous tool is granted only by name, and naming one in a role goes to the operator as
**reads**, **acts** or **dangerous**. The catalogue's check refuses an unmarked tool, once every module already in
the catalogue is marked. A dangerous tool is granted only by name, and naming one in a role goes to the operator as
an ask first. Every other dangerous act is an ask (ADR 0259), bound to that act and carried out on the warrant.
The **break-glass** agent holds every tool and never asks. It is sealed until the operator opens it, or a condition
@@ -246,7 +246,7 @@ carry them:
1. A licence token replaced under a running session keeps that session working.
2. The sandbox module loads KVM on the home server, and a sandbox runs there.
3. Every existing module's tools carry an effect, and the catalog's check refuses an unmarked one.
3. Every existing module's tools carry an effect, and the catalogue's check refuses an unmarked one.
4. The switch, run on the mesh's own modules in the lab from the bed test kept on the lab's branch
`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.
@@ -260,7 +260,7 @@ From research 057 (ADR 0411 to ADR 0414):
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 catalog declares a login for a person or an agent; the catalog's check refuses one.
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.
@@ -292,7 +292,7 @@ and the issue tracker takes only registered owners. The building waits until the
| # | Outcome, in one sentence | Proves | Repositories and their parts | Depends on |
|---|---|---|---|---|
| 1 | Every tool on the mesh says whether it reads, acts or is dangerous, a tool's description shows it, and the catalog refuses a module that leaves one unmarked | test 3 | mesh-sdk: the effect in a tool's declaration, an unmarked tool served as dangerous; mesh-controller: the manifest field; mesh-catalog and mesh-media-catalog: every module marked, the check | — |
| 1 | Every tool on the mesh says whether it reads, acts or is dangerous, a tool's description shows it, and the catalogue refuses a module that leaves one unmarked | test 3 | mesh-sdk: the effect in a tool's declaration, an unmarked tool served as dangerous; mesh-controller: the manifest field; mesh-catalog and mesh-media-catalog: every module marked, the check | — |
| 2 | A session works on a repository's workspace through the mesh's tools alone: read, glob, search, edit, write, git, its tests and its merge check, with no command runner | — | mesh-catalog: the module `nox-workspace` on the node seat `workspace`, on the always-on nodes; mesh-controller: the seat | 1 |
| 3 | A thread created through the thread seat runs headless on an always-on node, its conversation readable through the seat, its usage counted per thread, and a move to another node loses at most the turn in progress | — | mesh-catalog: `nox-threads` (seat `thread`, restricted streams, leases), `nox-thread-handler` (seat `thread-handler`), claude-code (mirror and rebuild), claude-licence-manager (usage per thread) | 2 |
| 4 | The operator starts, follows, answers and stops a thread from a terminal client, and answers its questions on Telegram | — | mesh-catalog: the terminal client, a Go program of `nox-threads`; claude-code: questions and permission requests through the asks | 3 |
@@ -313,7 +313,7 @@ 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 catalog's check refuses one (997) | 6 |
| 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 |
+1 -1
View File
@@ -279,7 +279,7 @@ before `mesh-mail-notifier` is unassigned; test 12 with milestone 4.
|---|---|---|---|---|
| 1 | In a browser on the laptop and the workstation, the operator signs in and browses, searches and reads every account's mail with its attachments, and writes a mail that goes out on their Approve | tests 1 to 9 | mesh-catalog: `mesh-mail`'s account bucket (issue 1255, ADR 0439 §2); the module `mesh-mail-web` (manifest, sign-in, the pages, the send, the seat's verbs, the integration test) | — |
| 2 | Each new mail is read into a mail group on the mail page; the mail that matters reaches the operator's phone as one mail signal, a scam warning shows on the page only, the operator's corrections become reading lessons, and the pop-up for every new mail is gone | tests 10, 11, 13 to 17 | mesh-catalog: the seat `mail-reading` declared in `mesh-mail`'s manifest; the module `mesh-mail-reader` (batches, mail entries, reading lessons, mail signals, health, the integration test); `mesh-mail-web`'s mail groups, mail signals, scam warnings and reading lessons; `mesh-mail-notifier` unassigned (issue 1418) | 1 |
| 3 | Betterbird is gone from the laptop and the workstation, its profile and mail kept in the home | ADR 0379 §2 | mesh-catalog: Betterbird unassigned on the operator's Approve, then the `betterbird` module removed from the catalog (issue 806) | 2 |
| 3 | Betterbird is gone from the laptop and the workstation, its profile and mail kept in the home | ADR 0379 §2 | mesh-catalog: Betterbird unassigned on the operator's Approve, then the `betterbird` module removed from the catalogue (issue 806) | 2 |
| 4 | **Replaced by design 63** (2026-10-22): on the mail page the operator asks questions about their mail and gets the mails that answer them, in a mail conversation kept as a thread | test 12 | mesh-catalog: `mesh-mail-web`'s mail conversation as a client of the thread seat; the thread's mail tools under a caller of their own (issue 1419); `mesh-mail`'s folder case and `-any` switches | 2 (the reading lessons); design 58's milestone 3 (issue 880) |
**Milestone 1, confirmed by the operator on 2026-10-19, is delivered;** its live proof on the laptop and the
+2 -2
View File
@@ -199,10 +199,10 @@ missing page and expects the error.
## Milestones
The milestones build ADR 0468, feature `files-through-the-store`. They are cut by release order: the catalog's
The milestones build ADR 0468, feature `files-through-the-store`. They are cut by release order: the catalogue's
merge check reads manifests with the controller the mesh runs, and the controller vendors the SDK, so a new
manifest field ships SDK first, then the controller and every other strict reader of manifests (the build modules,
the delivery's indexer), then the catalog (playbook 07, *a new manifest word*).
the delivery's indexer), then the catalogue (playbook 07, *a new manifest word*).
| # | Outcome | Proves | Repository | Issues |
|---|---|---|---|---|
+1 -1
View File
@@ -48,7 +48,7 @@ document is written and this one's status becomes `implemented`.
| [`45-a-core-that-cannot-fail-silently.md`](45-a-core-that-cannot-fail-silently.md) | **Designed.** The core says when it is wrong, refuses what is stale or unreadable, heals what it knows, upgrades one machine at a time with a witness that rolls it back, and is checked against the real mesh before merge: the writers and signals tables, the condition store, `doctor`, the minimal output channel, healers, the lease and report order, staged upgrades, the facts snapshot and replays, in six phases | ADR 0227, ADR 0224, ADR 0218 |
| [`46-the-conversation-with-the-operator.md`](46-the-conversation-with-the-operator.md) | **Designed.** The mesh tells and asks its operator over channels that are holders of two kinded benches, `channel` and `intake`, declaring capabilities from a fixed vocabulary; the router orders them by work context and never lowers the bar; an answer that performs an action is checked and performed by the controller, on a TOTP code or a verified Telegram sender, never on a desk click alone; operator messages addressed or answered by the mesh's responder, untrusted input kept as data, references for what words may not carry, and a recoverable factor; Telegram first, in six phases | ADR 0234, ADR 0227 |
| [`47-delivery-from-commit-to-delivered.md`](47-delivery-from-commit-to-delivered.md) | **Designed.** A delivery is one commit in one repository, from its pull request's head to every machine; a delivery group is deliveries sharing a branch name, ordered and checked as one future state; both owned by the `mesh-delivery` module with one state table, its state on the bus, every transition said, noted on the commit and shown on the pull request; the controller keeps the planner, the gate, sending and the rollout, and the core's own updates never wait for the module | ADR 0239, ADR 0238, ADR 0236 |
| [`49-the-mesh-in-domains.md`](49-the-mesh-in-domains.md) | **In progress.** Every concept belongs to one of eleven domains, which owns its one word; the glossary is organised by them and is the authority, a retired word is named on its replacement's line with its scope, and two checks hold this repository's documents and the catalog's tool descriptions to it | ADR 0244, ADR 0006, ADR 0008, ADR 0302 |
| [`49-the-mesh-in-domains.md`](49-the-mesh-in-domains.md) | **In progress.** Every concept belongs to one of eleven domains, which owns its one word; the glossary is organised by them and is the authority, a retired word is named on its replacement's line with its scope, and two checks hold this repository's documents and the catalogue's tool descriptions to it | ADR 0244, ADR 0006, ADR 0008, ADR 0302 |
| [`50-split-dns-on-a-machine-with-a-vpn-client.md`](50-split-dns-on-a-machine-with-a-vpn-client.md) | **In progress.** A machine whose VPN client pushes servers of its own runs a resolver of its own, on a node seat, that routes the VPN's domains over its link and every other name to the mesh's resolvers; the VPN client's module carries the adapter | ADR 0247, ADR 0223 |
| [`51-the-registries-keep-what-is-named.md`](51-the-registries-keep-what-is-named.md) | **In progress.** The artifact store, the package registry and each machine's container runtime get tools that say what they hold, what names each thing and what could go; every removal is a dry run unless a person asks and says why, and what decides is always a record | ADR 0251, ADR 0189 |
| [`52-shares-and-mounts.md`](52-shares-and-mounts.md) | **In progress.** A node shares one folder at a time through `nfs-server`, which provides it as a provision to the private network only, mapping every client to the folder's owner; a node that asks mounts it through `mounts`, a mount unit and an automount per share, so nothing mounts or fails at boot; a device that comes and goes is an occasional source whose absence is a state | ADR 0263, ADR 0051, ADR 0092 |
@@ -1,8 +1,8 @@
# Diagnosis — 2026-09-21
1. The report's own remedy was taken: the same order against a second ACME implementation. The
catalog's own certificate authority, step-ca, was raised in the bed beside Pebble, pinned as
the catalog pins it, and the same proxy pointed at it over the same challenge path. It issued
catalogue's own certificate authority, step-ca, was raised in the bed beside Pebble, pinned as
the catalogue pins it, and the same proxy pointed at it over the same challenge path. It issued
within a second of the order.
2. The name was still not served, and the reason was neither authority's: the bed's stand-in
backend, a netcat loop behind the route, used flags this machine's netcat has not got and never
@@ -1,6 +1,6 @@
# Diagnosis — 2026-09-21
1. The catalog was read again for mounts no resource declares: twenty-four remained, in two
1. The catalogue was read again for mounts no resource declares: twenty-four remained, in two
kinds only. Nine media modules mount the operator's library, and every one of those paths is
already in the module's `accesses` — the vocabulary ADR 0051
gave exactly this. Four modules mount the container runtime's socket, and every one of them
@@ -9,6 +9,6 @@
check that was withdrawn needed only to read both: an access is a declared path, and a
capability declares the facility it grants.
**Located in:** the catalog's parser. The check is back, refuses an undeclared mount naming the
path and the three remedies, and a test parses the whole catalog. Decided in
**Located in:** the catalogue's parser. The check is back, refuses an undeclared mount naming the
path and the three remedies, and a test parses the whole catalogue. Decided in
ADR 0091.
@@ -18,7 +18,7 @@ Not "a registry cannot be installed" — **a mesh cannot hold its own modules.**
The artifact store is one store for everything a module ships: images, and archives, which are
directories from a module's repository packed and pushed as content-addressed blobs. So it is the
mesh's module catalog in artefact form, and the same role a separate object store plays in the
mesh's module catalogue in artefact form, and the same role a separate object store plays in the
arrangement being replaced.
Until it exists, a module can be described and resolved but nothing it carries can be kept
@@ -6,7 +6,7 @@ opened: 2026-09-04
## What was observed
Rolling the module runtime out to the catalog (the runtime that serves a module's tools and
Rolling the module runtime out to the catalogue (the runtime that serves a module's tools and
runs its events under the module's own account), each tools+events module receives its
configuration the way the design intends: a mergeable config file the module declares, into
which the assignment's settings are merged. The runtime container mounts that file and reads
@@ -38,7 +38,7 @@ failing to load because the value that would fix them is present but unread.
## Why it matters beyond this instance
Every tools+events module converted to the runtime model now takes its URL and credentials
this way, so this is not one module's quirk — it is the config path for the whole catalog.
this way, so this is not one module's quirk — it is the config path for the whole catalogue.
The gap turns the headline promise of the settings design ("change it without editing it, on
the fly") into "change it, then recreate the container by hand," which is the manual step the
design existed to remove. And because the file is genuinely updated, nothing surfaces the
@@ -6,7 +6,7 @@ opened: 2026-09-02
## The symptom, as observed
Found by review of the catalog examples (2026-09-02), not by an outage — the outage is the
Found by review of the catalogue examples (2026-09-02), not by an outage — the outage is the
part the design permits to be silent.
The cache module declares its access-control file as an ordinary file resource with fixed,
@@ -40,5 +40,5 @@ even when what grew inside since is somebody else's work the mesh asked for.
some other way?
- ADR 0010 treats every added resource type as a security artefact. Does a create-once
semantic widen what a compromised control plane can express, or narrow it?
- Are there other seeded-then-mutated files already in the catalog that this failure is
- Are there other seeded-then-mutated files already in the catalogue that this failure is
waiting inside?
@@ -6,7 +6,7 @@ opened: 2026-09-02
## The symptom, as observed
Found by review of the catalog examples (2026-09-02). The media stack is several modules —
Found by review of the catalogue examples (2026-09-02). The media stack is several modules —
a library server, the acquisition managers, a download client and their satellites — and each
of them declares the same library and download directories as its own resources.
@@ -28,7 +28,7 @@ shared workspace of several modules was written six times as six private ones. T
a media-stack peculiarity: any pipeline of modules handing files to each other on one machine
(an ingest directory, a spool, a drop folder) hits the same wall.
It is also a fork in the design the catalog has otherwise avoided: the fix could be a new
It is also a fork in the design the catalogue has otherwise avoided: the fix could be a new
owning module the others depend on, a shared-resource concept in the manifest, or a statement
that co-located file handoff is not a thing the mesh supports and these modules are one
module. Each answer changes what a module *is*, which is why this is an issue and not a patch.
@@ -6,7 +6,7 @@ opened: 2026-09-05
## The symptom, as observed
Found while converting the catalog (2026-09-05), across several modules at once. A module can
Found while converting the catalogue (2026-09-05), across several modules at once. A module can
declare *things that exist* — a directory, a file with fixed content, a network, a container — but
it cannot declare *a step that runs* at a defined point in its own lifecycle. Three converted
modules need exactly that and have nowhere to put it:
@@ -2,7 +2,7 @@
opened: 2026-09-10
---
# 039 — The lab's registry was pinning what the catalog left unpinned
# 039 — The lab's registry was pinning what the catalogue left unpinned
## Symptom
@@ -43,7 +43,7 @@ quietly supplying a property of the system under test, and nothing said so.
## Fixed, and what the fix does not settle
Each of the nine references now names the digest its tag resolved to, read from the registry that
serves them. Every manifest in the catalog is pinned; the host's refusal has nothing left to
serves them. Every manifest in the catalogue is pinned; the host's refusal has nothing left to
catch, and the check that would have caught this — *no manifest names a tag* — now passes on
content rather than on a harness's rewriting.
@@ -57,14 +57,14 @@ either way.
Still open, and worse than reported: the controller's own manifest now delivers its store
connections **and** its broker credentials through an env-file, so both halves reach the process
environment; 28 catalog manifests use env-file for a secret, and nothing in the catalog
environment; 28 catalogue manifests use env-file for a secret, and nothing in the catalogue
engine refuses a `${secret:…}` placeholder in one. The vault work of
ADR 0085 rests on the seal this weakens.
## Resolved 2026-09-21
By ADR 0086: a secret reaches
a process as a file, an environment exception is declared with a reason, and the catalog engine
a process as a file, an environment exception is declared with a reason, and the catalogue engine
refuses the undeclared shape. The controller, the instance this was opened on, reads all six of
its credentials from files. The 35 other containers carry a declared reason; converting each where
its software accepts a path remains per-module work and is not this issue's.
@@ -107,7 +107,7 @@ recipe — so the mesh could *say* which modules a base change had invalidated,
produced the old base anyway until somebody edited that line by hand.
Worse than inconvenient, as it turned out. **A fingerprint names one particular copy of an image,
and a copy exists on one mesh.** The three modules in the catalog named a copy produced inside a
and a copy exists on one mesh.** The three modules in the catalogue named a copy produced inside a
throwaway lab, so none of them could be built anywhere else at all; and the line each of them
replaced named a copy from an even earlier throwaway lab. Nobody noticed because the only place they
had ever been built was the place the copy existed.
@@ -56,7 +56,7 @@ The rules are computed from what modules declare they listen on. During a migrat
about almost nothing — the services are still running under the system being replaced — so it opens
almost nothing. Anything listening **on the host** rather than in a container is then dropped.
**Including ssh.** No module in the catalog declares an ssh port, and the generator has no
**Including ssh.** No module in the catalogue declares an ssh port, and the generator has no
built-in allowance for one. So the ruleset loaded on a machine that has not been told otherwise
accepts established connections, loopback and ping, and refuses every new ssh connection.
@@ -23,16 +23,16 @@ So the account is right and the request is reasonable, and there is no account t
## Why this matters
**A module's tools are its operator-facing surface, and nothing can reach it.** The catalog serves
**A module's tools are its operator-facing surface, and nothing can reach it.** The catalogue serves
five: what this mesh holds, what a module is made of, what provides a given provision, what depends
on a module, and what is stale. Those are the questions the catalog exists to answer, and today
on a module, and what is stale. Those are the questions the catalogue exists to answer, and today
the answer to "how does anyone ask one" is that they run a container joined to the broker's network
namespace and authenticate as the substrate's bootstrap admin.
**It is why a running module gets mistaken for a working one.** A test that cannot ask a module
anything checks that its container is up, and a container being up is not a claim about function.
That substitution has now hidden two separate faults in one week — a crash-looping runtime beside a
healthy container of a similar name, and a catalog that was never installed at all.
healthy container of a similar name, and a catalogue that was never installed at all.
**The mesh already has the shape for this.** An account scoped to a purpose, minted by the mesh and
sealed to a holder, is exactly what provisioning does. What is missing is the recognition that
@@ -2,12 +2,12 @@
opened: 2026-09-14
---
# 050 — The catalog knows nothing that was built before it
# 050 — The catalogue knows nothing that was built before it
## Symptom
A mesh raised from bare metal built six modules in order: the shared base, a store, the catalog,
the control plane, a module, and a broker. Asked afterwards what it holds, the catalog answered
A mesh raised from bare metal built six modules in order: the shared base, a store, the catalogue,
the control plane, a module, and a broker. Asked afterwards what it holds, the catalogue answered
with three — exactly the three built *after* it started running:
```
@@ -16,47 +16,47 @@ lavinmq built from <the catalogue repository>
mesh-control built from <the control plane repository>
```
Missing: the shared base, the store, and the catalog itself.
Missing: the shared base, the store, and the catalogue itself.
The catalog learns what exists by consuming the builder's announcement over the broker. Anything
The catalogue learns what exists by consuming the builder's announcement over the broker. Anything
built before it was running was announced to nobody, and nothing goes back for it.
## Why this matters
**The modules it cannot see are the ones a mesh is made of.** On a fresh mesh the things built
before the catalog are, necessarily, the things the catalog needed in order to exist: the base
before the catalogue are, necessarily, the things the catalogue needed in order to exist: the base
it was compiled against and the store it keeps its records in. So the hole is not random — it is
always the foundation, on every mesh, at exactly the moment the graph is first populated.
**It breaks the question the catalog exists to answer.** Build edges are derived facts between
versions, and the base is the node nearly every other module hangs off. A catalog with no record
**It breaks the question the catalogue exists to answer.** Build edges are derived facts between
versions, and the base is the node nearly every other module hangs off. A catalogue with no record
of the base cannot know that moving it makes everything standing on it stale, so `catalog_stale`
answers confidently and wrongly — the worst shape for a question about what to rebuild.
**Nothing reports the gap.** The catalog does not know what it was not told, so it does not say
**Nothing reports the gap.** The catalogue does not know what it was not told, so it does not say
"three of six" — it says three, and a reader with no independent count believes it. This was found
by asking it a question and comparing the answer against what the mesh had just been watched doing.
**The record is not lost, only the catalog's copy.** The control plane holds every build it
**The record is not lost, only the catalogue's copy.** The control plane holds every build it
ordered, with commit, repository, path and resolved artifacts. So this is a gap between two stores
that should agree, rather than information nobody has.
## Open questions
- **Should the catalog backfill on start** — ask the control plane for the builds it already
recorded and take them in? That fixes the fresh-mesh case and every case where the catalog was
- **Should the catalogue backfill on start** — ask the control plane for the builds it already
recorded and take them in? That fixes the fresh-mesh case and every case where the catalogue was
down while something was built, which is the same fault with a different cause.
- **Or should announcements be durable**, so a build announced while nothing was listening is
delivered when something is? That treats the catalog as one consumer among several, which it
delivered when something is? That treats the catalogue as one consumer among several, which it
already is.
- **Or should the control plane be the record and the catalog a projection of it?** Two stores
- **Or should the control plane be the record and the catalogue a projection of it?** Two stores
that must agree, kept in step by events, is the arrangement that produced this.
- **How would anyone notice next time?** The catalog cannot compare itself to a source of truth
- **How would anyone notice next time?** The catalogue cannot compare itself to a source of truth
it does not have. Whatever is chosen, something has to be able to say "these disagree".
## How this would be checked
| Rule | Checked by |
|---|---|
| The catalog holds every module the mesh built | A mesh is raised from bare metal and the catalog is asked; its list is compared against the control plane's build records, and a module in one and not the other is a failure. |
| A change to the base makes what stands on it stale | The base is rebuilt on a fresh mesh and the catalog names the modules that must follow — which requires it to know the base exists. |
| The catalogue holds every module the mesh built | A mesh is raised from bare metal and the catalogue is asked; its list is compared against the control plane's build records, and a module in one and not the other is a failure. |
| A change to the base makes what stands on it stale | The base is rebuilt on a fresh mesh and the catalogue names the modules that must follow — which requires it to know the base exists. |
@@ -53,11 +53,11 @@ machine.
## Open questions
- **Which private registry, and does the bootstrap have one?** The catalog holds `verdaccio`;
- **Which private registry, and does the bootstrap have one?** The catalogue holds `verdaccio`;
a git host typically serves package registries too. ADR 0014 says *the* private registry as
though there is one, and today a fresh mesh has neither until something installs it — so the
first SDK build happens before the registry the record assumes.
- **Or should the SDK be a published package?** The catalog holds a private registry module, and a
- **Or should the SDK be a published package?** The catalogue holds a private registry module, and a
published package is how the rest of the world does this — at the cost of a mesh needing that
registry up before it can build anything, which is a bootstrap problem where there is currently
none.
@@ -9,7 +9,7 @@ opened: 2026-09-16
Adopting the store and broker as ordinary modules (issue 051) needs them reachable by
consumers across the mesh, so genesis now raises `mesh-store` and `mesh-broker` bound to
`0.0.0.0` rather than to loopback as the foundation used to. The packet filter is a module,
installed several steps after the store, the broker and the catalog are already running.
installed several steps after the store, the broker and the catalogue are already running.
Between each server coming up and the filter being applied, both listen on every interface
of the machine with only the genesis bootstrap credentials, and nothing drops traffic to
@@ -35,7 +35,7 @@ container in place must declare the same bind, and the module has to serve consu
- Can a default-deny base filter (drop everything but loopback, established, and ssh) be
applied at genesis, before the store and broker come up, and the mesh-scoped rules refined
once the private network has addresses to name? The consumers that must reach the store
(the catalog) come up before the network step, so the `from: mesh` rule would have to be
(the catalogue) come up before the network step, so the `from: mesh` rule would have to be
in place by then.
- Or should the servers bind narrowly at genesis (loopback plus the container bridge) and
widen only once the filter that protects them exists — accepting that a bind change is a
@@ -2,11 +2,11 @@
opened: 2026-09-17
---
# 060 — The mesh cannot build most of its own catalog
# 060 — The mesh cannot build most of its own catalogue
## Symptom
Only 8 of the catalog's 70 modules carry a `build` section; 62 do not. A module without one
Only 8 of the catalogue's 70 modules carry a `build` section; 62 do not. A module without one
cannot be built by the mesh's own builder: its runtime container pins a placeholder digest
(`mesh-runtime-<m>@sha256:0000…`) that only an out-of-band script ever resolves — the lab builds
the image on a workstation and rewrites the manifest before registering it. Asked to `build` such
@@ -1,6 +1,6 @@
# Diagnosis — 2026-09-18
The gap was mechanical for most of the catalog and structural for a few.
The gap was mechanical for most of the catalogue and structural for a few.
**The mechanical part (fixed).** 44 of 70 modules now carry a `build` section and a Dockerfile
mirroring the eight that already had one: two named bases (build and runtime), the module's own
@@ -41,4 +41,4 @@ in one missing manifest field.
provably runs no provisioner.
- Can validation know an image's entrypoints, so "compiled but never named by any container" is
refusable at build time rather than discoverable only by an end-to-end bed?
- Does any other converted module in the catalog have the same shape today?
- Does any other converted module in the catalogue have the same shape today?
@@ -25,5 +25,5 @@ same bed: with the command named, the vhost and user are minted and the consumer
connection over the overlay (bed run of 2026-09-18).
The open questions in the report — refusing a provider whose containers all default their
command, and auditing the rest of the catalog for the same shape — remain open; this record
command, and auditing the rest of the catalogue for the same shape — remain open; this record
covers the one module the bed proved.
@@ -8,7 +8,7 @@
went to the composing code with two candidates: the declaration never named the trust, or it
named it and was never applied.
3. The composer's trust step asks which machine on the network is assigned a module serving the
artifact-store provision. Two silent degradations sat in that path: a failed catalog read
artifact-store provision. Two silent degradations sat in that path: a failed catalogue read
returned "not found", and a failed per-machine assignment read *skipped that machine* and kept
scanning. Either converts a transient inventory error into a declaration without the trust,
under a push that reports success.
@@ -18,7 +18,7 @@ from a public registry the build environment does not reach:
apt-based installs in the same Dockerfiles succeed, so the build has ordinary internet: it is npm
and image pulls specifically that are redirected to the mesh's registry. Observed while giving the
catalog its build sections (hq issue 060): 44 of 70 modules build from their own source with no
catalogue its build sections (hq issue 060): 44 of 70 modules build from their own source with no
external fetch; these three need one and stop.
## Why it matters beyond the instance
@@ -15,15 +15,15 @@
3. Ruled out: that the build environment has no internet. Package installs from the system's
repositories succeed in the same Dockerfiles.
**Located in:** the builder (what it tells npm and the runtime) and the catalog (three modules
**Located in:** the builder (what it tells npm and the runtime) and the catalogue (three modules
whose Dockerfiles fetch what no manifest names). The image half is decided and built:
ADR 0097 — a vendor image
is declared under `build.on`, copied in, and a recipe fetching what is undeclared is refused. Still
open: the package half — a build must be re-run against the mesh's proxying registry to place the
404 — and the three recipes themselves, which now declare their images or are refused.
*2026-09-21, later.* The package half was placed by a run rather than a reproduction: the catalog
was read again, and exactly one recipe installs a public package — the catalog module's own,
*2026-09-21, later.* The package half was placed by a run rather than a reproduction: the catalogue
was read again, and exactly one recipe installs a public package — the catalogue module's own,
which installs a postgres driver into an empty directory. That module was built through the mesh's
builder and installed in the genesis bed run that day, against the mesh's package registry as it
now proxies the public one. The 404 the report saw is not reproducible on current code. No recipe
@@ -13,7 +13,7 @@ minted for that consumer↔vault pair. A pair has **exactly one** credential —
rotation touches one holder and nothing else.
A module may only require a provision name once, so a module that requires `secret` receives
**one value**. Counted across the catalog at the time of writing, a module's own secrets other
**one value**. Counted across the catalogue at the time of writing, a module's own secrets other
than its broker account number as follows:
| own secrets | modules |
@@ -4,7 +4,7 @@
a pair credential is keyed on (name, consumer node, consumer module, provider), and the login
the vault derives is keyed on the (node, module) pair. Two secrets for one module and one vault
collide on every key.
2. The third question was answered by reading the catalog as it is today: nine modules hold two
2. The third question was answered by reading the catalogue as it is today: nine modules hold two
or more own secrets besides their broker account. Two hold a relay user beside a relay
password — the user is a name, not a secret, and could travel as configuration. The other
seven hold genuinely independent values with independent lifetimes: an internal token beside
@@ -18,7 +18,7 @@
is a manifest-vocabulary decision, and it moves the pair key: the credential must be keyed on
the local name, not the provision name, or the second pair overwrites the first.
**Located in:** the manifest's `secrets` vocabulary (the catalog parser) and the pair
**Located in:** the manifest's `secrets` vocabulary (the catalogue parser) and the pair
credential's key (the controller's secret store). Fixed as
ADR 0094: the
key gains the local name, empty for every existing pair, so nothing that exists changed.
@@ -7,12 +7,12 @@ opened: 2026-09-21
## Symptom, as observed
The control plane's manifest lives at the root of its own repository, where the mesh reads it
whenever it builds the controller from source, and again in the catalog under
whenever it builds the controller from source, and again in the catalogue under
`modules/mesh-controller`, where the installer reads it at genesis. The two differ only in how the
image is named — a build section in one, a placeholder digest in the other — and are otherwise
meant to be the same document.
Changing the catalog copy alone (to make the controller read its credentials from files,
Changing the catalogue copy alone (to make the controller read its credentials from files,
ADR 0086) produced a mesh that
installed correctly and then could not be pushed to: the first rebuild of the controller from
source replaced the mesh's record with the repository's copy, which still carried the old shape,
@@ -30,6 +30,6 @@ machine as doing what it was told throughout, because nothing had been sent.
## What would close it
Either the catalog copy goes and genesis reads the controller's manifest from the controller's
Either the catalogue copy goes and genesis reads the controller's manifest from the controller's
repository (the installer already has the checkout, since it builds from it), or a check refuses
two copies that differ outside the image lines. The first is the honest one.
@@ -2,16 +2,16 @@
1. The two documents were compared. They differ in exactly one place: the repository's names its
server container by a build artifact and carries the build section that produces it; the
catalog's names a placeholder image digest and carries no build section. Everything else was
catalogue's names a placeholder image digest and carries no build section. Everything else was
equal on the day of reading, which is the only day that can be said of.
2. Who reads which. The mesh's own builder reads the repository's, whenever the control plane is
rebuilt from source, and registers the manifest it built with the artifact resolved to the
image — that is what replaced the mesh's record. The installer's step 9 read the catalog's,
image — that is what replaced the mesh's record. The installer's step 9 read the catalogue's,
pinned its placeholder to the image the registry assigned, and registered that. Nothing else
read the catalog's copy.
read the catalogue's copy.
3. The decision was already taken. ADR 0069
says the control plane's manifest belongs in its own repository, not the catalog, and the
raising design names the catalog copy as "a thing that can be removed rather than a thing
says the control plane's manifest belongs in its own repository, not the catalogue, and the
raising design names the catalogue copy as "a thing that can be removed rather than a thing
that must be designed". It was not removed because step 9 had no other source — at genesis the
installer carries the builder, not the control plane, and reads manifests off a checkout put on
the machine by hand.
@@ -20,10 +20,10 @@
artifact resolved to the image the machine now holds. Step 9 was reading a second copy of a
document it had been handed six steps earlier.
**Located in:** mesh-host `internal/bootstrap` (steps 3 and 9), and the catalog's copy. The fix
**Located in:** mesh-host `internal/bootstrap` (steps 3 and 9), and the catalogue's copy. The fix
keeps the built manifest from step 3, registers it at step 9 with the built image's bare id
re-pinned to the registry's reference, and deletes the catalog's copy. The builder module's
manifest is still the catalog's and still pinned from a placeholder — ADR 0069 puts it in the
re-pinned to the registry's reference, and deletes the catalogue's copy. The builder module's
manifest is still the catalogue's and still pinned from a placeholder — ADR 0069 puts it in the
control plane's repository too, and it is not there yet; that is a smaller instance of the same
gap, left open here and named in the lab's genesis check. Ruled out: teaching the two copies to
stay equal (a check across two repositories that only fires after somebody edits one), and reading
@@ -2,18 +2,18 @@
opened: 2026-09-21
---
# Beds carry copies of catalog manifests, so a catalog change is proven nowhere
# Beds carry copies of catalogue manifests, so a catalogue change is proven nowhere
## Symptom, as observed
The per-module beds (`assigned-catalogue-small`, `assigned-catalogue-apps`, `assigned-grafana`,
`assigned-model-usage`, `assigned-redis`, and others) build the manifests they install inline, as
JSON in the test, rather than reading `modules/<name>/module.json` from the catalog checkout the
JSON in the test, rather than reading `modules/<name>/module.json` from the catalogue checkout the
lab is pointed at. The copies were taken when each bed was written and have not moved since.
Converting six modules to file-delivered secrets (ADR 0086)
therefore changed nothing any of those beds run. Worse: the copies still deliver secrets through
an env-file without the declared exception, which the catalog engine now refuses, so the beds
an env-file without the declared exception, which the catalogue engine now refuses, so the beds
would fail against a current controller for a reason that has nothing to do with what they test.
## Why it matters beyond this instance
@@ -22,13 +22,13 @@ would fail against a current controller for a reason that has nothing to do with
bed: a manifest with two sources of truth, and the one the proof runs against is the stale one.
- "Proven in the lab" is the standard playbook 06 sets for a module. A bed that installs a copy
proves the copy.
- The genesis bed and the vault bed already read the catalog (`--catalog` and a
- The genesis bed and the vault bed already read the catalogue (`--catalog` and a
`catalogueManifest()` helper that swaps the build artifact for the image the lab built); the
pattern exists and is one helper away for the rest.
## What would close it
Every bed that installs a catalog module reads its manifest from the catalog checkout,
Every bed that installs a catalogue module reads its manifest from the catalogue checkout,
rewriting only what the lab must (the built artifact's image, a lab-local address). The inline
copies go. Until then, each bed's copy is updated by hand alongside the catalog, which is how
copies go. Until then, each bed's copy is updated by hand alongside the catalogue, which is how
the six conversions were proven.
@@ -1,33 +1,33 @@
# Diagnosis — 2026-09-21
1. Every bed was read against the catalog. Of forty-five, thirteen already read a manifest
from the catalog checkout, twenty-one built one or more inline, and eleven install no
catalog module. The thirteen readers used five private copies of one loader, and the copies
1. Every bed was read against the catalogue. Of forty-five, thirteen already read a manifest
from the catalogue checkout, twenty-one built one or more inline, and eleven install no
catalogue module. The thirteen readers used five private copies of one loader, and the copies
had drifted: one never resolved a runtime artifact to the image the lab stocks, so a module
the mesh builds travelled to the machine unresolved; one still asked the catalog for two
the mesh builds travelled to the machine unresolved; one still asked the catalogue for two
modules by names it no longer uses and recorded the miss as "not assigned".
2. The inline copies fall into three kinds, and only the first is what the report assumed:
- copies that differ from the catalog only in what the lab must rewrite — an image, a
- copies that differ from the catalogue only in what the lab must rewrite — an image, a
build section, an optional key dropped. Ten modules across eight beds;
- a second provider raised beside the foundation's. The catalog's postgres and lavinmq
- a second provider raised beside the foundation's. The catalogue's postgres and lavinmq
*claim* the foundation's store and broker and adopt them in place; four beds raise a
second one next to it, renamed and on a private network. Reading the catalog there
second one next to it, renamed and on a private network. Reading the catalogue there
changes what the bed raises, and its assertions with it;
- a module cut down to the shape a mesh mechanism needs — no upstream server, a secret in
the environment, a requirement edge removed so no second provider is wanted — under a
catalog module's name. Twelve beds. These are mesh tests wearing a module's name, and
the honest fix is a name of their own, not a catalog read
catalogue module's name. Twelve beds. These are mesh tests wearing a module's name, and
the honest fix is a name of their own, not a catalogue read
([issue 074](../074-a-mesh-test-wears-a-catalogue-modules-name/00-report.md)).
3. The receipt claimed the host, the controller and the lab and never the catalog, so even a
bed that read the catalog could not be told apart from one that had read it last week.
3. The receipt claimed the host, the controller and the lab and never the catalogue, so even a
bed that read the catalogue could not be told apart from one that had read it last week.
4. While converting, the images the beds stock for the modules' runtimes were found to date
from two weeks before the manifests they serve — built by hand, by a script the run never
calls. The run rebuilds the host and the controller and not these
([issue 075](../075-a-stocked-runtime-image-is-never-rebuilt-by-the-run/00-report.md)).
**Located in:** mesh-lab, the integration beds and their harness. The fix gives the harness one
loader that reads the catalog and rewrites only what the lab must, converts the first kind of
copy to it, folds the five private loaders onto it, makes the receipt claim the catalog, and
adds a unit test that refuses an inline copy naming a catalog module unless the bed is declared
loader that reads the catalogue and rewrites only what the lab must, converts the first kind of
copy to it, folds the five private loaders onto it, makes the receipt claim the catalogue, and
adds a unit test that refuses an inline copy naming a catalogue module unless the bed is declared
with its reason. The second and third kinds are declared there; each declaration names the work
that removes it. Decided in ADR 0089.
@@ -2,18 +2,18 @@
opened: 2026-09-21
---
# A mesh test wears a catalog module's name
# A mesh test wears a catalogue module's name
## Symptom, as observed
Twelve lab beds install a module named `redis`, `grafana`, `plex`, `sonarr`, `minio`, `postgres`,
`route-proxy` or `hello-web` that is not the catalog's module of that name. Each is cut down to
`route-proxy` or `hello-web` that is not the catalogue's module of that name. Each is cut down to
what the bed's mechanism needs: the sidecar runtime without the upstream server it manages; a
token or an API key in the container's environment where the catalog delivers a file; a
token or an API key in the container's environment where the catalogue delivers a file; a
requirement on a route or a certificate authority removed so no second provider is needed; a
vault-granted secret turned into one the module mints itself; a route contribution in the shape a
decision replaced. One bed's header says its manifests are "verbatim from the catalogue" and its
manifest adds three resources the catalog has not got.
manifest adds three resources the catalogue has not got.
Found while diagnosing [issue 073](../073-beds-carry-copies-of-catalogue-manifests/00-report.md);
the beds are listed, each with what it cuts, in the lab's `beds-read-the-catalogue` unit test.
@@ -22,16 +22,16 @@ the beds are listed, each with what it cuts, in the lab's `beds-read-the-catalog
- **A green bed named for a module reads as that module proven.** The status view counts a bed
by the module it names; a bed proving a grant mechanism with a `redis` that mints its own
secret proves nothing about the catalog's redis, which requires the vault's.
secret proves nothing about the catalogue's redis, which requires the vault's.
- **The cut is invisible.** Nothing distinguishes "this is redis" from "this is a redis-shaped
fixture" except reading the literal against the catalog, which is what issue 073 found nobody
fixture" except reading the literal against the catalogue, which is what issue 073 found nobody
had done.
- ADR 0089 now refuses the
copy; until each bed is renamed or made to read the catalog, it is declared debt.
copy; until each bed is renamed or made to read the catalogue, it is declared debt.
## What would close it
Each of the twelve beds either reads the catalog's module and meets its declared requirements
Each of the twelve beds either reads the catalogue's module and meets its declared requirements
(a route needs the route module beside it; a secret needs the vault), or gives its fixture a name
that is no catalog module's — `a-cache`, `a-store`, `a-sidecar` — so a green run claims exactly
that is no catalogue module's — `a-cache`, `a-store`, `a-sidecar` — so a green run claims exactly
what it proved. The declared list in the unit test is empty of the `WEARING` reason.

Some files were not shown because too many files have changed in this diff Show More