Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e65f897a1f | ||
|
|
7fd89de7cb | ||
|
|
f4fdbde558 | ||
|
|
7d022710b3 | ||
|
|
f89b8599c6 | ||
|
|
eec73b8af0 | ||
|
|
1aa887dd28 | ||
|
|
aa4c2fe7ef | ||
|
|
292232dad2 | ||
|
|
a9830f5110 | ||
|
|
02a18c9aa8 | ||
|
|
606956da97 | ||
|
|
d088f17cd2 | ||
|
|
dd03229ea8 | ||
|
|
781392b121 | ||
|
|
c2f4bb2d48 | ||
|
|
ea0c62355c | ||
|
|
8f8393fa93 | ||
|
|
dd36b7557a | ||
|
|
d348256090 | ||
|
|
2ea3368b76 | ||
|
|
3c2d5af436 | ||
|
|
bcad1e363a | ||
|
|
edbb53bb78 | ||
|
|
32da464794 | ||
|
|
10bc8d396c | ||
|
|
8e732e22ae | ||
|
|
6ef40bbcc7 | ||
|
|
7ff4c7ce68 | ||
|
|
0d1aa93e37 | ||
|
|
1afab9739e | ||
|
|
420751b214 | ||
|
|
043f8ecf9c | ||
|
|
132aae8433 | ||
|
|
28d84f31bf | ||
|
|
278ca42b3d | ||
|
|
1dab64134a | ||
|
|
a0e4c1fe17 | ||
|
|
7c88f297fc | ||
|
|
495d740d5f | ||
|
|
df4ff3d039 | ||
|
|
d698942b1d | ||
|
|
a6cc3d2e0b | ||
|
|
b6572acd72 | ||
|
|
ebc1a84e59 | ||
|
|
5bb109e36e | ||
|
|
d948774794 | ||
|
|
68f8a8c198 | ||
|
|
b5b84fafc6 | ||
|
|
0488bc1801 | ||
|
|
973182cdf9 | ||
|
|
97d600650a | ||
|
|
515c405c89 | ||
|
|
a6e27687db | ||
|
|
b7dabd8ceb |
@@ -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 catalogue's copies of the retired words and the daemons' lines with the glossary.
|
||||
test also compares the catalog'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 catalogue'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 catalog'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 catalogue's copy
|
||||
spans included, quotations and block quotes taken out. With `MESH_CATALOG_DIR` set it compares the catalog'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 —
|
||||
|
||||
@@ -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 catalogue, its copies of the retired words and the
|
||||
// copy with MESH_CATALOG_DIR set to a checkout of the catalog, 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
|
||||
@@ -396,7 +396,7 @@ func checkUnique(retired []retiredWord, heads []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// linesList is the daemons' lines as the catalogue's copy lists them: "<daemon> <command>", with " …" when an
|
||||
// linesList is the daemons' lines as the catalog'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 catalogue's copies of the retired words and the daemons' lines with this glossary's. A
|
||||
// checkCopy compares the catalog'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 catalogue's copy of the retired words cannot be read: " + err.Error()}
|
||||
return []string{"the catalog'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 catalogue's retired-words differs from the glossary: it should list "+strings.Join(want, ", "))
|
||||
out = append(out, "the catalog'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 catalogue's copy of the daemons' lines cannot be read: "+err.Error())
|
||||
return append(out, "the catalog'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 catalogue's retired-lines differs from this check's list: it should list, one per line:\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"))
|
||||
}
|
||||
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 catalogue's copy of the list was not read")
|
||||
t.Skip("NOT COMPARED: MESH_CATALOG_DIR is not set, so the catalog'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 catalogue comparison.
|
||||
// H5d: the daemons' lines and the catalog comparison.
|
||||
|
||||
func TestLinesCatchesADaemonsLineButNotProse(t *testing.T) {
|
||||
fsys := mapOf(map[string]string{
|
||||
|
||||
+65
-27
@@ -66,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 catalogue, one manifest per module. **Upstream of** every other domain; its words
|
||||
**Recorded in** the catalog, 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
|
||||
@@ -104,7 +104,8 @@ 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.
|
||||
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).
|
||||
|
||||
## Config — the values someone may change
|
||||
|
||||
@@ -134,11 +135,15 @@ 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~~
|
||||
- **predefined key** — a key of the catalogue's `predefined.json`, the fixed list of keys for which mesh
|
||||
- **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
|
||||
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
|
||||
catalogue build's: a change to it is validated with every manifest naming a key, and removing a key still
|
||||
catalog 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
|
||||
@@ -235,18 +240,31 @@ 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 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.
|
||||
**Uses:** node, machine, module, seat, manifest (Module), config value (Config).
|
||||
**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.
|
||||
**Uses:** node, machine, module, seat, manifest (Module), config value (Config), proposal (Operator and conversation).
|
||||
|
||||
- **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 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** — 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).
|
||||
- **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).
|
||||
- **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).
|
||||
@@ -496,9 +514,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.
|
||||
- **catalogue** — what the mesh holds of every module, at which commit; and the repository the
|
||||
- **catalog** — 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:* ~~catalog~~ (hq)
|
||||
*Not:* ~~catalogue~~
|
||||
- **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).
|
||||
@@ -548,7 +566,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 catalogue that owns deliveries and delivery groups, holding the mesh-scoped
|
||||
ADR 0429 also the module in the catalog 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
|
||||
@@ -559,7 +577,8 @@ 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) and the
|
||||
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
|
||||
**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.
|
||||
@@ -627,7 +646,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.
|
||||
becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
|
||||
**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
|
||||
@@ -641,7 +660,14 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
|
||||
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.
|
||||
**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).
|
||||
- **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.
|
||||
- **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;
|
||||
@@ -649,8 +675,8 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
|
||||
(ADR 0231).
|
||||
What a wrapped dashboard raises is its own; the mesh's notion is a condition.
|
||||
*Not:* ~~alert~~ (hq)
|
||||
- **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.
|
||||
- **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.
|
||||
*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.
|
||||
@@ -660,7 +686,15 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283.
|
||||
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.
|
||||
- **hand-act** — a repair a person made by hand, recorded so the mesh knows it happened. The **hand-act log**
|
||||
is 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).
|
||||
- **witness** — what rolls a core component back when its new build does not become healthy: never the
|
||||
component itself (to-be 45 §8).
|
||||
|
||||
@@ -735,7 +769,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 catalogue's check refuse
|
||||
or the node-engine's binary followed by a command), which the words test and the catalog'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`
|
||||
@@ -811,7 +845,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 catalogue outside the
|
||||
episode, a book, a session of a sport event. It is known by an identity from a catalog 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
|
||||
@@ -864,7 +898,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 *catalogue entry*: the catalogue is Delivery's.
|
||||
due date; never the body (ADR 0459 §4). Not *catalog entry*: the catalog 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
|
||||
@@ -932,6 +966,10 @@ Not a domain of the mesh but of the way it is built, listed because its words me
|
||||
- **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).
|
||||
- **progress** — of a design, a milestone, an issue or a record: how far it has come, each part done, moving,
|
||||
waiting or stuck with when it last moved, worked out by the knowledge base's verb `progress` from the issue tracker
|
||||
and the deliveries and never written by anyone; `minimal` says one line per part, `detailed` the tree of a lead's
|
||||
report (issue 1947).
|
||||
- **glossary entry** — one entry of the glossary as a record of the knowledge base: its head word or words, its
|
||||
domain, its meaning, its *Not:* line and its identifier line (ADR 0423). Until the move, an entry of this page.
|
||||
- **milestone** — a group of a design's issues delivered together, as one delivery group, or one pull request when
|
||||
@@ -1022,7 +1060,7 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| 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 controller's own examination | **self-check** | Health and repair |
|
||||
| | the mesh'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 |
|
||||
@@ -1058,7 +1096,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 catalogue as `retired-words`, whose own repository check holds the descriptions of
|
||||
are copied into the catalog 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.
|
||||
|
||||
+18
-2
@@ -173,6 +173,22 @@ quietly stop being true, and nobody will learn that from a document.
|
||||
Not test-driven development as a blanket rule — a test written first against undiscovered
|
||||
behaviour asserts a guess. The obligation is that every decision has a defender.
|
||||
|
||||
### A bus permission is proven under the real permission
|
||||
|
||||
A change that adds or widens what a mesh process reads or writes on the bus (a subscription, a publish, a stream or
|
||||
key-value read, a consumer) carries an integration test that runs it on a bus user with exactly the production
|
||||
permissions of that account (mesh-sdk's composition of them), against a real bus server, and that fails with the
|
||||
bus's permissions violation when the widest new access is not granted. It runs in the forge's check like every
|
||||
integration test. A reviewer reading code does not see a grant, and a fake bus grants everything (ADR 0560).
|
||||
|
||||
How it is checked: a review that approves such a change whose pull request names no such test is the rule skipped.
|
||||
|
||||
### A bus permission under the real permission — how it was earned
|
||||
|
||||
On 2026-10-27 a change widened what every node agent watches; two reviews read its code and its tests ran against a
|
||||
fake bus. Live, every node's agent was refused that read by its grant and stopped composing, and the same day the
|
||||
grants part's user list dropped nine seats' subscriptions (issues 1126 and 1945).
|
||||
|
||||
### Tests run at the lowest level that proves them, within a time budget
|
||||
|
||||
Three levels, by how much real system a test uses (ADR 0391):
|
||||
@@ -180,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 catalogue 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 catalog 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
|
||||
@@ -193,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 catalogue's whole merge check (about ten minutes), a
|
||||
On 2026-10-16 a feature's fixing agents ran the catalog'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.
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ A drawn overview of them, the cycle, a feature's steps, the review loop and an i
|
||||
| [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces |
|
||||
| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh |
|
||||
| [07](07-feature-branches.md) | Features | Work that changes code across issues or repositories: prepared context, local development, one review, one check, one delivery |
|
||||
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one try, one review, one delivery |
|
||||
| [08](08-small-fixes.md) | Small fixes | One issue in one repository, about an hour of work: one review, one check, one delivery |
|
||||
| [09](09-cleanup.md) | Cleanup | Merged branches, stale pull requests, finished worktrees and folders: read first, delete only what the operator approves, report the rest |
|
||||
| [10](10-visual-design.md) | Visual design | A page the operator will use is about to be designed or rebuilt: mockups with fixed data, the operator picks, the pick is the reference |
|
||||
|
||||
|
||||
@@ -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 catalogue's bus, forge or build
|
||||
names the controller, the node-engine, the tool runner, the SDK, or the catalog'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);
|
||||
|
||||
@@ -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 catalogue branch, pushed once after
|
||||
- **The first check after the whole branch is written.** A feature's catalog 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
|
||||
@@ -35,25 +35,34 @@ A small fix (one repository, one issue, about an hour of work) follows
|
||||
proved nothing, and dropping the field it was for passed every test. A reader of the changed record that the
|
||||
survey never listed ended a module's conditions at each sync. The reviewer judged the bus tests by reasoning, and
|
||||
wrongly, because they ran only after the review.
|
||||
- **A check asked again and again.** Build capacity is the mesh's scarcest resource ("we don't have more
|
||||
processing power", the operator, 2026-10-26), and a release that ran a try before its review, a try after it
|
||||
and a check per fix round on an open pull request spent it several times over: every push to an open pull
|
||||
request asks a new check, of every member for a group. A release runs one check ("every release should only
|
||||
run 1 check or 1 try", the operator, 2026-10-26; ADR 0548).
|
||||
- **A node agent's bus reads, proven only by review.** On 2026-10-27 a change widened what every node agent watches
|
||||
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 module proven only after it reached the machines.** The mail modules were delivered with unit tests
|
||||
only, then failed live three times (issues 859, 865, 868) and took five more hours of fixes.
|
||||
|
||||
## The shape
|
||||
|
||||
One feature is **one branch name**, **one worktree per repository**, **one pull request per repository**,
|
||||
opened **once**, after the review. It runs in five steps, and its time is bounded. A milestone has no phases
|
||||
opened **once**, after the review, and **one check** (ADR 0548). It runs in five steps, and its time is bounded. A milestone has no phases
|
||||
(the operator, 2026-10-17): these are its steps, and a report names the step it is in:
|
||||
|
||||
| Step | What happens | Bound |
|
||||
|---|---|---|
|
||||
| 0. Prepare | the lead settles the open questions and writes one context file per repository | 15 minutes |
|
||||
| 1. Develop | one fixing agent per repository builds every issue locally, the whole unit suite before every commit; then each branch is pushed and tried | the milestone's issues, 30 minutes each, and one try |
|
||||
| 2. Review | one reviewer reads all of it with the tries' results; reports go both ways; at most 3 rounds | 15 minutes |
|
||||
| 3. Check | push the reviewed heads; a head the review changed is tried again; then the pull requests are opened and the forge's merge checks run the integration tests again; no further review | at most one more try and one check run |
|
||||
| 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 |
|
||||
| 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` |
|
||||
|
||||
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, tried before its review, reviewed once,
|
||||
opened once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
|
||||
**One milestone is one release** (the operator, 2026-10-17). A milestone is developed whole, reviewed once,
|
||||
pushed once, checked once and delivered once. It is never cut into steps for its size or for time: every cut adds a review, a
|
||||
merge check, an Approve and a delivery of waiting, so the fewest cuts is the fastest. Nor is it cut for an
|
||||
order between its parts (the operator, 2026-10-22: "a milestone is a milestone, we shouldn't cut it"): which module
|
||||
reaches the machines before which is the assigner's rules' (a build and a composition before the want, a provider
|
||||
@@ -84,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 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 detailed 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)
|
||||
@@ -100,7 +109,10 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
clone stays on `main`** (the operator, 2026-10-17): no work is done in it and no branch is checked out
|
||||
there, so a session that opens in it reads what holds now; fetch and fast-forward it before reading.
|
||||
3. **Settle every open question** in the feature's issues before any agent starts: on the issue, through
|
||||
the knowledge base, or by asking the operator. A fixing agent that meets an undecided design question
|
||||
the knowledge base, or by asking the operator. **Read what is decided before designing** (`knowledge-base.ask`):
|
||||
a question about delivery, checks or builds is most likely answered in design 47 and its decisions already (the operator,
|
||||
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.
|
||||
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:
|
||||
@@ -112,7 +124,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
- what not to touch, and what lands beside it in other repositories;
|
||||
- the local facts: the worktree, the branch, the check commands, a temporary directory for tests outside
|
||||
the home (`TMPDIR`), and the unit tests to run (never a database server: tests that need one are
|
||||
integration tests, which the try (`mesh-controller.try`) and the checker's merge check run).
|
||||
integration tests, which the pull request's one check runs).
|
||||
When the reading is large, a read-only agent on the smaller model answers the lead's exact questions with
|
||||
file:line, and the lead writes the file from its answers.
|
||||
5. **Write the findings into the issues** (the operator, 2026-10-17: the better the content of the issues, the
|
||||
@@ -145,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
|
||||
catalogue milestone found mesh-build-go and the delivery's indexer reading manifests with an SDK from before the
|
||||
catalog 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
|
||||
@@ -165,13 +177,12 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
(one line for every key-value bucket key and stream the work creates or starts writing; "none created" if none)
|
||||
Trying the switch-over on one node first (b):
|
||||
- <which node goes first, and what is read there before the rest>
|
||||
- or: the operator chose no try on one node first, on <date>; what that risks: <one sentence>
|
||||
Integration level before the pull requests (c):
|
||||
- <the tries that run it, per repository; a group try if issue 1921 has delivered one>
|
||||
- what no try covers: <the parts of the group that meet only in the merge check>
|
||||
- or: the operator chose no first trial on one node, on <date>; what that risks: <one sentence>
|
||||
Integration level (c):
|
||||
- <the one check that runs it: the group's pull requests, one check each, none before the review>
|
||||
- what the check does not cover: <the parts of the group that meet only live>
|
||||
```
|
||||
A key or stream with no one who deletes it and no time to live is a leak and is settled before work starts. Until
|
||||
issue 1921 gives a group try, answer (c) with the single-repository tries and say so.
|
||||
A key or stream with no one who deletes it and no time to live is a leak and is settled before work starts.
|
||||
9. **Move the issues to `fixing`**, `by` naming the feature's agent for that repository.
|
||||
|
||||
## Step 1 — Develop (one fixing agent per repository, smaller model)
|
||||
@@ -189,31 +200,32 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
its helper alone, and dropping the field from the request passed every test); `gofmt`, `go vet` and the whole unit suite stay green. **The tests are written from the issue's
|
||||
acceptance criteria**: one test or more per criterion at the level it names, each test's comment citing
|
||||
the criterion it proves by its label (`// 1118/AC2`), and no criterion left without its test; an integration criterion is a test
|
||||
that compiles and waits for the try at this step's end. The integration tests (those that start a store, a bus
|
||||
or a process) are run first by that try, and again by the checker's merge check.
|
||||
- **No pull request is opened in this step. When every issue is committed, the lead brings each feature branch up
|
||||
to the current trunk (fetch by name, then merge), pushes it and tries it** (`mesh-controller.try`, as step 3 item 1 says), so the review reads the integration tests' real
|
||||
results and judges none by reasoning (2026-10-19: a reviewer reasoned that a warning goes out at once, the
|
||||
router holds it 30 s, and the try after the review found it). A failed try goes back to the same fixing agent
|
||||
with its whole log, read as step 3 item 3 says, before the review starts. Never land on a red check: run the
|
||||
checks, read the result, then land in a separate step.
|
||||
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.
|
||||
- **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
|
||||
`mesh-controller.try`, `mesh-delivery-checker.try` or `mesh-delivery-checker.checker_try`; one is run only when the operator asks for it. A reviewer
|
||||
or a session that wants the code on the forge may push a branch that has no pull request: it runs nothing. Never land
|
||||
on a red check: read the result, then land in a separate step.
|
||||
- **The fixing agent's brief** (the lead gives it with the context file): name your issue in every step and
|
||||
message ("issue 805: landing in mesh-catalog"); reach the mesh only through the mesh MCP server, never ssh;
|
||||
never push, open, merge or close a pull request; never write to a store's database by hand, a schema change
|
||||
never push, open, merge or close a pull request; never call a try; never write to a store's database by hand, a schema change
|
||||
is a numbered migration and the consolidated schema; Go only unless the context file says otherwise; never
|
||||
move another issue; **leave the integration tests to the try while developing** (no `go test -tags integration`,
|
||||
no store or bus started by hand): compile them with `go vet -tags integration ./...` and leave the running to
|
||||
the lead's try at the end of this step, where `mesh-controller.try` runs them on the pushed branch. On 2026-10-17 three fixing agents ran them anyway, one for minutes on end, because the
|
||||
brief only said they wait for the check (the operator). After a failed try or check it
|
||||
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
|
||||
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 another try: a verdict names only the first failed
|
||||
test (issue 1921), and on 2026-10-23 R4g's faults surfaced one per try until a local run listed them all in a minute (a
|
||||
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
|
||||
guideline, the operator). Report back in under 150 words: commits, tests added and that they failed first, the
|
||||
check results, anything you stopped on.
|
||||
local results, anything you stopped on.
|
||||
- **An agent that finds the code differs from its context file stops and reports** (file:line); the lead
|
||||
settles it, updates the context file, and continues the agent.
|
||||
- **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 catalogue manifest that uses it.
|
||||
writer, the controller's new manifest word before the catalog 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
|
||||
@@ -222,11 +234,11 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
|
||||
## Step 2 — Review (one reviewer, larger model; at most 3 rounds, about 15 minutes)
|
||||
|
||||
1. **The reviewed tree is the tree that was tried**: every feature branch was brought up to the current trunk
|
||||
before its push and try (step 1). A try and a merge check each run on the merge with the trunk as it is then, so a
|
||||
1. **The reviewed tree is the tree that is pushed**: every feature branch was brought up to the current trunk
|
||||
before the review (step 1). The check runs on the merge with the trunk as it is then, so a
|
||||
trunk that moves later is met by the forge's merge check, not by another merge here. Every verdict names the
|
||||
trunk it merged onto ("pass <head> on <trunk>"), and the lead reads that trunk against the repository's main
|
||||
at once: a trunk behind main means the source revision (source.main) lags, and every check and try in that
|
||||
at once: a trunk behind main means the source revision (source.main) lags, and every check in that
|
||||
repository is judged on it until it moves, so the operator's Approve will merge nothing. Say it then, not at the
|
||||
merge (issue 1726: on 2026-10-24 the gate dropped two merges it heard before it held its seat, and a
|
||||
reviewed, approved pull request in each of the two repositories was refused at its Approve).
|
||||
@@ -238,7 +250,7 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
It says which round it is in ("round 1 of at most 3").
|
||||
3. **The report goes to the same fixing agents** that wrote the code, continued by their ids, so they fix
|
||||
with the context they already hold. Each answers the reviewer: what it changed, per finding, or why not.
|
||||
4. **Every new integration test is read against the code it drives and against its try.** Fixing agents only compile it, and its one run before the review is the try (step 1), which shows whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
|
||||
4. **Every new integration test is read against the code it drives.** Fixing agents only compile it, and no run precedes the review (step 1), so the reviewer judges it by reading; the check that follows shows whether it passed but not whether it proves what it says. The reviewer traces each step of each new integration test through the code (fixtures against the manifest rules, what the store keeps and reads back, who holds a seat), and says which steps it could not trace. On 2026-10-17 a new test failed on its first two runs, on a fixture and then on a fault of the change; reading it against the code after the first failure found another fault, which would have failed a run too. A review that finds a fault only by reading is the cheaper run. The reviewer also checks that each new integration test enters through the same path the live trigger takes, not through a state it writes by hand: on 2026-10-17 milestone 4's test saved its delivery plan directly, so it skipped the cut from a batch that every live plan takes, and it passed while the backup step never ran (issue 1086).
|
||||
5. **The reviewer reads the answers and the new commits**, and approves or reports again (step 7 says what a round
|
||||
may ask). After round 3 the lead stops and brings the operator what is left, with a recommendation.
|
||||
6. **The reviewer reads every acceptance criterion against its test**: each criterion has a test at its level
|
||||
@@ -252,11 +264,13 @@ 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 tries named
|
||||
in the pull request. A missing answer, or a key or stream the diff creates that the answer omits, is a change asked.
|
||||
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.
|
||||
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
|
||||
@@ -275,26 +289,47 @@ 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)
|
||||
|
||||
1. **Push the reviewed heads, try again a head the review changed, then open one pull request per repository,
|
||||
together.** **The integration tests run before the pull requests are opened**, first before the review (step 1)
|
||||
and again for a head the review changed since its try: `mesh-controller.try` with the
|
||||
repository and the pushed branch is a check ask of the checker (ADR 0501 §5): the checker runs the repository's tests on the
|
||||
merge with the trunk's head, at the unit and the integration level, through the build module of the repository's language and by
|
||||
that language's convention, as the merge check does, and answers with an id to read back. The try is an ask on the language's check
|
||||
stream (ADR 0525 §1), so it waits behind the asks before it until a build node has a free slot; no script of the repository decides
|
||||
it (ADR 0445, ADR 0529). A try that fails goes to step 3 item 3 as a failed check does; the pull requests are opened only
|
||||
when every head's last try passed (the operator, 2026-10-18: three pushes in a row failed their first merge check on
|
||||
faults a try finds in minutes). While developing, no `go test -tags integration` or store or bus started
|
||||
by hand on the machine (the operator, 2026-10-18); after a failed try, a local run of the whole level is the guideline
|
||||
in step 1's brief. **Before a session trusts an agent's "pushed", it reads the forge branch's head** (`git ls-remote origin refs/heads/<branch>`) and compares it with the commit reviewed; an agent may commit on a detached HEAD and report a push that never happened (2026-10-26: the first try of a reviewed fix failed "not our ref"). The try and the pull request are started only on that read head. Pull requests in two or
|
||||
1. **Push the reviewed heads once, then open one pull request per repository, together** (ADR 0548; the operator,
|
||||
2026-10-26: "every release should only run 1 check or 1 try"). **No pull request, draft or not, exists before the
|
||||
review approves the heads**, and no fix round is pushed to an open pull request: every push to an open pull request
|
||||
asks a new check, of every member for a group, and the mesh's build capacity is spent on it ("we don't have more
|
||||
processing power"). The integration tests run once, in the checker's merge check on the merge with the trunk's head, at the unit
|
||||
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
|
||||
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.
|
||||
**Before a session trusts an agent's "pushed", it reads the forge branch's head** (`git ls-remote origin refs/heads/<branch>`) and compares it with the commit reviewed; an agent may commit on a detached HEAD and report a push that never happened (2026-10-26: the first check of a reviewed fix failed "not our ref"). The pull request is opened only on that read head. Pull requests in two or
|
||||
more repositories that share the branch name are one delivery group (ADR 0239 §2).
|
||||
**A temporary mode that merges by hand** (the lead merges because the normal Approve and merge path is not
|
||||
running): before it asks the merge, the lead runs the full integration level with nothing kept, a try with tests
|
||||
`.` for each repository of the group, and names those tries (ids and verdicts) in the pull request's description.
|
||||
A merge asked without them is a step skipped (research 071).
|
||||
running): the pull request's one check still runs the integration level, and the lead names its verdict in the
|
||||
pull request's description. A try with tests `.` is asked only when the operator asks for one, and then named there with its id and verdict (research 071).
|
||||
Where a session pushes through the forge tool `gitea_commit` (one commit on the forge's current base), the
|
||||
reviewer first confirms that commit's tree equals the reviewed one. **A large change is pushed by a pusher agent** on the smaller
|
||||
model: `gitea_commit` patches of about 30 KB each, a file copied from another path sent as a copy (`copy_from`)
|
||||
@@ -315,21 +350,24 @@ 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 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 try 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
|
||||
**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
|
||||
`internal/beside` says.
|
||||
2. **The checker's merge check runs the integration tests again, as the gate** (its one status, `mesh/check`). No
|
||||
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.
|
||||
3. **A check that fails** goes back to the same reviewer, continued by its id, with the failure, for **one more cycle** of step 2 with
|
||||
the same fixing agents; then push once again. **The failure is read from the check's whole log**, never
|
||||
the same fixing agents; then push **once** again (the one more push of ADR 0548). The check is **read, never re-asked to see if it passes**: a
|
||||
check asked again without a new, reviewed head spends the build capacity for nothing. **The failure is read from the check's whole log**, never
|
||||
reasoned from its summary: `mesh-delivery.show` names the check's build id, and `mesh-controller.builds` with
|
||||
`log` set to that id prints everything the builder said. A check's summary picks only some failing lines;
|
||||
on 2026-10-17 it showed a store test's `FAIL` without its cause, and a fix was first guessed from the code.
|
||||
**A failure the check hides or loses** (a verdict naming only the first failed test, a log not kept) is a fault of the
|
||||
checking tool: it is fixed there, the same turn (instruction fix-a-bug-met-twice), not asked around.
|
||||
**A change to the Go build module's recipe is judged by the build module the nodes run**, not by the changed one, so a
|
||||
test the change adds must pass under either version. **A change that needs the new recipe to judge it ships the recipe
|
||||
first, as a small fix of its own**, and the rest waits for its delivery: on 2026-10-17 milestone 3 of design 47 moved its
|
||||
@@ -349,9 +387,9 @@ issue whether it is being fixed in between, after the delivery, or waits, and on
|
||||
hand (issue 1226).
|
||||
5. **The operator's Approve is asked as soon as the checks pass** (ADR 0421). Nobody marks, labels, comments or
|
||||
rechecks: mesh-delivery asks the Approve on Telegram, the channel that proves who answered today (ADR 0398 §3;
|
||||
an Approve at the terminal is issue 953), once both checks passed, for a delivery group once for all its
|
||||
an Approve at the terminal is issue 953), once its one check (`mesh/check`) passed, for a delivery group once for all its
|
||||
members, naming the review record. On the Approve it merges the approved heads, a group's in one pass
|
||||
(ADR 0418). A new head ends the Approve and is asked again once its checks pass. A watcher follows the checks,
|
||||
(ADR 0418). A new head ends the Approve and is asked again once its checks pass, which is why a head is pushed only after its review. A watcher follows the checks,
|
||||
the Approve and the merge, and wakes the lead. Beyond that
|
||||
Approve, the operator's word is waited for only when the milestone changes what the operator sees, removes
|
||||
data, or needs a step only the operator's terminal can take; the lead says which before the milestone starts.
|
||||
@@ -365,16 +403,31 @@ 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 catalogue change that uses a new manifest word.** The catalogue's merge check reads
|
||||
**One exception: a catalog change that uses a new manifest word.** The catalog's merge check reads
|
||||
manifests with the controller the mesh runs, so the controller's pull request is merged and delivered first;
|
||||
then `mesh-delivery.recheck` the catalogue's and merge it on the new verdict. Such work is two releases, the controller's first, and playbook 04 cuts its milestones that way.
|
||||
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.
|
||||
**A new key the node-engine checks is such a word too** (issue 1051): the controller judges every machine with the
|
||||
node-engine's own validator, vendored at the commit `go.mod`'s `replace` pins, so a key added to the node-engine is
|
||||
refused by the gate until the controller's pin moves to the delivered node-engine (`go mod vendor`); the feature is
|
||||
then three releases, the node-engine's, the controller's, the catalogue's. **Every release that must merge before
|
||||
the catalogue's is on a branch name of its own** (`feat/<slug>-node-engine`, `feat/<slug>-controller`): on the
|
||||
feature's name it joins the catalogue's delivery group, and a group merges only whole, so neither merges.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -392,7 +445,10 @@ 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; then the lead resolves the feature's issues with `fixed-by` naming its pull
|
||||
`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
|
||||
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
|
||||
@@ -472,7 +528,7 @@ on, with when it started and its bound.
|
||||
| a fixing agent's issue | 30 minutes | ask it where it stands; split or take over |
|
||||
| the review | 15 minutes a round | ask the reviewer; stop at round 3 |
|
||||
| a merge check | 15 minutes | read `mesh-delivery.show` (its state and verdict), then the check's log; the check stream's backlog and the age of its oldest ask are in the delivery view (ADR 0525 §9) |
|
||||
| the Approve's ask | 5 minutes after both checks pass | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
|
||||
| the Approve's ask | 5 minutes after the check (`mesh/check`) passes | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
|
||||
| a merge after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
|
||||
| a delivery | its class's budget (3 minutes for a leaf module, 10 for a core module), at most 30 minutes | read the view's wants page, or `assigner_wants` against `node_haves`: a want held back names its rule, a `stuck` module its words |
|
||||
|
||||
@@ -497,47 +553,43 @@ 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.
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
**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:
|
||||
- **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:
|
||||
|
||||
```
|
||||
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>
|
||||
```
|
||||
|
||||
**The short layout** (on a channel such as Telegram, or when nothing changed but time):
|
||||
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.
|
||||
|
||||
```
|
||||
<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.
|
||||
On a channel such as Telegram the same short report is sent, the tree as a code block.
|
||||
|
||||
## The context of the lead's own session
|
||||
|
||||
@@ -619,8 +671,13 @@ issues in `fixing` that nobody works on reads to the operator as stuck work.
|
||||
- A running milestone's file holds the three answers of step 0 item 8 (`cat .work/<slug>/milestone.md`), and the
|
||||
review report says it read them (step 2, item 6a): a bucket key or stream in the diff (`git diff main --
|
||||
| grep -i 'bucket\|stream'`) that the lifetime answer omits is the failure of that item (research 071).
|
||||
- A pull request merged by hand in a temporary mode names, in its description on the forge (`gitea.gitea_get_pull_request`), a try with
|
||||
tests `.` for each repository of the group; one without is the step 3 rule skipped.
|
||||
- A pull request merged by hand in a temporary mode names, in its description on the forge (`gitea.gitea_get_pull_request`), the
|
||||
verdict of its one check; a try with tests `.` is named only where the operator asked for one.
|
||||
- **One release, one check** (ADR 0548): `gitea.gitea_get_pull_request` shows each pull request created after its
|
||||
review record's time, and `mesh-delivery.show` shows one check per head and, for a group, one head per member; a pull
|
||||
request that existed before its review approved it, a second head with no failed check before it, or a check of a
|
||||
head asked more than once, is this rule skipped. A call of `mesh-controller.try`, `mesh-delivery-checker.try` or `checker_try` that the operator
|
||||
did not ask for shows in the checker's asks and is the same breach.
|
||||
- A milestone that adds or changes a module whose pull request has no integration test that starts the module
|
||||
whole is the failure of the check step's fourth item.
|
||||
- A bed run from `.work/<slug>/mesh-lab` that fails naming a `.work/<slug>/<repo>/…` path it cannot find is a
|
||||
|
||||
@@ -26,14 +26,15 @@ this moment): it goes alone as a small fix, and the lead's report says why.
|
||||
|
||||
## The aim
|
||||
|
||||
A small fix spends its time on the fix, and runs the long part once: **one try before the review, one review, one check run,
|
||||
one delivery**. Everything before the try is local and fast.
|
||||
A small fix spends its time on the fix, and runs the long part once: **one review, one push, one check run,
|
||||
one delivery** (ADR 0548; the operator, 2026-10-26: "every release should only run 1 check or 1 try"; "we don't have more
|
||||
processing power"). Everything before the push is local and fast, and no try is run.
|
||||
|
||||
## 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). If a design question is open, it is settled before any
|
||||
code is written: on the issue, through the knowledge base, or by the operator. A fix that turns out to
|
||||
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
|
||||
criteria** as playbook [07](07-feature-branches.md) step 0 item 5 says: each one sentence, given, when, then,
|
||||
@@ -50,30 +51,33 @@ one delivery**. Everything before the try is local and fast.
|
||||
caught only by the review); read `git show --stat` before pushing.
|
||||
The tests are written from the acceptance criteria, at least one per criterion at its level, each citing the
|
||||
criterion it proves by its label (`<issue>/AC<n>`, playbook 07 step 0 item 5). Commit, naming the issue. For a playbook or design edit, run the repository's own checks (in hq,
|
||||
`go test ./...` at the root). Then push the branch and run `mesh-controller.try` on it, so the review reads the
|
||||
integration tests' real results; a failed try goes back to the same agent before the review.
|
||||
4. **One review of the commit and its try** (a reviewer that did not write it, larger
|
||||
`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.
|
||||
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; a finding outside the issue's done-when and its acceptance
|
||||
criteria is opened as an issue of its own, never asked as a change). The reviewer's findings go back to the same agent that wrote the fix, continued by
|
||||
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
|
||||
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, try it again if the review changed it, and open the pull request.** The trunk is what runs and nothing holds merged work back
|
||||
(ADR 0401), so a fix waits for no other change's delivery. `mesh-controller.try` on the pushed branch runs
|
||||
the integration tests, and the pull request is opened only when the head's last try passed (the pushed head is read from the forge first, as playbook [07](07-feature-branches.md) step 3 item 1 says); the checker's merge check runs
|
||||
them again as the gate (playbook [07](07-feature-branches.md) step 3). A failed try or check is best run locally
|
||||
first: the repository's whole integration level (`go test -tags integration -count=1 ./...` in each module) shows every
|
||||
failure at once, where a verdict names only the first (issue 1921) and a try per fault costs a round each (a guideline,
|
||||
the operator, 2026-10-23). The failures go back to the same agent, fixed in one change where they can be, and the
|
||||
reviewer reads only the change it makes, before the next try.
|
||||
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
|
||||
(ADR 0401), so a fix waits for no other change's delivery. No pull request, draft or not, exists before the review
|
||||
approves the head, and no fix round is pushed to an open pull request: every push to an open pull request asks a new
|
||||
check. The checker's merge check runs the unit and the integration level once, as the gate (playbook [07](07-feature-branches.md) step 3); the pushed head is read from the forge first, as playbook 07 step 3 item 1 says.
|
||||
A failed check is read from its whole log (never re-asked to see if it passes), then the repository's whole integration level
|
||||
is run locally (`go test -tags integration -count=1 ./...` in each module) so every failure shows at once, where a verdict
|
||||
names only the first (issue 1921); the failures go back to the same agent, fixed in one change, the reviewer reads only the change
|
||||
it makes, and the head is pushed once more.
|
||||
**A temporary mode that merges by hand** (the lead merges because the normal Approve and merge path is not
|
||||
running): before it asks the merge, the lead runs the full integration level with nothing kept, `mesh-controller.try`
|
||||
with tests `.`, and names that try (id and verdict) in the pull request's description (research 071; playbook
|
||||
running): the one check still runs, and the lead names its verdict in the pull request's description; a try with tests `.` is asked only when the operator asks for one (research 071; playbook
|
||||
[07](07-feature-branches.md) step 3).
|
||||
6. **The pull request's description carries the review record** (`Reviewed-by: <who@node> at <full sha>`,
|
||||
playbook [07](07-feature-branches.md) step 3) and the session that pushed it (`Pushed-by: <who@node>`, ADR 0505 §5), and mesh-delivery asks the operator's Approve as soon as its
|
||||
checks pass and merges the approved head (ADR 0421); nobody marks, labels, comments or rechecks. A fix to a module that changes how it runs carries an
|
||||
integration test starting the module whole against the real bus and store, run by `mesh-controller.try` and the checker's merge check
|
||||
integration test starting the module whole against the real bus and store, run by the checker's one merge check
|
||||
(ADR 0391 §1, §6); unit tests alone are not that proof, and the lab is not used. The operator's word is waited for only when the fix changes what the operator sees, removes data,
|
||||
or needs the operator's terminal.
|
||||
**A fix that depends on another pull request outside its delivery group opens only once that one is delivered**, not merely merged (members of one group open together, as playbook 07 step 3 item 1 says) (a note
|
||||
@@ -86,13 +90,14 @@ one delivery**. Everything before the try is local and fast.
|
||||
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. Then prove the fix on the mesh (the symptom gone, measured), and resolve the issue with
|
||||
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
|
||||
`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
|
||||
(branch, last commit, what is left), as playbook [07](07-feature-branches.md) says.
|
||||
|
||||
**An issue opened along the way is fixed too** (the operator, 2026-10-21). A fix's review, try or delivery that
|
||||
**An issue opened along the way is fixed too** (the operator, 2026-10-21). A fix's review, check or delivery that
|
||||
opens an issue (a remark outside the done-when, a fault met live) leaves it to the same session to fix: right after
|
||||
this fix's delivery, as a small fix of its own, or folded into the next one in the same repository. It waits only
|
||||
when another running milestone owns it, or it needs a decision or the operator first; the report then says on what.
|
||||
@@ -116,10 +121,9 @@ code.
|
||||
| Waiting on | Bound | Past the bound |
|
||||
|---|---|---|
|
||||
| building the fix | 1 hour | it is a feature: stop and plan it under playbook 07 |
|
||||
| the try | 30 minutes | a try is a check ask on the language's check stream and waits behind the asks before it for a build node's free slot (ADR 0525); past the bound, read the stream's backlog and the age of its oldest ask in the delivery view |
|
||||
| the review | 15 minutes | ask the reviewer where it stands |
|
||||
| the merge check | 15 minutes | read `mesh-delivery.show`, the check's log and the check stream's backlog in the delivery view |
|
||||
| the Approve's ask | 5 minutes after both checks pass | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
|
||||
| the merge check | 30 minutes (it waits behind the asks before it for a build node's free slot, ADR 0525) | read `mesh-delivery.show`, the check's log and the check stream's backlog in the delivery view |
|
||||
| the Approve's ask | 5 minutes after the check (`mesh/check`) passes | read `mesh-delivery.show`: an ask not raised is a fault, said to the operator |
|
||||
| the merge after the Approve | 5 minutes | read `mesh-delivery.show` for the delivery |
|
||||
| the delivery | its class's budget (3 minutes for a leaf module, 10 for a core module), at most 30 minutes | read the view's wants page, or `assigner_wants` against `node_haves`: a want held back names its rule, a `stuck` module its words |
|
||||
|
||||
@@ -134,8 +138,10 @@ reading itself and uses one subagent only for the review.
|
||||
|
||||
- A small fix has one pull request with one or few commits naming its issue, opened after its review: a pull
|
||||
request with many heads is a fix reviewed after its pull request was opened.
|
||||
- A pull request merged by hand in a temporary mode names a try with tests `.` at its head in its description
|
||||
- A pull request merged by hand in a temporary mode names the verdict of its one check in its description
|
||||
on the forge (`gitea.gitea_get_pull_request`); one without is the step skipped.
|
||||
- One fix, one check (ADR 0548): a pull request that existed before its review approved the head, or a head whose check was
|
||||
asked more than once with no new reviewed head, is steps 4 and 5 skipped; so is a `mesh-controller.try` or `mesh-delivery-checker.try` the operator did not ask for.
|
||||
- Its issue moves to `resolved` within the hour the view says its source revision `delivered`.
|
||||
- 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
|
||||
|
||||
@@ -75,7 +75,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<main>
|
||||
<button class="chip theme" id="theme" type="button">Theme</button>
|
||||
<h1>Novox Playbooks</h1>
|
||||
<p class="sub">How work is done on the mesh, from an idea or a symptom to a delivered change. Drawn from hq's <code>00-META/process/</code> at main <code>f269e83</code>, 2026-10-19, including playbook change hq #433 (try before the review).</p>
|
||||
<p class="sub">How work is done on the mesh, from an idea or a symptom to a delivered change. Drawn from hq's <code>00-META/process/</code> at main <code>f269e83</code>, 2026-10-19, amended 2026-10-26 for ADR 0548 (one release, one check: no try).</p>
|
||||
|
||||
<nav>
|
||||
<a href="#cycle">The cycle</a>
|
||||
@@ -189,7 +189,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
|
||||
<!-- ============ FEATURE SWIMLANE ============ -->
|
||||
<h2 id="feature">A feature, step by step (playbook 07)</h2>
|
||||
<p class="lead">One milestone is one release: one branch name in every repository it touches, one worktree per repository, one fixing agent per repository kept for the whole milestone, one reviewer, and one pull request per repository, opened once. Since hq #433 the branches are <b>tried before the review</b>, so the reviewer reads real integration results.</p>
|
||||
<p class="lead">One milestone is one release: one branch name in every repository it touches, one worktree per repository, one fixing agent per repository kept for the whole milestone, one reviewer, and one pull request per repository, opened once. Nothing is pushed or tried before the review (ADR 0548): the branches are pushed <b>once, after the review approves</b>, and the one check runs the integration tests.</p>
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 1080 430" role="img" aria-label="Playbook 07 in lanes: lead, fixing agents, reviewer, the mesh, the operator">
|
||||
<defs>
|
||||
@@ -199,12 +199,12 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<g class="mute">
|
||||
<text x="190" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">0 · Prepare</text>
|
||||
<text x="190" y="34" text-anchor="middle" class="mute">15 min</text>
|
||||
<text x="410" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">1 · Develop + try</text>
|
||||
<text x="410" y="34" text-anchor="middle" class="mute">30 min per issue · one try</text>
|
||||
<text x="410" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">1 · Develop</text>
|
||||
<text x="410" y="34" text-anchor="middle" class="mute">30 min per issue · local only</text>
|
||||
<text x="630" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">2 · Review</text>
|
||||
<text x="630" y="34" text-anchor="middle" class="mute">≤ 3 rounds · 15 min</text>
|
||||
<text x="830" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">3 · Check</text>
|
||||
<text x="830" y="34" text-anchor="middle" class="mute">≤ 1 more try · 1 check</text>
|
||||
<text x="830" y="34" text-anchor="middle" class="mute">push once · 1 check</text>
|
||||
<text x="1000" y="20" text-anchor="middle" class="t" style="fill:var(--mute)">4 · Deliver</text>
|
||||
<text x="1000" y="34" text-anchor="middle" class="mute">until delivered</text>
|
||||
</g>
|
||||
@@ -216,7 +216,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<text x="10" y="86" class="t">Lead</text><text x="10" y="102" class="mute">larger model</text>
|
||||
<text x="10" y="158" class="t">Fixing agents</text><text x="10" y="174" class="mute">smaller model</text>
|
||||
<text x="10" y="230" class="t">Reviewer</text><text x="10" y="246" class="mute">larger model</text>
|
||||
<text x="10" y="302" class="t">The mesh</text><text x="10" y="318" class="mute">try, checks, wants</text>
|
||||
<text x="10" y="302" class="t">The mesh</text><text x="10" y="318" class="mute">the one check, wants</text>
|
||||
<text x="10" y="374" class="t">Operator</text><text x="10" y="390" class="mute">Telegram</text>
|
||||
</g>
|
||||
<!-- lead -->
|
||||
@@ -224,11 +224,11 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<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 · push</text>
|
||||
<text x="410" y="96" text-anchor="middle" class="mute">each branch</text>
|
||||
<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>
|
||||
<rect x="760" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="830" y="78" text-anchor="middle" class="t">Push reviewed heads</text>
|
||||
<text x="830" y="96" text-anchor="middle" class="mute">open the PRs together</text>
|
||||
<text x="830" y="96" text-anchor="middle" class="mute">once, PRs together</text>
|
||||
<rect x="930" y="56" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--dec)" stroke-width="2"/>
|
||||
<text x="1000" y="78" text-anchor="middle" class="t">Follow to done</text>
|
||||
<text x="1000" y="96" text-anchor="middle" class="mute">resolve the issues</text>
|
||||
@@ -242,14 +242,14 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<!-- reviewer -->
|
||||
<rect x="560" y="200" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
|
||||
<text x="630" y="222" text-anchor="middle" class="t">Read all repos</text>
|
||||
<text x="630" y="240" text-anchor="middle" class="mute">+ tries · every criterion</text>
|
||||
<text x="630" y="240" text-anchor="middle" class="mute">reads tests · every criterion</text>
|
||||
<!-- mesh -->
|
||||
<rect x="340" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
|
||||
<text x="410" y="294" text-anchor="middle" class="t">try</text>
|
||||
<text x="410" y="312" text-anchor="middle" class="mute">integration tests run</text>
|
||||
<text x="410" y="294" text-anchor="middle" class="t">no try</text>
|
||||
<text x="410" y="312" text-anchor="middle" class="mute">nothing pushed yet</text>
|
||||
<rect x="760" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
|
||||
<text x="830" y="294" text-anchor="middle" class="t">Forge checks</text>
|
||||
<text x="830" y="312" text-anchor="middle" class="mute">gate · repo · group</text>
|
||||
<text x="830" y="312" text-anchor="middle" class="mute">one check · mesh/check</text>
|
||||
<rect x="930" y="272" width="140" height="52" rx="8" fill="var(--card)" stroke="var(--mesh)" stroke-width="2"/>
|
||||
<text x="1000" y="294" text-anchor="middle" class="t">Merge · wants</text>
|
||||
<text x="1000" y="312" text-anchor="middle" class="mute">backups first, canary first</text>
|
||||
@@ -260,10 +260,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<!-- edges -->
|
||||
<path class="edge" d="M190,108 C190,140 260,154 298,154" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M410,128 V110" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M480,90 C545,110 545,250 462,270" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M480,298 C530,298 540,226 558,226" marker-end="url(#ar2)"/>
|
||||
<text x="490" y="262" class="mute">results</text>
|
||||
<path class="edge" d="M630,200 V182" marker-end="url(#ar2)"/>
|
||||
<path class="edge" d="M630,200 V182" marker-end="url(#ar2)"/>
|
||||
<path class="edge dash" d="M610,182 V198" marker-end="url(#ar2)"/>
|
||||
<text x="514" y="196" class="mute">round n+1</text>
|
||||
<path class="edge" d="M700,226 C740,226 740,82 758,82" marker-end="url(#ar2)"/>
|
||||
@@ -302,7 +299,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
|
||||
<!-- ============ TESTS ============ -->
|
||||
<h2 id="tests">When tests run</h2>
|
||||
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) never run there: <code>mesh-controller.try</code> runs them on the pushed branch (a check ask on the language's stream), and the checker's merge check runs them again as the gate.</p>
|
||||
<p class="lead">Two levels. Unit tests run all the time on the machine; integration tests (a bus, a store or a process started) are compiled there and never run: the checker's one merge check, asked by the one push after the review, runs them as the gate. There is no try (ADR 0548).</p>
|
||||
<div class="box scroll">
|
||||
<svg viewBox="0 0 1080 170" role="img" aria-label="Test levels over a feature's timeline">
|
||||
<defs><marker id="ar3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
|
||||
@@ -318,8 +315,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<g>
|
||||
<rect x="120" y="96" width="340" height="16" rx="8" fill="var(--mute)" opacity=".18"/>
|
||||
<text x="290" y="108" text-anchor="middle" class="mute">compiled only: go vet -tags integration</text>
|
||||
<rect x="470" y="90" width="110" height="28" rx="6" fill="var(--mesh)"/><text x="525" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">try</text>
|
||||
<rect x="770" y="90" width="120" height="28" rx="6" fill="none" stroke="var(--mesh)" stroke-width="2" stroke-dasharray="5 4"/><text x="830" y="109" text-anchor="middle" class="mute">try again if changed</text>
|
||||
<rect x="770" y="90" width="120" height="28" rx="6" fill="none" stroke="var(--mesh)" stroke-width="2" stroke-dasharray="5 4"/><text x="830" y="109" text-anchor="middle" class="mute">push once</text>
|
||||
<rect x="920" y="90" width="120" height="28" rx="6" fill="var(--mesh)"/><text x="980" y="109" text-anchor="middle" style="fill:var(--bg)" class="t">merge check</text>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -332,7 +328,7 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<svg viewBox="0 0 520 300" role="img" aria-label="The review loop: at most three rounds">
|
||||
<defs><marker id="ar4" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="var(--mute)"/></marker></defs>
|
||||
<rect x="20" y="20" width="150" height="54" rx="8" fill="var(--card)" stroke="var(--idea)" stroke-width="2"/>
|
||||
<text x="95" y="44" text-anchor="middle" class="t">Round n of ≤ 3</text><text x="95" y="62" text-anchor="middle" class="mute">code + tries + criteria</text>
|
||||
<text x="95" y="44" text-anchor="middle" class="t">Round n of ≤ 3</text><text x="95" y="62" text-anchor="middle" class="mute">code + criteria</text>
|
||||
<polygon points="300,20 380,47 300,74 220,47" fill="var(--card)" stroke="var(--mute)"/>
|
||||
<text x="300" y="51" text-anchor="middle" class="t">Wrong?</text>
|
||||
<rect x="400" y="20" width="110" height="54" rx="8" fill="var(--okbg)" stroke="var(--ok)" stroke-width="2"/>
|
||||
@@ -357,10 +353,12 @@ code{font:12.5px ui-monospace,monospace;background:var(--soft);padding:1px 5px;b
|
||||
<h3 style="margin-top:12px">What every round reads</h3>
|
||||
<ul style="margin:6px 0;padding-left:20px;font-size:14px">
|
||||
<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, and its try's result.</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. hq #433 changes all three.</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -465,17 +463,17 @@ 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.","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, how the integration level runs before the pull requests. 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, push, and try it. (new)","Review all of it with the tries' results; at most 3 rounds.","Push the reviewed heads and read each forge head against the reviewed commit; try again only what changed.","A merge by hand in a temporary mode first runs a try with tests \".\" and names it in the pull request. (new)","Open one pull request per repository, together, with Reviewed-by and Pushed-by.","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; 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)"],
|
||||
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 try and one check",
|
||||
prev:["Pull requests as scratchpads","Context bought again and again","Review rounds spent on what a run would show"],
|
||||
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","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.","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)","Push and try before the review. (new)","One review of the commit and its try.","Push the reviewed head; open the pull request.","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 try before the review, one review, one check",
|
||||
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"],
|
||||
check:"The checker's status; the review record; the tracker's move; one delivery group and one source revision per milestone."},
|
||||
{n:"09",name:"Cleanup",kind:"side",c:"--side",
|
||||
|
||||
+2
-2
@@ -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 catalogue, the delivery machinery, and the bootstrap scripts. Every core module lives here. |
|
||||
| `hal` | The monorepo — the node runtime, the module catalog, 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 catalogue | One directory per module, each with a manifest. Core modules sit under the mesh's own namespace; everything else at the top level. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
|
||||
@@ -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 catalogue
|
||||
A set of machines, each running the same runtime, each loading only the parts of the catalog
|
||||
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 catalogue's modules do.**
|
||||
as a dependency. **Zero of the catalog'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 catalogue is wrong has not been decided.
|
||||
or the catalog 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 catalogue. A capability is written once and is
|
||||
The two are the same code with the same catalog. 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 catalogue's definition into it, generate the environment, run any
|
||||
reconcile the link from the catalog'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 catalogue, and starts the mesh. This resolves the
|
||||
mesh reads from, publishes the catalog, 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.
|
||||
|
||||
@@ -9,9 +9,9 @@ decisions:
|
||||
- "ADR 0009"
|
||||
---
|
||||
|
||||
# The catalogue, and what its shape says
|
||||
# The catalog, and what its shape says
|
||||
|
||||
The catalogue holds **124 modules**. Thirty-three belong to the mesh's own domain; the other
|
||||
The catalog 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 catalogue itself does not express:
|
||||
Grouped by what they are *for* — a grouping the catalog itself does not express:
|
||||
|
||||
| Purpose | Roughly |
|
||||
|---|---|
|
||||
@@ -51,7 +51,7 @@ connectivity is made four times.
|
||||
|
||||
## What the shape records
|
||||
|
||||
**The catalogue's shape records what was installed, not what anything is for.** One module is
|
||||
**The catalog'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 catalogue's shape is not arbitrary and it is not a series of mistakes. **This repository
|
||||
The catalog'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 catalogue**, and it is why the
|
||||
**This is the most useful single fact for anyone changing the catalog**, 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 catalogue's flat level is not a dumping ground by policy; it is one by
|
||||
it in the monorepo. The catalog's flat level is not a dumping ground by policy; it is one by
|
||||
history.
|
||||
|
||||
## Known inconsistencies in the catalogue itself
|
||||
## Known inconsistencies in the catalog itself
|
||||
|
||||
Recorded because a reader will meet them:
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ decisions:
|
||||
|
||||
# The seats, as they run
|
||||
|
||||
Written from the controller's code and the catalogue's manifests on their main branches. This is
|
||||
Written from the controller's code and the catalog'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 catalogue seats, and the
|
||||
one ecosystem and the git service. The remaining nine — the controller and catalog 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.
|
||||
|
||||
@@ -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
|
||||
catalogue (`mesh-catalog`) carries no module named `mesh-console` any more — the name survives only
|
||||
catalog (`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 catalogue exposes.
|
||||
for it: removing it from a node is not an operation the controller or the catalog exposes.
|
||||
|
||||
## What this repository's prior text got wrong
|
||||
|
||||
The document this replaces described `mesh-console` as a catalogue module served by a container and
|
||||
The document this replaces described `mesh-console` as a catalog 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
|
||||
|
||||
@@ -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
|
||||
catalogue — on every start and, since the first module that declared state found it missing, on every
|
||||
catalog — 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 catalogue and broker tests (names, grants, memberships, a bucket asserted in place
|
||||
The controller's catalog 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
|
||||
catalogue's code on main (both local clones were a few commits behind the forge, so each claim below was
|
||||
catalog'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 catalogue. Their unit tests hold the node record's refusals and the guard's account rule.
|
||||
guard in the catalog. Their unit tests hold the node record's refusals and the guard's account rule.
|
||||
|
||||
@@ -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 catalogue's shape, and what its shape says |
|
||||
| [`10-module-catalogue.md`](10-module-catalogue.md) | The catalog'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 |
|
||||
|
||||
@@ -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 catalogue it proves
|
||||
## A bed reads the catalog 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 catalogue
|
||||
when each bed was written; the copies did not move when the catalogue did, and a catalogue change
|
||||
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
|
||||
was proven by no bed at all, while every bed stayed green against its copy.
|
||||
|
||||
**A bed that installs a catalogue module reads that module's manifest from the catalogue the run
|
||||
**A bed that installs a catalog module reads that module's manifest from the catalog 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 catalogue and installs what the module requires; a name of its own is for a
|
||||
so it reads the catalog and installs what the module requires; a name of its own is for a
|
||||
fixture that runs no real runtime.
|
||||
|
||||
**The receipt names the catalogue's commit** with the others', so a run taken before a manifest
|
||||
**The receipt names the catalog'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
|
||||
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
|
||||
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
|
||||
the run is pointed at one.
|
||||
|
||||
## Consequences
|
||||
|
||||
@@ -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 catalogued before.
|
||||
The second is the more dangerous shape and it is a variant the mesh has not cataloged 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.
|
||||
|
||||
|
||||
@@ -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 catalogue is checked out beside it.
|
||||
holds the installer's constants to the module's manifest where the catalog is checked out beside it.
|
||||
|
||||
**What filters the machine, and the found firewall kept retired** — revision, 2026-10-02
|
||||
(ADR 0168). The node-engine
|
||||
|
||||
@@ -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 catalogue, the builder, the vault — are
|
||||
The mesh's own components — the node-engine, the controller, the catalog, 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.
|
||||
|
||||
@@ -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 catalogue's managers' modules
|
||||
(NetworkManager, systemd-networkd; dhcpcd's left the catalogue with
|
||||
whose program would otherwise overwrite it. Each of the catalog's managers' modules
|
||||
(NetworkManager, systemd-networkd; dhcpcd's left the catalog 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 catalogue writes the path, a resolution test refusing a second writer, and the
|
||||
nothing else in the catalog 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 catalogue at a
|
||||
in full — the forge as `git.example.tld`, spelt out in the module. Pointing the same catalog 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 catalogue and asserts the routed name is served.
|
||||
from the catalog 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
|
||||
|
||||
@@ -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 catalogue
|
||||
made a repository hold exactly one module. Nothing that exists is shaped that way: the catalog
|
||||
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 catalogue the builder finds no manifest, pointed at a module's source
|
||||
unbuildable — pointed at the catalog 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 catalogue checkout beside it and
|
||||
*How it is checked:* the controller's test runs the check over the catalog 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 catalogue entry, and a machine that ends up running what
|
||||
*Checked end to end: a commit, a build, a catalog 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 catalogue
|
||||
component. It is one rule; the instances are listed below. Revised the same day, when the catalog
|
||||
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 catalogue | It owns the module graph, and nothing can be resolved or installed without it. | 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) |
|
||||
|
||||
**How they arrive is settled and not yet built.** The installer carries an init builder, which
|
||||
clones the source and builds the controller, the catalogue and the builder before a mesh exists
|
||||
clones the source and builds the controller, the catalog 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 catalogue owns the module graph
|
||||
**There is a fourth, and the list was closed too early.** The catalog 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
|
||||
catalogue had an owner and is corrected here rather than left to be reasoned from.
|
||||
catalog 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, catalogue and builder —
|
||||
*init builder* and builds the core modules on the machine — controller, catalog 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 catalogue engine refuses a
|
||||
secrets are exposed that way. **Checked** at composition: the catalog 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.
|
||||
|
||||
|
||||
@@ -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 catalogue, resolved it and
|
||||
refused one correctly — but the controller accepted it into the catalog, 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.
|
||||
|
||||
@@ -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 catalogue is waiting to be produced, because a manifest
|
||||
anything** — and almost every module in the catalog 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 catalogue's `postgres` module declares, and every step
|
||||
directory and with the superuser password file that the catalog'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 catalogue.** Every module with source of its own is built the same way. Until this
|
||||
**And then the catalog.** 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 catalogue being built are a thing somebody now types, rather than a
|
||||
paragraphs above describing the catalog 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 catalogue; that is
|
||||
builder's manifest belongs in the same repository and is still read from the catalog; 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 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 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 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. |
|
||||
|
||||
@@ -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 catalogue's (ADR 0072), *where an artifact runs* is the
|
||||
the catalog'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 catalogue: **most modules are not converted**, and the
|
||||
The evidence that this is too hard is in the catalog: **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 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. |
|
||||
| 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. |
|
||||
| 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 catalogue beside the checkout.
|
||||
remedies, and a test parses every manifest in the catalog 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 catalogue 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 catalog 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 catalogue module at 09:35 UTC; the one-holder `builder` is
|
||||
build taken by a workstation's agent was a catalog 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 catalogue
|
||||
the daemon following the role's event, so a build nobody waited for still reaches the catalog
|
||||
([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 catalogue.
|
||||
The toolchain list was typescript and python, and only typescript had a base module in the catalog.
|
||||
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.
|
||||
|
||||
@@ -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 catalogue holds `verdaccio`, and a git host usually serves package registries too.
|
||||
not: the catalog 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
|
||||
catalogue be missing from a test for weeks without anything complaining.
|
||||
catalog 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 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)) |
|
||||
| 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)) |
|
||||
| 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 |
|
||||
|
||||
@@ -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 catalogue's database — so it belongs before more is built on it.
|
||||
provisioning — the catalog'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 catalogue, a `postgres-database` consumer, gets its database from it. Follow-up: the store
|
||||
the catalog, 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.)*
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 catalogue'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 catalog'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 catalogue.*
|
||||
branches in the controller and the media catalog.*
|
||||
|
||||
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
|
||||
|
||||
@@ -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 catalogue 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 catalog 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 catalogue 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 catalog 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 catalogue module claiming the seat `mesh-broker`
|
||||
`nats` is a catalog 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 catalogue (`mesh.tools.mesh-catalog.catalog_tools`), and
|
||||
over TLS, lists tools by asking the catalog (`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 catalogue module (runs on an enrolled workstation node) or a
|
||||
- Whether the person's client is a catalog 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.
|
||||
|
||||
@@ -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 catalogue. A provision a module merely offers may have several
|
||||
bus, the vault, the artifact store, the catalog. 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 catalogue rather than maintained by hand, which is the property 0110 actually
|
||||
computed from the catalog 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 catalogue. What this document keeps
|
||||
as rows, and every registered module's, which is computed from the catalog. 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 catalogue |
|
||||
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalog |
|
||||
| `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 catalogue. **This table and
|
||||
they are read from the catalog. **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 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 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 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 catalogue 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 catalog 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 catalogue. Today contracts are implicit in each provider's served fields; the first
|
||||
defined in the catalog. 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 catalogues with the controller's rule and comparing
|
||||
moved no data, proven by resolving both catalogs 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 catalogues resolved and compared —
|
||||
`${dir:<id>}/<rest>` and moves with it, and the same proof — both catalogs 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 catalogue check before merge and left out of the provider's grants,
|
||||
and an overflow is refused by the catalog 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 catalogue
|
||||
store's provisioner changing its own superuser), or *reads it at start*. Every module in the catalog
|
||||
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 catalogue test lists
|
||||
Each is retired only once nothing uses it. Until then both are accepted, and a catalog 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 catalogue definition is rewritten, adopted
|
||||
3. **Definitions move, and seats move to assignments.** Every catalog 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 catalogue tests of ADR 0112. |
|
||||
| A definition names no host path, node or mesh | The catalog 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 catalogue test, from phase 3: no definition uses the seat placeholder. |
|
||||
| Only the controller reads the seat placeholder | A catalog 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 catalogue test listing definitions still using one. It must be empty before a form is removed. |
|
||||
| The old forms retire | The catalog test listing definitions still using one. It must be empty before a form is removed. |
|
||||
|
||||
## Not settled here
|
||||
|
||||
|
||||
@@ -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 catalogue, which would compose a user with no authority and fail
|
||||
assigned but absent from the catalog, 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 catalogue, not
|
||||
partial file is worth writing. A seat's protocol is gathered across the whole catalog, 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 catalogue test asserting all 72 manifests hold no subject — because the
|
||||
module), and as a catalog 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 catalogue stopped using one.
|
||||
repository and may be registered long after the catalog 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 catalogue 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 catalog 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 catalogue would otherwise place something that was never made.
|
||||
the catalog 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 **catalogue** has rather than what this
|
||||
Three decisions in it worth keeping. It lists what the **catalog** 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 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
|
||||
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
|
||||
([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)). A
|
||||
whole mechanism followed: the catalogue asks, the controller re-publishes.
|
||||
whole mechanism followed: the catalog 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 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 |
|
||||
| 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 |
|
||||
| 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-catalogue check asserts none does. Not
|
||||
a manifest that provides or requires `amqp`, and a whole-catalog 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 catalogue deployed before the rename and one after both heard it.
|
||||
existed only so a catalog 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` catalogue module itself exists on a branch of the
|
||||
**Written but not shipped:** the `ssh-client` catalog 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 catalogue's webhook does it automatically — see below).
|
||||
trigger, so this is manual; the catalog'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 + catalogue together — they are one change).
|
||||
4. Land the code (controller + catalog 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 catalogue because its repository has a Gitea webhook pointing at
|
||||
Build-on-push works for the catalog 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 catalogue) against the build-context
|
||||
repository (and changed paths, for a monorepo like the catalog) 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 catalogue package the controller does, so **it validates a
|
||||
The builder embeds the same catalog 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
|
||||
catalogue — `depends-on` edges of four kinds: stands-on, packages, built-by, declared. A merge takes
|
||||
catalog — `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
|
||||
|
||||
@@ -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 catalogue, and the mesh would have hundreds of copies of a decision it made once.
|
||||
the catalog, 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 catalogue, as a test:
|
||||
old bus's form is refused with the name to write instead. Across the whole catalog, 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 catalogue is still correct. That is the property
|
||||
and every manifest in the catalog 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 catalogue places it in the graph. That is the fan-out a shared exchange gave for free, written
|
||||
it, the catalog 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 catalogue, mesh-wide, existing from registration so a reader can
|
||||
the controller creates from the catalog, 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 catalogue which modules
|
||||
**The change becomes work.** The controller consumes `pushed`, asks the catalog 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 catalogue subscribes to `built` (ADR 0134).
|
||||
the catalog 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 catalogue, the telegram sender. This class is not mitigated; it
|
||||
same is true of the builder, the catalog, 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 catalogue test: no manifest contains a string matching the
|
||||
- **A manifest holds no subject.** A catalog 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 catalogue today.
|
||||
which is the drift a hand-written step invites, three times over in the catalog 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 catalogue module. §3 makes every promised verb a condition of
|
||||
case for every mesh seat whose holder is a catalog 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 catalogue's merge check runs over every
|
||||
at registration, at handover, and in `module check`, which the catalog'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
|
||||
|
||||
@@ -28,7 +28,7 @@ it put it there (ADR 0152).
|
||||
|
||||
## 1. What it is
|
||||
|
||||
A module, `mesh-console`, in the catalogue. Its image is the tool runner's own — the client that
|
||||
A module, `mesh-console`, in the catalog. 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 catalogue says together:
|
||||
Its manifest says three things nothing else in the catalog 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 catalogue which modules the mesh holds, then asks each.** `catalog_modules`
|
||||
**The mesh MCP server asks the catalog 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 catalogue
|
||||
verb asks the mesh when it is called, so nothing is kept for a session's length; the flat catalog
|
||||
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 catalogue by hand recorded its source as a URL rather
|
||||
on the live mesh; and a module registered from the catalog 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
|
||||
catalogue, never on disk; a store's database is never written by hand; main is never pushed; the mesh
|
||||
catalog, 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 catalogue; this
|
||||
**Order:** the manager assigned and a refresh observed; the mesh MCP server's provision in the catalog; 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 catalogue serves
|
||||
**Every configurable thing on a node is a module, the home included, and the same catalog 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 catalogue renames the mesh MCP server. Proven when the packet-filter verbs answer
|
||||
runtime per node; the catalog 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 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 |
|
||||
| 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 |
|
||||
|
||||
**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 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
|
||||
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
|
||||
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 catalogue's pipeline to make a point
|
||||
gate refusing every rebuild in the meantime would stop the catalog'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
|
||||
catalogue, `mesh-console` is retired as a module and `node-tools` assigned where it was. The
|
||||
catalog, `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 catalogue. Three things the step found are issues
|
||||
retired from the catalog. 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 catalogue held, thirty-one remain, and all but these two carried their
|
||||
thirty-three tool containers the catalog 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 catalogue check
|
||||
**What changes**, in order: the manifest's tools artifact gains `env` and the catalog 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 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 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 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 catalogue's checks pass.
|
||||
runtime's port; the controller's tests and the catalog's checks pass.
|
||||
|
||||
## WP2 — The agent module
|
||||
|
||||
@@ -130,7 +130,7 @@ runtime's port; the controller's tests and the catalogue'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 catalogue's checks pass.
|
||||
contains a token. The catalog'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 catalogue;
|
||||
account refused and notified; `anthropic-manager` and `anthropic-consumer` retired from the catalog;
|
||||
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 catalogue
|
||||
**Proof.** `claude_code_status` answers on every node; the refusal's notification arrived; the catalog
|
||||
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 catalogue's checks pass.
|
||||
§2, §3 and §4, each failing on the commit before. The catalog'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 catalogue check and at
|
||||
module that does not claim the matching seat is refused, both at the catalog 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 catalogue check over the whole catalogue passes.
|
||||
The catalog check over the whole catalog passes.
|
||||
|
||||
## WP3 — The modules
|
||||
|
||||
@@ -188,7 +188,7 @@ The catalogue check over the whole catalogue 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 catalogue check over the whole catalogue passes. The modules' tests pass.
|
||||
**Proof.** The controller's catalog check over the whole catalog 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 catalogue module's first build is asked
|
||||
3. Merge WP3 and WP4, and build the new modules by hand: a new catalog 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 catalogue. Either order composes. With the catalogue
|
||||
**Merge order.** The controller first, then the catalog. Either order composes. With the catalog
|
||||
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 catalogue; the controller holds the seat's definition beside the other node seats. It has a verb for each file action: list,
|
||||
in the catalog; 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 catalogue, with its tools and their tests, and checked by the controller's catalogue
|
||||
1. Written in the catalog, with its tools and their tests, and checked by the controller's catalog
|
||||
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 catalogue's `checks/paths` refuses a module that names another
|
||||
bar's own, reading the kernel. The catalog'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 catalogue comes last. Left open: the
|
||||
next, because it parses `data`, `if-capability` and `renders`. The catalog 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 catalogue check, at merge. On the proving machine, each tool answered
|
||||
Each module's own tests and the catalog 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 catalogue check (`module check`, and the catalogue-wide test beside it) refuses a provider that
|
||||
The catalog check (`module check`, and the catalog-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 catalogue checked out beside it, never by `module check`. The weekly
|
||||
was held only by a controller test over the catalog 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 catalogue's
|
||||
`operator-channel` seat's holder and channels, the watcher, the providers' retirement, the catalog'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 catalogue commit, and the versions the mesh runs of
|
||||
non-secret values, seats and their holders, the catalog 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 catalogue change is
|
||||
The verdict is the head commit's status `mesh/merge-gate`, an error never a pass. A catalog 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 catalogue and the photo application, not yet merged. **Not yet:** the TypeScript
|
||||
sizes), the media catalog 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 catalogue'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 catalog'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 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 |
|
||||
| **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 |
|
||||
| **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,8 +2,13 @@
|
||||
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-26
|
||||
updated: 2026-10-28
|
||||
decisions:
|
||||
- "ADR 0567"
|
||||
- "ADR 0565"
|
||||
- "ADR 0564"
|
||||
- "ADR 0559"
|
||||
- "ADR 0555"
|
||||
- "ADR 0551"
|
||||
- "ADR 0550"
|
||||
- "ADR 0549"
|
||||
@@ -23,6 +28,8 @@ decisions:
|
||||
- "ADR 0493"
|
||||
- "ADR 0491"
|
||||
- "ADR 0488"
|
||||
- "ADR 0506"
|
||||
- "ADR 0497"
|
||||
- "ADR 0487"
|
||||
- "ADR 0486"
|
||||
- "ADR 0485"
|
||||
@@ -125,10 +132,8 @@ 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. 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).
|
||||
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).
|
||||
- **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
|
||||
@@ -157,14 +162,16 @@ 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 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).
|
||||
(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.
|
||||
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).
|
||||
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
|
||||
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
|
||||
mesh-config, its secrets and bus credential asked of the vault sealed to the node's key, its licences and what
|
||||
@@ -210,8 +217,9 @@ there is three milestones, one issuer of each key at every moment (ADR 0547 §11
|
||||
have records on the bus, and the second milestone waits for it (ADR 0547 §8, §11).
|
||||
2. **Nodes compose** (issue 1126): each node agent composes its own modules; no node applies a composition made
|
||||
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** (issues 1126, 774): the vault mints bus accounts, the grants part
|
||||
writes grants, the try is the checker's own verb, placement is read from the catalogue alone (ADR 0549 §5,
|
||||
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), 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).
|
||||
|
||||
@@ -234,7 +242,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, placement
|
||||
gate: the merge, the source revision ◄┤ ├── indexer: fingerprints, manifests
|
||||
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
|
||||
@@ -251,7 +259,7 @@ The target (ADR 0547):
|
||||
- **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. 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 operator's answers of 2026-10-26 and 2026-10-28: placement lives on the bus, held by the seat `mesh-placement` (ADR 0567), 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
|
||||
@@ -393,7 +401,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 catalogue's check accepts an SDK
|
||||
after the group merges because the forge keeps every pull request's head. The catalog'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
|
||||
@@ -496,7 +504,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 catalogue is read beside nearly every check. A merge
|
||||
new event. Siblings a check reads beside are not held: the catalog 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
|
||||
@@ -662,7 +670,7 @@ backup holder backs up nothing, and the have says so.
|
||||
|
||||
## Compositions, wants and the apply
|
||||
|
||||
*ADR 0360 §8, §9, ADR 0377, ADR 0429 §4, §6, ADR 0539 §9, §14 to §16, ADR 0547 §2 to §9.*
|
||||
*ADR 0360 §8, §9, ADR 0377, ADR 0429 §4, §6, ADR 0539 §9, §14 to §16, ADR 0547 §2 to §9, ADR 0549, ADR 0555.*
|
||||
|
||||
- **Each node composes its own.** For each want `want.<node>.<module>` the node agent composes that module for
|
||||
its own node with mesh-sdk's composition from the manifest by digest the indexer wrote, the module's resolved
|
||||
@@ -671,6 +679,121 @@ backup holder backs up nothing, and the have says so.
|
||||
own composition (adoption, firewall openings, accounts, the resolver, identities and groups) from the same
|
||||
inputs. Of other nodes it reads only their public records and the manifests wanted on them, which its firewall
|
||||
and resolver need. Nothing composed crosses the bus, so nothing composed is signed (ADR 0547 §2).
|
||||
- **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);
|
||||
- **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;
|
||||
- **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 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
|
||||
adopted with the modules taken on it, the tunnel found on it at adoption (no longer there to observe once taken
|
||||
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
|
||||
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;
|
||||
- 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
|
||||
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
|
||||
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`.
|
||||
- **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.
|
||||
- **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 `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>.>`.
|
||||
Since the take-over the grants part's list is the one the bus loads; after a `hand-back` the controller's list
|
||||
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:
|
||||
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
|
||||
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
|
||||
follows it apart. A secret or credential the vault does not hold is such a gap: the vault names it missing, and
|
||||
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).
|
||||
- **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).
|
||||
@@ -696,7 +819,10 @@ backup holder backs up nothing, and the have says so.
|
||||
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).
|
||||
(ADR 0547 §2). A want to a node the catalog 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).
|
||||
- **The apply, one module at a time.** At its first start the node agent takes the declaration it kept last as
|
||||
what each module runs and writes each have. Then it applies a module only when its want's fingerprint
|
||||
differs from what runs or the composition it composed differs from the one it runs, and only that module,
|
||||
@@ -777,9 +903,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 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
|
||||
(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
|
||||
5's last plan (ADR 0539 §12).
|
||||
|
||||
| Link | Names | Written by | Bucket |
|
||||
@@ -787,8 +913,10 @@ assignments bucket is kept equal to it until ADR 0547's third milestone and is a
|
||||
| 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, from the catalogue's placement file of that node at each trunk merge of the catalogue | the indexer | the indexer's state `placement` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| 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
|
||||
@@ -804,9 +932,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 indexer's state, on its watch; from ADR 0547's second
|
||||
mesh-config and the node agents read placement from the seat `mesh-placement`'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 0549 §3).
|
||||
(ADR 0567).
|
||||
- **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.
|
||||
@@ -920,7 +1048,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 catalogue with a new seat verb is delivered as one group, with no
|
||||
1. A feature across the controller and the catalog 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.
|
||||
@@ -947,9 +1075,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 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).
|
||||
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).
|
||||
|
||||
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
|
||||
@@ -1008,7 +1136,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 catalogue change's composition check composes only the machines it touches, and clones no
|
||||
23. A library or catalog 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
|
||||
@@ -1040,7 +1168,8 @@ 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).
|
||||
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).
|
||||
- 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
|
||||
@@ -1083,12 +1212,12 @@ 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 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) |
|
||||
| 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) |
|
||||
| 4 | A delivery backs up the data it moves first, and a failed backup holds it and is said | 3 | mesh-controller, mesh-catalog (mesh-delivery, the backup seat's holder) | 980, split into 1065, 1066, 1067, 1068 |
|
||||
| 3 fix | A group approved together merges in one pass and goes out in one delivery plan, also when its members' tests read each other's repositories | 1 | mesh-catalog (mesh-delivery, gitea) | 1082, 1049, 1047, 1083, 1079 (1084 closed as its duplicate) |
|
||||
| 5 | One read of a module on a node names the commit, build and rollout that put it there | 10, 12 | mesh-controller, mesh-catalog (mesh-config, nats) | 981 |
|
||||
| 5b | Decided: building and delivering leave the controller for parts that watch and write keys in the repository `mesh-delivery`, landing in six releases (ADR 0429); the forge module makes that repository from its config, protected before any team reaches it (ADR 0428) | — (ADR 0429 names them: 13 to 20) | hq, mesh-catalog (gitea) | 1117, 1124, 1128 |
|
||||
| 6 | Folded into release 4 by ADR 0429 §10 (the fetch over SSH in R4c, the cut-over in R4e): installed toolchains, builders fetching over SSH with their own key, and an SDK change shipping with its users are built in the builder's new home, so the builder is changed once | (2: proved by R4e; 4: in part by R4e, wholly by R4g) | — | 979, 971 (moved to release 4) |
|
||||
| 6 | Folded into release 4 by ADR 0429 §10 (the fetch over SSH in R4c, the cut-over in R4e): installed toolchains, builders fetching over SSH with their own key, and an SDK change shipping with its users are built in the builder's new home, so the builder is changed once | (2: proved by R4e; 4: in part by R4e, wholly by R4g) | — | its issue is R4g's (the installed toolchains); the builder's own forge key moved to release 4 (R4c) |
|
||||
| R1a | The node-engine, the controller and the parts can read one layout of the have, presence, pulse and stuck keys: mesh-sdk ships it before its users | — (13, 14 need it) | mesh-sdk | 1131 (key layouts) |
|
||||
| R1b | Every node writes what it runs of each module, on its own keys and nobody else's, and the bus says to one part when a connection opens and closes | 13 in part | the node-engine (`mesh-host`), mesh-controller | 1132 (have keys), 1142 (the node-engine's check, its tests half), 1136 (the have bucket and its grants), 1145 (a module and a build per resource), 1146 (a new repository's first module registered at its merge's Approve), 1147 (the bus's connect events for the watchman), 1160 (a plan passes a machine silent at a middle tier), 1162 (the have bucket takes a node's facts) |
|
||||
| R1c | The operator sees every module on every node, with its build, phase and health, in `mesh-delivery-cli` (aliased `nox-delivery`) and its web view | 13 | mesh-delivery (view) | 1134 (the view), 1137 (the alias, in the view's manifest) |
|
||||
@@ -1097,20 +1226,42 @@ release reports the controller's code lines against research 063's baseline (acc
|
||||
| R2c | The controller says when it last heard each machine from presence alone, and no longer hears the alive message | 14 | mesh-controller | 1183 (every reader of last heard takes presence; the controller's alive side deleted) |
|
||||
| R2d | The node-engine sends no alive message | 14 | the node-engine (`mesh-host`) | 1158 (the node-engine's alive side deleted) |
|
||||
| R3a | The gate, the bridge, the indexer and the teller can read one layout of the source revision, candidates, modules, merges, asks, answers, owed acts, forge messages and conditions, and one function computes the source fingerprint: mesh-sdk ships them before their users (ADR 0437) | — (15 needs it) | mesh-sdk | 1200 (source revision, candidates, modules, forge messages), 1201 (merges, asks, owed acts, answers), 1202 (a condition on its one subject), 1203 (the source fingerprint, proven against three fingerprints the live builder recorded); ahead of it in mesh-controller, so those could be read: 1204 and 1208 (`builds` says each build's source fingerprint) |
|
||||
| R3b | The controller finds the delivery seat's holder by its claim and records each build's fingerprint inputs; the router takes conditions on their one subject; the operator sees no change (ADR 0437) | — | mesh-controller, mesh-catalog (messenger) | 1210 (the controller's own path and the composition check's rule by the seat's claim), 1211 (a merge's announcement heard from the seat's holder), 1212 (the pull requests' keys read from the holder's bucket), 1213 (a merge's warrant read under the asker the announcement names: the teller, or mesh-delivery until R3d), 1214 (the builder computes the source fingerprint with mesh-sdk's function), 1215 (a build record carries its fingerprint's inputs), 1216 (a module may say a condition only under its own name, proven on the controller's grant); in the messenger: 1217 (the router takes a condition on its one subject), 1218 (the same over the bus); one delivery group. Its prepare step found that the controller hears no `transition` or `group` event, and that the router cannot refuse a condition under another module's name, the grant does: both go into R3c's insight on ADR 0437 with 1209 |
|
||||
| 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 (1243); 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 |
|
||||
| R3b | The controller finds the delivery seat's holder by its claim and records each build's fingerprint inputs; the router takes conditions on their one subject; the operator sees no change (ADR 0437) | — | mesh-controller, mesh-catalog (messenger) | 1210 (the controller's own path and the composition check's rule by the seat's claim), 1211 (a merge's announcement heard from the seat's holder), 1212 (the pull requests' keys read from the holder's bucket), 1213 (a merge's warrant read under the asker the announcement names: the teller, or mesh-delivery until R3d), 1214 (the builder computes the source fingerprint with mesh-sdk's function), 1215 (a build record carries its fingerprint's inputs), 1216 (a module may say a condition only under its own name, proven on the controller's grant); in the messenger: 1217 (the router takes a condition on its one subject), 1218 (the same over the bus); one delivery group. Its prepare step found that the controller hears no `transition` or `group` event, and that the router cannot refuse a condition under another module's name, the grant does: both go into R3c's insight on ADR 0437 with R3c's fingerprint issue |
|
||||
| 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 (1318) |
|
||||
| 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 to 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), 1297 (the assigner's composition check), 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 1294 that are no test become tests in R4c to R4e; the rest opened when it is prepared |
|
||||
| R4d | Four modules stop being built from a Dockerfile: route-proxy, nats, n8n and mail's autoconfiguration module (automx, replaced by a Go program), each rework its own release step, nats in a delivery of its own; free between R4a and R4e and never in the same delivery as another release (ADR 0452, ADR 0453 clauses 13, 14, 16) | — | mesh-catalog, mesh-controller (route-proxy), the node-engine (`mesh-host`: n8n's supplementary group) | the four image reworks, opened when it is prepared |
|
||||
| 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 | 979 (the installed toolchains; closed with the toolchain images in R4g), 1304, the controller asking no build (opened when it is prepared) |
|
||||
| R4f | A pull request carries one required status that says what it proved and how long each part took; the controller's composition check and its replays are deleted with the two old statuses; the controller's deletions R3e makes possible go; each level's times are measured, and a bound fails a repository from the first check that meets it (ADR 0447, ADR 0450 §8, ADR 0453 clauses 4, 9, 10) | 21, 22, 23, 24 (the status; the scripts' absence is proved in R4g) | mesh-delivery (checker, assigner), mesh-controller (composition check, replays, `delivery-plan`, `forget-checks`, the asker `mesh-delivery` deleted), mesh-catalog (gitea) | 1299 (the composition check deleted; closes issue 709, the replays' socket), 1300, 1301, 1302 (one status), 1305 (ADR 0482 §8), 1318 |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| R4f | A pull request carries one required status that says what it proved and how long each part took; the controller's composition check and its replays are deleted with the two old statuses; the controller's deletions R3e makes possible go; each level's times are measured, and a bound fails a repository from the first check that meets it (ADR 0447, ADR 0450 §8, ADR 0453 clauses 4, 9, 10) | 21, 22, 23, 24 (the status; the scripts' absence is proved in R4g) | mesh-delivery (checker, assigner), mesh-controller (composition check, replays, `delivery-plan`, `forget-checks`, the asker `mesh-delivery` deleted), mesh-catalog (gitea) | 1300, 1301, 1302 (one status); the composition check deleted (closing issue 709, the replays' socket), ADR 0482 §8 and the controller's deletions are R4g's issues |
|
||||
| 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), 1945's fix before the switch (the bus's user list grants what release 6 uses; delivered first), 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), 1945 (as released the controller writes compositions and grants, which ADR 0547 moves; the plans and the push deleted), 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) |
|
||||
| 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, 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: 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, and keeps the dead letters from the lease write; `mesh-self-check` runs the probes and says `checked`; `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 0564, 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: each node agent writes each handed module's provisioning plan on its own have key, which the vault, the node's provisioner client and every provider read through mesh-sdk instead of the controller's assignments; providers retire and clean up the consumers no plan names by themselves; `rotate` and `issued` are answered by the vault alone (a decision record first for the plan's home; ADR 0349 §10, §12, ADR 0230, ADR 0547 §11) | — | mesh-sdk, the node-engine (`mesh-host`), mesh-tools (the tool runner's provisioner client), mesh-catalog (the vault, the provisioner client, postgres, keycloak, gitea, route-adapter), mesh-controller | 1954 |
|
||||
| L7 | History and records leave the controller: builds, artifacts, durations and delivery history are read from the builder's keys and streams; the artifact store's sweep (`collect`, `held`, `mirrors`, `images`) is a seat's own; data readings are node-backup's own state; modules, seats and tools are read from the indexer, placement and the tool runner; no manifest but the controller's reads a controller bucket, so the lease write's check passes (a decision record first, under research 072; ADR 0189, ADR 0547 §11, ADR 0549 §6) | — | mesh-sdk, mesh-controller, mesh-delivery (builder, indexer, view), mesh-catalog (artifact-store-tools, the backup seat's holder, mounts, nfs-server, minio, mesh-watcher), mesh-tools (the tool runner) | 1955 |
|
||||
|
||||
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
|
||||
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
|
||||
(issue 1781): once its seed has run, the tracker is the only list of a milestone's issues, `issue-tracker.milestones`
|
||||
reads them, and this table keeps its rows without the Issues column (the operator, 2026-10-26).
|
||||
|
||||
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). 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
|
||||
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.
|
||||
@@ -1123,7 +1274,7 @@ is issue 1055. Acceptance test 6 spoke of a ready mark when milestone 2 was deli
|
||||
the Approve, it is proved again by milestone 3.
|
||||
|
||||
R4's prepare step on 2026-10-20 cut release 4 by its orders into R4a to R4g, each one release (ADR 0453, the operator's Approve): the SDK ships before its users, the new parts run dark before anything relies on them, the status switch follows a measured run, and the scripts and build-agent go last;
|
||||
R4d, the four image reworks, has no order of its own and may be delivered at any point between R4a and R4e. Where a record dates a rule by release 4, it reads the R4 release that builds the rule (ADR 0453 clause 15).
|
||||
R4d, the three image reworks (route-proxy, nats, automx; n8n left by ADR 0506), had no order of its own and was delivered between R4a and R4e. Where a record dates a rule by release 4, it reads the R4 release that builds the rule (ADR 0453 clause 15).
|
||||
|
||||
## What is not decided here
|
||||
|
||||
|
||||
@@ -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 catalogue's check, run on the build
|
||||
- **The bed.** mesh-lab provides a throwaway container runtime that the catalog'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
|
||||
catalogue's count of long-running resources without `health` is written down and starts.
|
||||
catalog'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 catalogue-wide parse | a test per refusal; the whole catalogue passes `module check` |
|
||||
| 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` |
|
||||
| | `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 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 |
|
||||
| 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 |
|
||||
| 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
|
||||
catalogue declares only once that controller runs: until then the composition check refuses a declaration, which
|
||||
catalog 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 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
|
||||
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
|
||||
(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 catalogue keeps the count in a file of its own, and its merge check fails
|
||||
**Phase E — the migration.** The catalog 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 catalogue's declarations; the count's first live reading; and
|
||||
the bed's first run on a catalogue change at the build seat.
|
||||
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.
|
||||
|
||||
## As built — Phase F
|
||||
|
||||
|
||||
@@ -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 catalogue, one manifest per module |
|
||||
| **Module** | to say what one module is, in the manifest every other domain reads | the catalog, 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 catalogue**, its own repository check reads a copy of the words retired for the tools and fails
|
||||
- **In the catalog**, 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 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.
|
||||
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.
|
||||
- **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 catalogue | 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 catalog | 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 |
|
||||
|
||||
|
||||
@@ -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 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 |
|
||||
| `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 |
|
||||
| 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,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog modules/mesh-issues, mesh-catalog modules/claude-code, mesh-catalog modules/messenger, mesh-sdk]
|
||||
updated: 2026-10-11
|
||||
updated: 2026-10-26
|
||||
decisions:
|
||||
- "ADR 0275"
|
||||
- "ADR 0278"
|
||||
@@ -13,6 +13,7 @@ decisions:
|
||||
- "ADR 0291"
|
||||
- "ADR 0294"
|
||||
- "ADR 0300"
|
||||
- "ADR 0493"
|
||||
---
|
||||
|
||||
# 53 — The issue tracker
|
||||
@@ -81,7 +82,10 @@ agent finds them with `mesh_search` ("open an issue") and calls them as `issue-t
|
||||
| `set` | a number and any of `severity`, `needs`, `suggested-owner` | changes them as a person decides (§ 10) |
|
||||
| `tracking` | optionally condition keys | for every condition key an issue links, the issues that link it and where each stands: status, needs, worker, fix (§ 12) |
|
||||
| `groom` | optionally `confirm` and a pass's id | the duplicate check over the backlog (§ 9): without `confirm`, starts a dry run in the background and answers its id; with the id alone, its progress and findings; with `confirm` and the id, applies exactly that pass's findings, each checked again |
|
||||
| `import` (slice 3) | optionally `dry-run`, a commit | reads every issue folder of hq through the records module and writes each as an issue at its own number; creates what is absent, fills a status the tracker holds empty, never overwrites, never advances the counter; run again, changes nothing and says so |
|
||||
| `import` (slice 3) | optionally `dry-run`, a commit, and `milestones` with a design's number | reads every issue folder of hq through the records module and writes each as an issue at its own number; creates what is absent, fills a status the tracker holds empty, never overwrites, never advances the counter; run again, changes nothing and says so. With `milestones`, it seeds that design's milestone records from its table instead (§ 13) |
|
||||
| `milestones` | optionally a design, a status, or one milestone as design/id | the milestone records, each with its issues and their count by status; one milestone with its issues' rows and its history (§ 13) |
|
||||
| `milestone-set` | a design, an id, an outcome, optionally an order, or `announce` | makes a milestone record, or changes its outcome or order; with `announce`, says the record again as its event (§ 13) |
|
||||
| `milestone-move` | a milestone as design/id, a status and a reason | opens or closes a milestone; closing is refused while an issue linked to it is neither resolved nor wontfix (§ 13) |
|
||||
|
||||
Each verb's description says in plain words what it does and what it refuses, without the mesh's internal names, so
|
||||
an agent picks the right one from `mesh_search` alone. A refusal says what was wrong and what would be accepted.
|
||||
@@ -100,8 +104,8 @@ the issue's worker, who works on it, as the worker states it
|
||||
- `located-in`: entries of the registered list of owners (repositories and modules), never free text;
|
||||
- `fixed-by` (pull requests or commits), `amended-design`, `replay` or `replay-none`, and a reason for `wontfix`;
|
||||
- `notes`: appended, each with who wrote it, when, and the text;
|
||||
- `links`, typed: to another issue (`duplicates`, `relates-to`), a pull request, a decision record, a design, a
|
||||
condition key (the issue **tracks** that condition, § 12) or a delivery;
|
||||
- `links`, typed: to another issue (`duplicates`, `relates-to`), a pull request, a commit, a decision record, a design,
|
||||
a condition key (the issue **tracks** that condition, § 12), a delivery, or the one milestone it belongs to (§ 13);
|
||||
- `documents`: the report, and a diagnosis and a resolution when written, each with its author and date, revised in
|
||||
place with its history kept (ADR 0291);
|
||||
- `record`: the path of the issue's folder in hq, while one exists; none is opened any more, and the folders are
|
||||
@@ -111,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 catalogue
|
||||
other module setting. Slice 1 starts it with the repositories in hq's `00-META/repos.md` and the modules the catalog
|
||||
registers.
|
||||
|
||||
**An issue that has a folder in hq** kept its status in that folder's frontmatter until the import of slice 3 ran;
|
||||
@@ -167,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 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) |
|
||||
| `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) |
|
||||
| `wontfix` | a reason |
|
||||
|
||||
A refused move changes nothing and says which rule refused it. The rows for `diagnosing`, `fixing` and `resolved` are
|
||||
@@ -203,6 +207,7 @@ worker; one that stops moves the issue on, or is stale a day later.
|
||||
| The triage fills only what is empty, links no cycle, and an issue is ready only when its dependencies' fixes are delivered | the rows of ADR 0279's "How it is checked" |
|
||||
| The duplicate check acts only on a clear duplicate, never on an older or a resolved issue, once per issue, loudly when it cannot judge | the rows of ADR 0278's "How it is checked" |
|
||||
| An issue's documents are kept whole, with author, date and history, and the import of hq's folders loses nothing (ADR 0291) | the module's tests of `documents` and of the import, one case per document kind; the comparison of every issue with its folder before the folders are removed, written on issue 427. Tracked by issue 427 |
|
||||
| An issue belongs to at most one milestone, linked only by the verb, and only to an open one (ADR 0493 §2 to §4) | the module's table test of the `milestone` and `commit` link kinds, one case per refusal; the test that the triage and the duplicate check never add a `milestone`, `pull-request` or `commit` link; one integration test that starts the module whole on a real bus and makes, links and refuses |
|
||||
| Live | on the mesh, an agent calls `issue-tracker.open` and gets the next number; two sessions on two nodes open at the same moment and get two different numbers; `list` shows both |
|
||||
|
||||
## 8. The slices after the first
|
||||
@@ -312,6 +317,32 @@ with no pull request open is **abandoned**.
|
||||
**tracking line** in every condition's message; the words and the verdict it changes are its own (to-be 45 § 5).
|
||||
The controller learns nothing of issues.
|
||||
|
||||
## 13. Milestones
|
||||
|
||||
**A milestone is a record of the tracker, made from its design's table** (ADR 0493 §1). The state `milestones`, which the
|
||||
manifest declares beside `issues`, keeps one value per milestone, keyed by its design's number and its id: the id as the
|
||||
design's table writes it, the outcome, its order in the table, its status (`open` or `closed`, with a reason), who made
|
||||
and who last moved it and when, and the hq commit and path it was seeded from. It is written by compare-and-set at the
|
||||
revision read, and its history is the key's kept values with the events `milestone-set` and `milestone-moved`.
|
||||
|
||||
**An issue's milestone is the issue's own link, not a list on the milestone.** The link kind `milestone` (its target
|
||||
design/id, such as 47/R6) is written in the issue's log like any link, so the rule that an issue belongs to at most one
|
||||
milestone (ADR 0493 §2) is checked inside that issue's one write, refused naming the milestone it is in; a link to a
|
||||
milestone that does not exist or is closed is refused (§3: a fix found on the way joins the open milestone). A milestone's
|
||||
issues are read from the issues in memory. Pull requests and commits are linked to the issue they fix the same way, with
|
||||
the kinds `pull-request` and `commit` (§4); nothing reads them from a description, and the triage never adds one.
|
||||
|
||||
**The seed** is `import` with `milestones` and a design's number: it reads the table through the records module, makes
|
||||
each record that is absent in the table's order, links each issue it names that has no milestone, refuses (and names)
|
||||
an issue another milestone holds or two rows name, closes a milestone whose issues are all done (wontfix, or resolved
|
||||
with every live fix delivered, or resolved with no live fix) when it refused none of its row's issues, and run again
|
||||
changes nothing. Once a design is seeded, the tracker is the only list of its milestones'
|
||||
issues: the design's table keeps its rows without an Issues column, and the knowledge base's `progress` reads
|
||||
`milestones` (issue 1781).
|
||||
|
||||
Readiness, the one ask per milestone and its release are the delivery seat's (ADR 0493 §7 to §11, issue 1783); the
|
||||
tracker gives them the records and links they read.
|
||||
|
||||
## What it leaves out
|
||||
|
||||
Priorities beyond severity, notifications beyond the `ready` and `ready-to-verify` events and the stuck-fix warning, full-text or semantic search, the forge's own issue lists, and an index of any kind:
|
||||
|
||||
@@ -228,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 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 |
|
||||
| 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 |
|
||||
| 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.
|
||||
@@ -318,7 +318,7 @@ 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 catalogue or the instruction text pointing at a removed path.
|
||||
in this repository, the catalog or the instruction text pointing at a removed path.
|
||||
|
||||
## What it leaves out
|
||||
|
||||
|
||||
@@ -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 catalogue's check refuses a manifest whose
|
||||
Owl declares the range of Betterbird versions it supports. The catalog'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 catalogue. The module ships only its policy's locks and the names it keeps at Betterbird's own
|
||||
layer, not in the catalog. 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 catalogue held before.
|
||||
hold. The first mesh-wide proposal also carries the operator's setup of `prefs`, which the catalog held before.
|
||||
|
||||
## How it is checked
|
||||
|
||||
- **The catalogue's tests:** both pins carry a URL, a version and a sha256; a Betterbird pin outside Owl's range
|
||||
- **The catalog'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 catalogue and the applications live in repositories of their own and move after it, and only
|
||||
the media catalog 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 catalogue and the applications kept in their own
|
||||
Inside the release this still holds: the media catalog 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 catalogue carries file text.
|
||||
their templates. **Proof:** as T2; afterwards no manifest in the catalog carries file text.
|
||||
|
||||
**T6 (issue 659) — The media catalogue and the applications.** *mesh-media-catalog and each application's
|
||||
**T6 (issue 659) — The media catalog 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 catalogue still registers.
|
||||
rule; every manifest in the catalog still registers.
|
||||
|
||||
**K2 (issue 661) — The list of predefined keys.** *mesh-controller and mesh-catalog. A day.* `predefined.json` at the
|
||||
catalogue's root, with `font-size` and `agent-managed-settings` first; the build validates it and every
|
||||
catalog'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 catalogue says config.** *mesh-catalog. A day.* The 15 modules using `${setting:}`, the 9
|
||||
**K6 (issue 665) — The catalog says config.** *mesh-catalog. A day.* The 15 modules using `${setting:}`, the 9
|
||||
declaring settings and the 4 with `takes-settings` move to the new names and the full declaration.
|
||||
**Proof:** the catalogue's check finds no old name in a moved module.
|
||||
**Proof:** the catalog'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 catalogue has moved: the catalogue's check refuses inline text, `settings`, `${setting:}` and the
|
||||
every catalog has moved: the catalog'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 catalogue and the applications carry their file text as templates | T6 | 659 |
|
||||
| 2 | The media catalog 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 catalogue's check refuses a manifest that
|
||||
person or an agent: claude-code's `agent-account` resource goes, and the catalog'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 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
|
||||
**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
|
||||
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 catalogue's check refuses an unmarked one.
|
||||
3. Every existing module's tools carry an effect, and the catalog'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 catalogue declares a login for a person or an agent; the catalogue's check refuses one.
|
||||
8. No manifest in the catalog declares a login for a person or an agent; the catalog'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 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 | — |
|
||||
| 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 | — |
|
||||
| 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 catalogue'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 catalog'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 |
|
||||
|
||||
|
||||
@@ -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 catalogue (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 catalog (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
|
||||
|
||||
@@ -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 catalogue's
|
||||
The milestones build ADR 0468, feature `files-through-the-store`. They are cut by release order: the catalog'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 catalogue (playbook 07, *a new manifest word*).
|
||||
the delivery's indexer), then the catalog (playbook 07, *a new manifest word*).
|
||||
|
||||
| # | Outcome | Proves | Repository | Issues |
|
||||
|---|---|---|---|---|
|
||||
|
||||
@@ -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 catalogue'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 catalog'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
|
||||
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
|
||||
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
|
||||
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 catalogue was read again for mounts no resource declares: twenty-four remained, in two
|
||||
1. The catalog 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 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
|
||||
**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
|
||||
ADR 0091.
|
||||
|
||||
+1
-1
@@ -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 catalogue in artefact form, and the same role a separate object store plays in the
|
||||
mesh's module catalog 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 catalogue (the runtime that serves a module's tools and
|
||||
Rolling the module runtime out to the catalog (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 catalogue.
|
||||
this way, so this is not one module's quirk — it is the config path for the whole catalog.
|
||||
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 catalogue examples (2026-09-02), not by an outage — the outage is the
|
||||
Found by review of the catalog 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 catalogue that this failure is
|
||||
- Are there other seeded-then-mutated files already in the catalog that this failure is
|
||||
waiting inside?
|
||||
|
||||
@@ -6,7 +6,7 @@ opened: 2026-09-02
|
||||
|
||||
## The symptom, as observed
|
||||
|
||||
Found by review of the catalogue examples (2026-09-02). The media stack is several modules —
|
||||
Found by review of the catalog 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 catalogue has otherwise avoided: the fix could be a new
|
||||
It is also a fork in the design the catalog 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 catalogue (2026-09-05), across several modules at once. A module can
|
||||
Found while converting the catalog (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 catalogue left unpinned
|
||||
# 039 — The lab's registry was pinning what the catalog 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 catalogue is pinned; the host's refusal has nothing left to
|
||||
serves them. Every manifest in the catalog 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 catalogue manifests use env-file for a secret, and nothing in the catalogue
|
||||
environment; 28 catalog manifests use env-file for a secret, and nothing in the catalog
|
||||
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 catalogue engine
|
||||
a process as a file, an environment exception is declared with a reason, and the catalog 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.
|
||||
|
||||
+1
-1
@@ -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 catalogue named a copy produced inside a
|
||||
and a copy exists on one mesh.** The three modules in the catalog 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 catalogue declares an ssh port, and the generator has no
|
||||
**Including ssh.** No module in the catalog 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 catalogue serves
|
||||
**A module's tools are its operator-facing surface, and nothing can reach it.** The catalog 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 catalogue exists to answer, and today
|
||||
on a module, and what is stale. Those are the questions the catalog 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 catalogue that was never installed at all.
|
||||
healthy container of a similar name, and a catalog 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 catalogue knows nothing that was built before it
|
||||
# 050 — The catalog 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 catalogue,
|
||||
the control plane, a module, and a broker. Asked afterwards what it holds, the catalogue answered
|
||||
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
|
||||
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 catalogue itself.
|
||||
Missing: the shared base, the store, and the catalog itself.
|
||||
|
||||
The catalogue learns what exists by consuming the builder's announcement over the broker. Anything
|
||||
The catalog 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 catalogue are, necessarily, the things the catalogue needed in order to exist: the base
|
||||
before the catalog are, necessarily, the things the catalog 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 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
|
||||
**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
|
||||
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 catalogue does not know what it was not told, so it does not say
|
||||
**Nothing reports the gap.** The catalog 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 catalogue's copy.** The control plane holds every build it
|
||||
**The record is not lost, only the catalog'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 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
|
||||
- **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
|
||||
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 catalogue as one consumer among several, which it
|
||||
delivered when something is? That treats the catalog as one consumer among several, which it
|
||||
already is.
|
||||
- **Or should the control plane be the record and the catalogue a projection of it?** Two stores
|
||||
- **Or should the control plane be the record and the catalog 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 catalogue cannot compare itself to a source of truth
|
||||
- **How would anyone notice next time?** The catalog 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 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. |
|
||||
| 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. |
|
||||
|
||||
@@ -53,11 +53,11 @@ machine.
|
||||
|
||||
## Open questions
|
||||
|
||||
- **Which private registry, and does the bootstrap have one?** The catalogue holds `verdaccio`;
|
||||
- **Which private registry, and does the bootstrap have one?** The catalog 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 catalogue holds a private registry module, and a
|
||||
- **Or should the SDK be a published package?** The catalog 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 catalogue are already running.
|
||||
installed several steps after the store, the broker and the catalog 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 catalogue) come up before the network step, so the `from: mesh` rule would have to be
|
||||
(the catalog) 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 catalogue
|
||||
# 060 — The mesh cannot build most of its own catalog
|
||||
|
||||
## Symptom
|
||||
|
||||
Only 8 of the catalogue's 70 modules carry a `build` section; 62 do not. A module without one
|
||||
Only 8 of the catalog'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 catalogue and structural for a few.
|
||||
The gap was mechanical for most of the catalog 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
|
||||
|
||||
+1
-1
@@ -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 catalogue have the same shape today?
|
||||
- Does any other converted module in the catalog have the same shape today?
|
||||
|
||||
+1
-1
@@ -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 catalogue for the same shape — remain open; this record
|
||||
command, and auditing the rest of the catalog for the same shape — remain open; this record
|
||||
covers the one module the bed proved.
|
||||
|
||||
+1
-1
@@ -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 catalogue read
|
||||
artifact-store provision. Two silent degradations sat in that path: a failed catalog 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.
|
||||
|
||||
+1
-1
@@ -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
|
||||
catalogue its build sections (hq issue 060): 44 of 70 modules build from their own source with no
|
||||
catalog 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
|
||||
|
||||
+3
-3
@@ -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 catalogue (three modules
|
||||
**Located in:** the builder (what it tells npm and the runtime) and the catalog (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 catalogue
|
||||
was read again, and exactly one recipe installs a public package — the catalogue module's own,
|
||||
*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,
|
||||
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 catalogue at the time of writing, a module's own secrets other
|
||||
**one value**. Counted across the catalog 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 catalogue as it is today: nine modules hold two
|
||||
2. The third question was answered by reading the catalog 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 catalogue parser) and the pair
|
||||
**Located in:** the manifest's `secrets` vocabulary (the catalog 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 catalogue under
|
||||
whenever it builds the controller from source, and again in the catalog 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 catalogue copy alone (to make the controller read its credentials from files,
|
||||
Changing the catalog 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 catalogue copy goes and genesis reads the controller's manifest from the controller's
|
||||
Either the catalog 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
|
||||
catalogue's names a placeholder image digest and carries no build section. Everything else was
|
||||
catalog'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 catalogue's,
|
||||
image — that is what replaced the mesh's record. The installer's step 9 read the catalog's,
|
||||
pinned its placeholder to the image the registry assigned, and registered that. Nothing else
|
||||
read the catalogue's copy.
|
||||
read the catalog's copy.
|
||||
3. The decision was already taken. ADR 0069
|
||||
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
|
||||
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
|
||||
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 catalogue's copy. The fix
|
||||
**Located in:** mesh-host `internal/bootstrap` (steps 3 and 9), and the catalog'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 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
|
||||
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
|
||||
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 catalogue manifests, so a catalogue change is proven nowhere
|
||||
# Beds carry copies of catalog manifests, so a catalog 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 catalogue checkout the
|
||||
JSON in the test, rather than reading `modules/<name>/module.json` from the catalog 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 catalogue engine now refuses, so the beds
|
||||
an env-file without the declared exception, which the catalog 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 catalogue (`--catalog` and a
|
||||
- The genesis bed and the vault bed already read the catalog (`--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 catalogue module reads its manifest from the catalogue checkout,
|
||||
Every bed that installs a catalog module reads its manifest from the catalog 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 catalogue, which is how
|
||||
copies go. Until then, each bed's copy is updated by hand alongside the catalog, which is how
|
||||
the six conversions were proven.
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
# Diagnosis — 2026-09-21
|
||||
|
||||
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
|
||||
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
|
||||
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 catalogue for two
|
||||
the mesh builds travelled to the machine unresolved; one still asked the catalog 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 catalogue only in what the lab must rewrite — an image, a
|
||||
- copies that differ from the catalog 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 catalogue's postgres and lavinmq
|
||||
- a second provider raised beside the foundation's. The catalog'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 catalogue there
|
||||
second one next to it, renamed and on a private network. Reading the catalog 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
|
||||
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
|
||||
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
|
||||
([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 catalogue, so even a
|
||||
bed that read the catalogue 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 catalog, so even a
|
||||
bed that read the catalog 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 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
|
||||
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
|
||||
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 catalogue module's name
|
||||
# A mesh test wears a catalog 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 catalogue's module of that name. Each is cut down to
|
||||
`route-proxy` or `hello-web` that is not the catalog'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 catalogue delivers a file; a
|
||||
token or an API key in the container's environment where the catalog 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 catalogue has not got.
|
||||
manifest adds three resources the catalog 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 catalogue's redis, which requires the vault's.
|
||||
secret proves nothing about the catalog'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 catalogue, which is what issue 073 found nobody
|
||||
fixture" except reading the literal against the catalog, 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 catalogue, it is declared debt.
|
||||
copy; until each bed is renamed or made to read the catalog, it is declared debt.
|
||||
|
||||
## What would close it
|
||||
|
||||
Each of the twelve beds either reads the catalogue's module and meets its declared requirements
|
||||
Each of the twelve beds either reads the catalog'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 catalogue module's — `a-cache`, `a-store`, `a-sidecar` — so a green run claims exactly
|
||||
that is no catalog 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.
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
from the manifest's name, and the tool runtime binds its queues under the module name baked
|
||||
into its image. A fixture named `redis-fixture` running the real redis runtime would be
|
||||
refused the queues it serves on. The name is not a label. **A fixture that runs a module's
|
||||
runtime carries the module's name**, and therefore reads the catalogue
|
||||
runtime carries the module's name**, and therefore reads the catalog
|
||||
(ADR 0093).
|
||||
3. Of the twelve, the two whose only cut was the vault's secret — the grant bed and the
|
||||
backend-network bed, both redis — read the catalogue's redis now and install the vault beside
|
||||
backend-network bed, both redis — read the catalog's redis now and install the vault beside
|
||||
it, as the vault bed does. The rest are declared with this reason: four sidecar beds need the
|
||||
module's server raised (and, for grafana and sonarr, the route module); the minio and postgres
|
||||
grant beds raise a second store beside the foundation's; the route-forwarding bed needs the
|
||||
@@ -22,13 +22,13 @@
|
||||
by decision; converted two at a time as the modules they need are raised beside them.
|
||||
|
||||
*2026-09-21, later.* Ten conversions were the wrong shape. Six of the ten beds duplicated coverage
|
||||
that exists elsewhere since the catalogue beds and the whole-mesh beds read the catalogue: the four
|
||||
that exists elsewhere since the catalog beds and the whole-mesh beds read the catalog: the four
|
||||
sidecar beds proved a sidecar alone, which the module beds prove whole; the minio and postgres
|
||||
grant beds proved a grant with a second store beside the foundation's, which the grant bed and
|
||||
the vault bed prove against the catalogue. Retired, with their scenarios. Two remain declared:
|
||||
the vault bed prove against the catalog. Retired, with their scenarios. Two remain declared:
|
||||
route-forwarding, which needs the certificate authority beside the proxy, and the large mesh test.
|
||||
|
||||
*Route-forwarding's conversion is blocked:* the catalogue's authority cannot be raised as written
|
||||
*Route-forwarding's conversion is blocked:* the catalog's authority cannot be raised as written
|
||||
([issue 076](../076-a-served-fact-made-at-first-start-cannot-be-served/00-report.md)).
|
||||
|
||||
*Later the same day.* Route-forwarding converted, with the authority beside the proxy
|
||||
@@ -37,23 +37,23 @@ route-forwarding, which needs the certificate authority beside the proxy, and th
|
||||
*2026-09-21, late.* The last three `WEARING` declarations are gone. The large mesh test's fixtures
|
||||
are `a-store` (a provider with no resources) and `adopted-analytics`, its builder fixture retired below; the
|
||||
runtime-restart bed's fixture is `a-runtime`. The credential-mechanism bed could not be renamed:
|
||||
its runtime image is the catalogue's, and a runtime names its queues by the module compiled into
|
||||
its runtime image is the catalog's, and a runtime names its queues by the module compiled into
|
||||
it, so the mesh's account for a module of another name is refused at the broker. It was retired —
|
||||
the grant end-to-end bed proves the same login against the catalogue's redis, with the mesh writing
|
||||
the grant end-to-end bed proves the same login against the catalog's redis, with the mesh writing
|
||||
the contributions the retired bed wrote by hand — and its one assertion nothing else made (the
|
||||
provider needs no seal key) moved there. Running the large test's renamed tests found two things
|
||||
its long idleness had hidden: the consumer fixture's login exceeded a backend's twenty characters
|
||||
(ADR 0049; it has a slug now), and the builder-as-module test declared the builder's image as an
|
||||
upstream artifact by bare image ID, which a registry copy cannot fetch since ADR 0096 — retired,
|
||||
since genesis installs the catalogue's builder and proves the same. The large test as a whole has
|
||||
since genesis installs the catalog's builder and proves the same. The large test as a whole has
|
||||
not been run end to end; its renamed tests and the tests around them have.
|
||||
|
||||
*2026-09-22.* The large mesh test was run end to end, for the first time in weeks: 12 of 25 passed.
|
||||
Everything that failed was the bed lagging the mesh, and one defect of the mesh's own. The bed now
|
||||
derives the anchor's filter after placing it on the overlay (ADR 0088), declares the ports its
|
||||
fixtures listen on, waits for an image pulled at apply, starts the hand-started host and builder
|
||||
once on a warm return, and reads the resolver modules from the catalogue instead of the example
|
||||
modules that moved there. Three more tests were retired for the catalogue beds that prove them
|
||||
once on a warm return, and reads the resolver modules from the catalog instead of the example
|
||||
modules that moved there. Three more tests were retired for the catalog beds that prove them
|
||||
(the modules resolving together and the forge, whole-mesh-novox; the cache grant, the grant bed)
|
||||
and one for what every later test proves (the hub filtered without severing the mesh). The
|
||||
defect: every machine named twice over in the files the mesh writes
|
||||
|
||||
@@ -10,7 +10,7 @@ A per-module bed stocks the module's runtime image — the tool runtime carrying
|
||||
code — from the workstation's image store, by tag. The image is built by hand, by a script in the
|
||||
lab repository that the suite never calls. On the day issue 073 was worked, the images for six
|
||||
modules whose beds were about to run dated from two weeks before the manifests they were to be
|
||||
installed with; the catalogue's code for those modules had changed since, and every bed would have
|
||||
installed with; the catalog's code for those modules had changed since, and every bed would have
|
||||
passed against the old image. The suite's own rule — *the run rebuilds what it tests* — is held
|
||||
for the host binary and the controller image and not for these.
|
||||
|
||||
@@ -20,13 +20,13 @@ for the host binary and the controller image and not for these.
|
||||
the module proven; nothing says the image predates the code.
|
||||
- **It is the same fault [issue 005](../005-pipeline-test-harness-unbuildable/00-report.md)
|
||||
named**, one layer down: a stale artifact reporting success against code that moved.
|
||||
- The receipt now names the catalogue's commit (ADR 0089),
|
||||
- The receipt now names the catalog's commit (ADR 0089),
|
||||
which makes this sharper, not better: the receipt claims a commit whose runtime code was never
|
||||
built into what ran.
|
||||
|
||||
## What would close it
|
||||
|
||||
The suite builds, or refuses to stock, a module runtime whose image is older than the module's
|
||||
source in the catalogue the run is pointed at — the same treatment the host binary and the
|
||||
source in the catalog the run is pointed at — the same treatment the host binary and the
|
||||
controller image get. Or the scenario says which images it stocks stale, and the receipt says
|
||||
so too.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
image store held, built by hand by a script the suite never called.
|
||||
2. The rule that covers the rest — *the run rebuilds what it tests* — was extended to these. For
|
||||
the beds named, every runtime their scenarios stock is compared against the module's source in
|
||||
the catalogue and the tool runtime and SDK it is built on; the image is rebuilt where it is
|
||||
the catalog and the tool runtime and SDK it is built on; the image is rebuilt where it is
|
||||
older, missing, or the source has uncommitted changes, and a build that fails stops the suite.
|
||||
Proven by removing a runtime image and watching the run rebuild it before the bed passed.
|
||||
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
opened: 2026-09-21
|
||||
---
|
||||
|
||||
# A served fact made at first start cannot be served, so the catalogue's authority cannot start
|
||||
# A served fact made at first start cannot be served, so the catalog's authority cannot start
|
||||
|
||||
## Symptom, as observed
|
||||
|
||||
The catalogue's certificate authority module declares its root certificate, its root key and
|
||||
The catalog's certificate authority module declares its root certificate, its root key and
|
||||
that key's password as its own secrets, and writes each into a file the container is told to
|
||||
initialise from. An own secret nobody delivers is minted by the mesh as random bytes, and random
|
||||
bytes are not a certificate: issued that way, the authority cannot initialise. It could be
|
||||
raised by an operator making a root with openssl and delivering all three through `secret
|
||||
accept` — the whole-mesh bed did exactly that, and has not run since it was converted — but a
|
||||
module that only starts once a person has hand-made its key material is not a module a mesh
|
||||
can raise. Found while converting the route-forwarding bed to the catalogue's proxy, which
|
||||
can raise. Found while converting the route-forwarding bed to the catalog's proxy, which
|
||||
requires the authority beside it.
|
||||
|
||||
The authority can make its own root at first start — the certificate bed raises it that way and
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
ADR 0098.
|
||||
|
||||
**Proven** the same day: the route-forwarding bed raises the authority, the proxy and a consumer
|
||||
from the catalogue on one node and serves a public name through the proxy. Two things the run
|
||||
from the catalog on one node and serves a public name through the proxy. Two things the run
|
||||
taught, both about the bed rather than the decision:
|
||||
|
||||
- The authority certifies itself for the machine's private-network address, which is what a
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
**Located in:** the two refusals and the proxy's manifest. Decided in
|
||||
ADR 0099; proven by unit
|
||||
tests on the host (the step runs again when its file changed, and not when it did not; the
|
||||
container naming the step is recreated after it ran) and the catalogue-wide manifest test.
|
||||
container naming the step is recreated after it ran) and the catalog-wide manifest test.
|
||||
|
||||
**What closed it, and what did not.** The report named two ways the fact changes: the provider
|
||||
moved, or its state wiped. The move is closed: it rewrites the consumer's binding, the step names
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user