Bus state by template, no readers, and the ledger: designs 25, 32 and 55 and the glossary follow ADR 0595-0597 #506
+35
-3
@@ -117,6 +117,8 @@ downstream of Module and Placement. **Decided by** ADR 0262, 0277, 0348.
|
||||
**Uses:** module, node, manifest (Module), assignment (Placement), provision (Provisioning), proposal
|
||||
(Operator and conversation).
|
||||
|
||||
- **template file** — a file a module ships with placeholders, such as `${config:<key>}`, which the provisioner
|
||||
client fills on each node (ADR 0349). Never a template of bus state (see [Homonyms](#homonyms)).
|
||||
- **config value** — anything a module reads that someone may change: a font size, a domain, an address.
|
||||
A module declares every one it reads under `config` in its manifest, with a type, a description,
|
||||
validation, whether it is trusted, optionally the predefined key it falls back to, and a default or
|
||||
@@ -213,7 +215,8 @@ every other domain has something to run on. **Recorded by** the controller and t
|
||||
A module reaches it by requiring `mesh-bus` (ADR 0128);
|
||||
one that does not require it has no account on it. Held by the `mesh-broker` seat, which is named
|
||||
after the role rather than the server, so the server can change without the seat doing so. The `nats`
|
||||
module holds it.
|
||||
module holds it. The **bus module** is the module holding `mesh-broker`; it also provides bus state (Data,
|
||||
ADR 0595).
|
||||
- **the deprecated broker** — the lavinmq module. It was the mesh's bus and is not any more. It keeps
|
||||
running as an **ordinary provider** of the `amqp` provision, for modules that need a message broker
|
||||
of their own the way something needs a database
|
||||
@@ -724,8 +727,8 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
|
||||
|
||||
**Purpose.** To know every item of data a module holds, how precious it is, and to keep it recoverable.
|
||||
**Recorded by** the manifests' `data` declarations and every node's `node-backup` seat.
|
||||
**Upstream of** Delivery and Health and repair. **Decided by** ADR 0232, 0233, 0235, 0571.
|
||||
**Uses:** module, node, provider and consumer (Provisioning), person.
|
||||
**Upstream of** Delivery and Health and repair. **Decided by** ADR 0232, 0233, 0235, 0571, 0595, 0596, 0597.
|
||||
**Uses:** module, node, provider and consumer (Provisioning), person, bus module (Core), tool runner (Core).
|
||||
|
||||
- **data class** — how precious an item of data is, ranked by the operator: `irreplaceable`,
|
||||
`valuable`, `rebuildable`, `cache`; and `none` for a provision that keeps nothing of anybody's
|
||||
@@ -740,6 +743,25 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564.
|
||||
self-check read them there, never the controller (ADR 0571 §5).
|
||||
- **sticky binding** — a consumer's tie to the data a provider keeps for it, which moves only by a
|
||||
person (ADR 0232).
|
||||
- **bus state** — what a module keeps on the bus, asked for in `requires` as a provision of the bus module, by a
|
||||
template and its options; created by the bus module and reached by the owner's account alone. Another module
|
||||
asks the owner's tools for its data, or hears its declared events (ADR 0595, ADR 0596).
|
||||
- **storage type** — how the bus keeps bus state: the **stream**, the base, or one built on it, the **key-value
|
||||
bucket**, the **object store** or the **ledger**, each with calls of its own (ADR 0595 §2).
|
||||
*Not:* ~~data type~~
|
||||
- **configuration** of a storage type — the settings it is given: what is kept, for how long, how much, and what
|
||||
happens when full. Never bare where it could be read as a config value (see [Homonyms](#homonyms)).
|
||||
- **template** — a storage type with a configuration, predefined and named: **state**, **events**, **work
|
||||
queue**, **log**, **objects** and **ledger** (ADR 0595 §2). What keeps its data safe, "when full" among it, is
|
||||
fixed by the template. *Template of bus state* where it could be read as a template file (see
|
||||
[Homonyms](#homonyms)). The template log is the module log of ADR 0297.
|
||||
- **option** of a template — one setting a module may change, within its bound, such as state's past values (1 to
|
||||
64) (ADR 0595 §3).
|
||||
- **ledger** — a storage type keeping, per instance of a type, a history of every **operation** (one change, kept
|
||||
in order) and a state derived from that history by each operation's **ledger effect** (one of a fixed set: set
|
||||
fields, set an entry of a map, append to a list, remove), through the tool runner's `apply` (ADR 0597).
|
||||
- **snapshot** of a ledger's state — the latest state of one instance, on a subject of its own, never expiring,
|
||||
from which a rebuild starts; distinct from the stream snapshot.
|
||||
|
||||
## Connectivity — how nodes and modules reach one another
|
||||
|
||||
@@ -1076,6 +1098,16 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| | an account in a group of the machine | **account group** | Placement |
|
||||
| domain | an area of the mesh that owns a set of concepts and the one word for each (ADR 0244), the sections of this page; the one sense "domain" keeps bare | **mesh domain** | The record |
|
||||
| | one of the eight concerns a direction and a design are reviewed against (playbook 01); never written bare | **architectural domain** | The record |
|
||||
| configuration | the settings of a storage type, which a template fixes or a module gives | **configuration** of a storage type | Data |
|
||||
| | anything a module reads that someone may change | **config value** | Config |
|
||||
| snapshot | the latest state of one ledger instance, from which a rebuild starts | **snapshot** of a ledger's state | Data |
|
||||
| | the bus's own backup of each stream | **stream snapshot** | Data |
|
||||
| template | a storage type with a configuration, asked for in `requires` | **template** of bus state | Data |
|
||||
| | a file with placeholders a provisioner fills | **template file** | Config |
|
||||
| option | a setting of a template a module may change | **option** of a template | Data |
|
||||
| | one choice of an ask to a person | an ask's **option** | Operator and conversation |
|
||||
| effect | what an operation does to a ledger's state | **ledger effect** | Data |
|
||||
| | a tool's mark: reads, acts or dangerous | a tool's **effect** | Identity and access |
|
||||
| push | the controller giving nodes their declarations, until release 6 | **send** (verb `push`) | Placement |
|
||||
| | a git push | **git push** | The record |
|
||||
| | a phone notification | **push notification** | Operator and conversation |
|
||||
|
||||
@@ -7,9 +7,14 @@ code:
|
||||
- mesh-tools src/broker-amqp.ts (to be replaced)
|
||||
- mesh-catalog modules/nats (to be written)
|
||||
- mesh-sdk src (the protocol's NATS binding, step 3)
|
||||
- mesh-tools node-tools/internal/bus (a module's state, ADR 0201)
|
||||
updated: 2026-10-10
|
||||
- mesh-tools node-tools/internal/bus (a module's state, ADR 0201; the new calls and apply, ADR 0595, ADR 0597)
|
||||
- mesh-sdk (the typed bus-state entry, its binding and the new calls' package, ADR 0595)
|
||||
- mesh-delivery (the judge's rules and the grants part's binding, ADR 0595)
|
||||
updated: 2026-11-03
|
||||
decisions:
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0597"
|
||||
- "ADR 0289"
|
||||
- "ADR 0264"
|
||||
- "ADR 0235"
|
||||
@@ -38,6 +43,11 @@ under which subject, with which guarantee, under whose account; how a node joins
|
||||
reaches a tool; how the mesh moves from the bus it has to this one; and how each claim is checked.
|
||||
Prose and diagrams only; no configuration is pasted.
|
||||
|
||||
**Amended, 2026-11-03** (ADR 0595, ADR 0596, ADR 0597): what a module keeps on the bus is its **bus state**, a
|
||||
provision of the bus module asked for by template, created by the bus module, read by its owner alone; and the
|
||||
**ledger** is a storage type of its own. § 12 is the design; the `KV_<module>_<name>` row of § 3 and the module's
|
||||
state in § 4 hold for a module still on the old sections.
|
||||
|
||||
## 1. What the bus is for
|
||||
|
||||
**The bus is where the mesh happens.** Not a transport the mesh sends things over — the place a
|
||||
@@ -179,7 +189,7 @@ Core NATS is at-most-once. Everything the mesh must not lose lives in a JetStrea
|
||||
| EVENTS | `mesh.mod.*.event.>` and `mesh.seat.*.event.>`; and `mesh.again.>` (ADR 0264) | limits (age, size), durable consumer per subscribing module | a subscriber that was down catches up; after `max-deliver` attempts the advisory feeds `mesh.events.dead` (its own small stream). *2026-10-08, ADR 0264:* that stream is DEAD_LETTERS, below; each consumer also filters `mesh.again.<consumer>.>`, where a message it gave up on is delivered again to it alone. *2026-10-01:* a build's whole log is here too, as the build seat's `log.<build id>` events (ADR 0157) — one subject per build, a week of retention, read back by `builds --log <id>` with a consumer that is gone when the reading is done |
|
||||
| DEAD_LETTER_NOTICES | the server's maximum-deliveries advisory | work queue, a week; one consumer (the controller), no `max-deliver` | the server's word that a consumer gave up on a message, kept whether or not a controller is listening, until the controller has copied the message (ADR 0264) |
|
||||
| DEAD_LETTERS | `mesh.events.dead.<stream>.<consumer>` | limits: no age, 256 MiB, refuses new when full; a day's de-duplication | every message a consumer gave up on, with its consumer, subject, deliveries and when, until a person delivers it again or drops it with why (ADR 0264); a `max-deliveries` condition is open per consumer while it holds any |
|
||||
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | the newest value per key — as many past values as the owner declared — no age unless the owner declared one; a value at most 256 KiB, a bucket at most 64 MiB | a module's state (ADR 0201): one per name in a manifest's `state`, created from the catalog on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data |
|
||||
| `KV_<module>_<name>` | `$KV.<module>_<name>.>` | the newest value per key — as many past values as the owner declared — no age unless the owner declared one; a value at most 256 KiB, a bucket at most 64 MiB | a module's state (ADR 0201): one per name in a manifest's `state`, created from the catalog on every raise, so it exists before its owner runs anywhere; kept when the module is unassigned, because what it holds is data. *2026-11-03, ADR 0595:* a module's kept data in the new form is created by the bus module from `requires`, by template (§ 12) |
|
||||
|
||||
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.
|
||||
@@ -266,7 +276,7 @@ expresses this exactly, per subject, and better than a vhost could:
|
||||
permissions for each consumed event's subject, its tool subjects, and that same inbox prefix.
|
||||
Nothing else. A module that tries to publish outside its emits is refused by the server, not by
|
||||
convention.
|
||||
- **A module's state** (ADR 0201), for whichever principal carries the module — today the machine's
|
||||
- **A module's state** (ADR 0201; for the new form, § 12.2: the owner's account alone, no reader, ADR 0596), for whichever principal carries the module — today the machine's
|
||||
runtime, whose grant is the union of its modules': binding to the bucket, reading a key directly,
|
||||
and an ordered consumer for listing and watching, created and deleted on the bucket's own stream
|
||||
and nothing else's; and, for the owner's instances only, publishing under the bucket's own
|
||||
@@ -617,3 +627,275 @@ find what changed and why.
|
||||
that needs it? ADR 0039's test —
|
||||
*does editing it recompile unrelated modules, and does it change often* — probably says no for
|
||||
one instance; worth asking again if a second module needs the same shape.
|
||||
|
||||
## 12. Bus state: what a module keeps on the bus, and the bus module that provides it
|
||||
|
||||
*Added 2026-11-03* (ADR 0595, ADR 0596, ADR 0597, from research 082; the operator, 2026-11-02 20:45: "The names
|
||||
simply represent named configurations or templates"). Until now a manifest declared what a module keeps on the bus
|
||||
in four sections with four vocabularies (`state`, `logs`, `streams`, `objects`), the grants part created each
|
||||
entry once and never changed it, and nothing compared what the bus holds with what was asked. This section is the
|
||||
design of the one form that replaces them; [design 32](32-what-a-module-declares.md) § 4 says how a manifest asks
|
||||
for it, and [design 55](55-the-knowledge-base.md) § 2.1 is its first ledger.
|
||||
|
||||
**Bus state is a provision of the bus module** (ADR 0595 §1). The **bus module** is the module holding the seat
|
||||
`mesh-broker` (today `nats`). A module asks it for each piece of its **bus state** the way it asks postgres for a
|
||||
database: an entry in `requires`, by its local name, its storage type, its template and the template's options.
|
||||
The bus module creates it; the module receives the names on the bus in its membership; nothing else creates it.
|
||||
|
||||
### 12.1 Three layers
|
||||
|
||||
```
|
||||
layer 3 templates state events work queue log objects ledger
|
||||
│ │ │ │ │ │
|
||||
layer 2 storage types key-value │ │ │ object store ledger
|
||||
built on the bucket │ │ │ │ (a stream and
|
||||
stream │ │ │ │ │ key-value buckets)
|
||||
└──────────┴───────────┴──────────┴──────────┴────────────┘
|
||||
layer 1 base type stream
|
||||
```
|
||||
|
||||
A **storage type** is how the bus keeps the data: the **stream** is the base, and the **key-value bucket**, the
|
||||
**object store** and the **ledger** are built on it, each with calls of its own (ADR 0595 §2). A **configuration**
|
||||
is the settings a storage type is given. A **template** is a storage type with a configuration, predefined and
|
||||
named: six exist, from the uses counted on main (158 state entries, 6 event streams, 4 logs, 3 work queues). An
|
||||
**option** is one setting of a template a module may change, within its bound. A module gives a configuration of
|
||||
its own only where no template fits: the bare stream, or a ledger whose history expires.
|
||||
|
||||
**What keeps the data safe is fixed, never an option** (ADR 0595 §3). Each template fixes what happens when it is
|
||||
full, and the judge refuses a manifest that gives it:
|
||||
|
||||
| Template | On | Fixed | Options, their bounds (default) |
|
||||
|---|---|---|---|
|
||||
| state | key-value | refuses when full; a value at most 256 KiB; the bucket at most 64 MiB; one copy, on disk | past values 1 to 64 (1), 0 refused; time to live (none); a lifetime per key; one bucket per machine |
|
||||
| events | stream | drops the oldest when full; the module's own subjects | age 1 to 30 days (7); cap 1 to 8192 MiB (1024); a grant (ADR 0363 §4) |
|
||||
| work queue | stream | removed once taken; refuses when full; one taker; no grant | deliveries 1 to 10; duplicate detection up to its age; cap; age |
|
||||
| log | stream | refuses when full; never rewritten; an entry at most 256 KiB; one subject per key | cap 1 to 8192 MiB (1024); age 1 to 3650 days (none) |
|
||||
| objects | object store | refuses when full; kept for good | cap 1 to 8192 MiB, required; a grant |
|
||||
| ledger | ledger | as log, plus its type and a snapshot per instance; its state and index buckets as state | cap; keeping days only in a ledger's own configuration |
|
||||
| (none: a bare stream) | stream | the module's own subjects; within the mesh's bounds | its own configuration, which must give "when full" (refuse or drop the oldest), a cap of 1 to 8192 MiB and an age of 1 to 3650 days or none, and may give a grant; the judge refuses one missing any of the three |
|
||||
|
||||
The template log is the **module log** of ADR 0297. The mesh, never the module, names the bus objects and their
|
||||
subjects (ADR 0201 §4): `<module>_<local name>`, and a local name holding the separator is refused so no two
|
||||
modules' names meet.
|
||||
|
||||
### 12.2 Who creates it, and how a module receives it
|
||||
|
||||
```
|
||||
indexer's judge ──verdict: accepted, with the module's bus-state entries whole──▶ bus module
|
||||
│ creates each entry
|
||||
▼
|
||||
grants part ◀──────────────── created (module, local name, bus name, template, settings)
|
||||
│ writes the binding into the module's membership
|
||||
▼
|
||||
the tool runner reads the membership ──▶ the module's calls reach its own entries, and only those
|
||||
```
|
||||
|
||||
- **The trigger is the judge's verdict** (ADR 0595 §4), heard as an event that carries the module's entries whole,
|
||||
so the bus module never reads the indexer's state and never polls. It hears the verdicts on a durable consumer, so
|
||||
one given while it was down is heard when it is back, and at start it runs `check` over every accepted manifest,
|
||||
creating what is missing. The judge checks every entry once, at the merge
|
||||
check and at take-in (ADR 0578; ADR 0595 §8).
|
||||
- **The bus module has a process of its own** beside its tool bundle, with a declared health check. Its credential
|
||||
creates streams and buckets under modules' names only, and deletes only through `drop` and `recreate`. Its health
|
||||
turns unhealthy, naming the cause, when an accepted verdict has waited on its durable consumer, unanswered, for
|
||||
more than three minutes, or when its last pass over fill, which runs every minute, is more than three minutes old, so the only guard against a store filling never stops without a sound; another module's entry at 75 percent
|
||||
raises that module's condition and leaves the bus module healthy.
|
||||
- **The binding travels on `created`** (ADR 0595 §1, §7). The grants part, which issues every membership, hears it
|
||||
on a durable consumer and writes the binding (the names on the bus) into the module's membership, kept in its own
|
||||
state `memberships`, so no part writes another's state and a binding outlives the 30 days its event is kept.
|
||||
A module whose binding is missing at start fails loudly, naming the entry; it never starts blind. A `refused`
|
||||
create hands no binding. **A refused or overdue creation is loud**: overdue means no `created` or `refused` within
|
||||
five minutes of an accepted verdict that asks for the entry. The condition is raised by the watchman, which today raises the judge's
|
||||
refusals as findings and gains, through its `consumes`, the accepted verdicts and the seat `mesh-broker`'s `created`
|
||||
and `refused`; never by the bus module, which cannot raise it while it is down; it names the module
|
||||
and the entry; a later `created` clears it, and so does a later accepted verdict of that module that no longer asks
|
||||
for the entry.
|
||||
- **Only the owner reaches it** (ADR 0596 §2). The membership grants the owner's account its own entries and no
|
||||
other module's: the new form has no readers. What another module needs of that data it asks through the owner's
|
||||
tools or seat verbs, and what the owner lets others hear is its declared events, subscribed to through
|
||||
`consumes` (ADR 0363, unchanged). A part watches only its own state, and hears another's events (ADR 0596 §4).
|
||||
|
||||
### 12.3 The seat's verbs, events and fill
|
||||
|
||||
The seat `mesh-broker`, until now a seat with no verbs, gains these (ADR 0595 §5, §6). Where the seat's definition
|
||||
lives, and so the exact form of each verb's arguments, waits for the research effort on how seats are defined,
|
||||
tracked on issue 1966; the build's third step does not start before it.
|
||||
|
||||
| Verb | Does | Who may call it |
|
||||
|---|---|---|
|
||||
| `list` | every entry, its template, its settings, its fill against its cap | any caller whose role grants it (ADR 0386) |
|
||||
| `show` | one entry, the same | the same |
|
||||
| `check` | compares what the bus holds with what was asked, entry by entry | the same |
|
||||
| `orphans` | entries the bus holds that no manifest asks for; never one whose manifest the judge refused or outgrew, nor one of the foundation's old sections | the same |
|
||||
| `create` | creates one entry now | the bus module's own process only |
|
||||
| `apply` | applies a difference that only grows: a larger cap, a longer age | the bus module's own process only |
|
||||
| `recreate` | rebuilds an entry whose settings differ in a way that cannot grow, from its data where the storage type allows | the bus module's process, on the operator's warrant |
|
||||
| `drop` | removes one orphan | the bus module's process, on the operator's warrant |
|
||||
|
||||
`create`, `apply`, `recreate` and `drop` are granted to the bus module's own credential alone, so a module calling
|
||||
them is refused by the bus. A person asks for them through the bus module's tools, and `recreate` and `drop` act
|
||||
only on the operator's warrant (ADR 0259 §6), asked through the operator channel. Before `recreate` or `drop` acts,
|
||||
the bus module takes a stream snapshot of the entry. A drifted entry is said, never quietly fixed: only a difference
|
||||
that grows is applied without a person.
|
||||
|
||||
**Events**: `created`, `refused` (with why), `drifted` and `drift-cleared`, `full` and `full-cleared`, `orphaned`
|
||||
(cleared by a `drop`, or by a manifest asking for the entry again, which emits `created` for it);
|
||||
each carries the module, the local name, the bus name, the template, and the settings asked against those held. The
|
||||
bus module's own kept data stays on the old sections (ADR 0595 §10), because it cannot wait for its own process to
|
||||
create it: it declares this stream in its `streams` section, kept 30 days (ADR 0363's bound), created by the grants
|
||||
part as today, with a cap of 256 MiB; it drops its oldest when full, which loses no live state, because every drift,
|
||||
orphan and fill stands as a condition until it clears. Thirty days, because a drift or an orphan is read back days later by a person; the conditions hold the
|
||||
live state. The tool `history` of one entry reads them back: every event the bus module said of it, in order. This
|
||||
settles where they are kept, which ADR 0595 §12 left to the design.
|
||||
|
||||
**Fill is the bus module's one periodic read** (ADR 0595 §6): every minute, for every entry that refuses when full,
|
||||
because the bus says nothing as a store fills. The condition `full` is raised from 75 percent of the cap and cleared
|
||||
below it. The grants part's `mesh-grants.history-full` moves to it: the new condition is raised first, then the old
|
||||
key cleared. A bare stream that refuses when full is read the same way. **The conditions**, each naming the module
|
||||
and the entry: `full` (cleared below 75 percent), `drifted` (cleared by `drift-cleared`), `orphaned` (cleared by a
|
||||
`drop` or by a manifest asking for it again) and a refused create (cleared by a later `created`).
|
||||
|
||||
**Its tools** (tools-are-how-a-module-is-spoken-to): the four reading verbs, marked reads, answer every entry the
|
||||
bus module created and its fill, and `history` its events for one entry; `create` and `apply`, marked acts, and
|
||||
`recreate` and `drop`, marked dangerous and so granted only by name (ADR 0386 §6, ADR 0387), are its tools for a
|
||||
person. Such a tool, in the bus module's bundle, holds no grant of those verbs' subjects, and neither does the tool
|
||||
runner's account: it hands the request to the process on the process's own tool subject, carrying the caller as
|
||||
the token the identity seat signed (ADR 0386 §8), whose signature the process checks, so no account granted that
|
||||
subject can name another caller; the process acts only for a caller whose role grants that tool, for `recreate` and `drop` only once
|
||||
the operator's warrant is held, and answers what it did. The bus seat's `apply` (a difference that grows) and the
|
||||
ledger's `apply` (§ 12.4) are two verbs on two subjects, granted apart; this design writes them qualified. Its health
|
||||
names the process's last pass, the entries it could not create, and the entries over 75 percent.
|
||||
|
||||
### 12.4 The ledger
|
||||
|
||||
A **ledger** is a storage type whose state is derived from its history (ADR 0597). It is built on a stream and
|
||||
key-value buckets, and asked for with the template ledger.
|
||||
|
||||
```
|
||||
module ──apply(instance, operation)──▶ tool runner on its own machine
|
||||
│ 1 checks the operation against the type
|
||||
│ 2 reads the history's last sequence for the instance; replays if the state is behind
|
||||
│ 3 appends to the history, that sequence expected ──▶ history (stream, a subject per instance)
|
||||
│ 4 derives the state by the operation's effect ──▶ state (key-value, updated at the revision read)
|
||||
│ 5 writes the instance's snapshot ──▶ snapshot subject (latest only, never expires)
|
||||
▼
|
||||
answers: taken, with its sequence (and "state behind" when the state bucket is full)
|
||||
module's process: a watcher keeps each declared index (a second key-value bucket) from the history
|
||||
```
|
||||
|
||||
- **A type** is a schema and the operations allowed on it; an **instance** of a type is named by a path
|
||||
(`listing/a17`, `decision/0290`); every change is an **operation**, kept in the instance's history.
|
||||
- **A ledger effect** is what an operation does to the state, one of a fixed set: set fields, set an entry of a
|
||||
map, append to a list, remove (ADR 0597 §2). An effect copies only the fields its type names, and may add the
|
||||
operation's sequence and author. Where a type pairs a text field with its digest, `apply` refuses an operation
|
||||
whose digest does not match. A module's own rules are checked by the module before it calls `apply`, never by the
|
||||
effects. Only the owning module calls `apply` on its ledger, for every effect, remove among them; a person
|
||||
removes through the owner's tools. This settles who may call the remove effect, which ADR 0597 §13 left to the
|
||||
design.
|
||||
- **One writer wins, and the state never goes back** (§5): the expected sequence makes the bus refuse a racing
|
||||
writer; the state is written as an update of the revision read, never a plain put; when that update loses to
|
||||
another writer, `apply` answers success with its operation's sequence and re-reads, never appending again.
|
||||
- **A full state bucket loses no operation** (§6): `apply` answers that the operation is taken, with its sequence,
|
||||
and that the state is behind because the bucket is full; the next `apply` after room is made catches it up.
|
||||
- **The author is proven** (§7): `apply` stamps the owning module's authenticated account and the account that
|
||||
called the owner's tool. The owner passes that author on by naming the tool call it serves: the tool runner, which
|
||||
served that call and proved its caller, stamps the caller it proved, so the module cannot set the author to words of
|
||||
its own; an `apply` naming no call in flight carries the owner's account alone, and one naming another module's
|
||||
call is refused. A copy of old records (design 55 § 8.1) is stamped with the owner's account and keeps the old
|
||||
author as a separate field marked imported, never as the proven author.
|
||||
- **Every instance has a snapshot** (§8): the latest state on a subject of its own, never expiring, written by every
|
||||
`apply`. It makes a kept ledger's rebuild fast and an expiring ledger's possible; a repair that finds it behind
|
||||
the state rewrites it.
|
||||
- **Only the tool runner's ledger credential publishes on a ledger's history** (§9): one per module, held by the
|
||||
tool runner and never by the module's own code, whose account may read its ledger and not publish on it. The grants
|
||||
part issues it with the module's membership on that machine, scoped to the subjects of that module's ledger
|
||||
histories and snapshots, and removes it when the module is unassigned from the machine. Only the
|
||||
owner's account reads the history, the snapshot and the state.
|
||||
- **An index** is a second key-value bucket kept by a watcher in the owning module's process, rebuildable from the
|
||||
history; it raises a condition when it lags behind (§4).
|
||||
|
||||
### 12.5 The calls, and where they live
|
||||
|
||||
The new calls live in a small mesh-sdk package of their own, served by the tool runner and imported only by the
|
||||
modules that use them; the module library, which 112 modules import, does not change (ADR 0595 §9). They are the
|
||||
object store's (put, get, list, delete, info, watch); a stream's read-back and fill; a work queue's size and dead
|
||||
letters; and the ledger's: `apply`, `get`, `history`, `watch`, `query`, and its repairs `rebuild` (the state from
|
||||
the history or the snapshot), `reindex`, `snapshot` and `lag` (ADR 0597 §10). The tool runner serves the repairs as
|
||||
tools of every module that owns a ledger, limited to its own ledgers, so no owner declares them and the next ledger's
|
||||
owner has them on its first day. mesh-sdk's catalog and membership
|
||||
packages change once for the typed `requires` entry and the binding, rebuilding about 30 modules.
|
||||
|
||||
### 12.6 The move, and the order of the build
|
||||
|
||||
**The build, each step undoable on its own** (ADR 0595 §10): (1) mesh-sdk's typed `requires`, binding and calls
|
||||
package; (2) the judge's rules; (3) the bus module's process, verbs and events, creating only for the new form, with the grants part's writing of
|
||||
the binding on `created`; (4) the tool runner's calls, the ledger's `apply` and its ledger credential among them; (5)
|
||||
modules move, each owner first. Each undo: undoing step 2 refuses the new form again, which no module uses before
|
||||
step 5; undoing step 3 leaves what it created in place, and a module moved after it fails loudly on its missing
|
||||
binding; undoing step 4 leaves a moved module without its calls, which the placement holds off that machine. The
|
||||
placement holds a moved module off a machine whose tool runner does not serve the typed binding and the calls the
|
||||
module asks for (a ledger's `apply` among them), and says so; where the placement reads what a tool runner serves is
|
||||
left to the build of step 4 and tracked on issue 1966.
|
||||
|
||||
**A module moves when it is next touched** (ADR 0595 §11), keeping its local names and carrying its old settings
|
||||
as options, so its bus objects and data stay and no drift is raised. It asks for each entry once: an old section
|
||||
and the new form are never both used for one local name. During the move the grants part keeps creating the old
|
||||
sections' entries, so each entry has one creator. The indexer counts the modules on each old section, a tool reads
|
||||
the count, and the judge refuses a section once its count is 0. The foundation's own kept data (the bus module's,
|
||||
the grants part's and the indexer's) stays on the old sections for good and is not counted: those modules cannot
|
||||
wait for a process that needs them first.
|
||||
|
||||
**Readers move after their owners** (ADR 0596 §3). Today 195 `reads` entries in 37 modules read another module's
|
||||
bus state, the indexer's 36 times and the assigner's 30. An owner declares the events and gains the tools its
|
||||
readers need before any reader moves: the judge refuses a moved module's subscription to an event its owner does
|
||||
not declare, and the review of a reader's move reads that the owner's tool is on main and delivered. These moves
|
||||
are tracked by issue 1966.
|
||||
|
||||
### 12.7 How it is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| A manifest's bus state is valid, once | the judge's unit tests, each refusal naming the field: an unknown template, an option outside its bound, past values 0, "when full" given, a fixed setting given, a name used twice across storage types, two modules' bus names alike, a `reads` in the new form, an effect outside the fixed set (ADR 0595 §8, ADR 0596 §2, ADR 0597 §2) |
|
||||
| The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused |
|
||||
| The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at 75 percent and cleared below |
|
||||
| Drift and orphans, live | live, made only by a manifest change: a manifest that shrinks a setting raises `drifted`; the conditions clear as above. Nothing is filled on the live bus, and no orphan is made there |
|
||||
| Overdue and stopped are loud | live, carried by the change of the first module that moves in step 5, never by a merge made for the test alone: with the bus module's process stopped, an accepted manifest asking for an entry raises the overdue condition within five minutes, from the watchman, and a later `created` clears it; the bus module's health turns unhealthy when its pass is more than three minutes old |
|
||||
| A person's tools reach the process, and only by role | live: a role without the dangerous grant is refused `drop`'s tool; the tool runner's account holds no grant of `create`, the seat's `apply`, `recreate` or `drop` |
|
||||
| The author is the proven caller | the ledger's integration test: an `apply` naming another module's tool call is refused; the author stamped is the caller the tool runner proved, whatever the module passed |
|
||||
| Only the owner reaches its data | live: another module's account reading an entry in the new form, a ledger's history, snapshot or state among them, is refused; a raw publish on a ledger's history is refused |
|
||||
| The ledger keeps its promises | one integration test against the real bus (ADR 0597's How it is checked): two writers race and one is refused; two interleaved applies leave the state at the higher sequence; a write cut between history and state is repaired; a full state bucket answers taken with its sequence and catches up; an idle instance whose operations expired rebuilds from its snapshot; `rebuild`, `reindex` and `lag` repair by hand; a forged author is refused; every `apply` leaves the snapshot equal to the state; an index behind raises its condition and `reindex` clears it |
|
||||
| Only the owner applies | the ledger's integration test: another module's ledger `apply` on the knowledge base's ledger is refused |
|
||||
| A bare stream is complete | the judge's unit test: a bare stream missing "when full", a cap or an age is refused, naming the field |
|
||||
| The calls rebuild only their users | a read of the delivery plan: only the importers of the calls package rebuild when it changes |
|
||||
| The move keeps data | live: a module moved from `state` keeps its bucket and data and raises no drift; a new entry in an old section is still created by the grants part; the count falls by one; at 0 a manifest using the section is refused and the foundation's are still accepted; a moved module is held off a machine whose tool runner does not read the typed binding |
|
||||
|
||||
**The test budget** (50 integration and 500 unit tests per repository): the design adds one integration test in the
|
||||
nats module's repository and one in mesh-tools (the ledger), and table cases, not new functions, to the judge's unit
|
||||
tests in mesh-delivery and to mesh-sdk's decoding tests. Each repository's count against the budget is read in the
|
||||
prepare step of issue 1966, and a repository over it merges tests to stay within it.
|
||||
|
||||
**Acceptance.** The operator asks the bus module's `list` and sees every module's kept data with its template, its
|
||||
settings and its fill; a module that moved keeps its data; a drift or an orphan reaches the operator as a condition
|
||||
and is changed only on their word; and the knowledge base keeps its records as a ledger (design 55 § 10).
|
||||
|
||||
## Reviewed by architectural domain
|
||||
|
||||
The amendment of 2026-11-03 to ADR 0595, ADR 0596 and ADR 0597 (designs 25 § 12, 32 § 4 and 55 together) was
|
||||
reviewed by the four reviewers by architectural domain side by side (playbook 01), over three rounds, and the lead
|
||||
ruled on every finding. What was found, and what answers it:
|
||||
|
||||
| Domain | Found | Answered by |
|
||||
|---|---|---|
|
||||
| security | the author could be words the module chose; a person's dangerous tools had no role, mark or path to the process; the caller travelled as a plain field | design 25 § 12.3 (marks, the process's own tool subject, the identity seat's signed token), § 12.4 (the tool runner stamps the caller it proved; the move's authors marked imported) |
|
||||
| seams | the bus module asked for its own events in the new form, which it would have to create itself (ADR 0595 §10); a person's tool had no join to the process; a verdict was written twice with no order | design 25 § 12.3 (its events on its old `streams` section; the process's own tool subject); design 55 § 2.1 (the `review` operation first, `reviews` caught up from it) |
|
||||
| running qualities | overdue creation was undefined and raised by nobody; the database's rollback write lived in memory; a forward delivery after a rollback could miss acceptances and verdicts; no size estimate of the heads | design 25 § 12.2 (five minutes, the watchman, durable consumers, `check` at start); design 55 § 8.1 step 6 (`database.sequence`, the check at every start before the drop), § 2.1 (the heads' estimate) |
|
||||
| health | the bus module's health would have fired on a quiet mesh; conditions had no clearing; `database-behind` had no clearing | design 25 § 12.2 and § 12.3 (each condition with what clears it); design 55 § 8.1 step 6 |
|
||||
| events and logs | an event lost when the module stopped after `apply`; the bus module's events had no reader | design 55 § 1 (each event sent with its operation's sequence as id, the state `sent`); design 25 § 12.3 (`history` of one entry) |
|
||||
| testing | the bus module had no test that starts it whole; live tests would have filled the live bus; the budget was not counted | design 25 § 12.7 (the nats module's integration test, live drift only by a manifest change, the budget paragraph); design 55 § 10 |
|
||||
| upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 |
|
||||
| tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 |
|
||||
|
||||
**Left to the operator**, because each changes an accepted record: a margin between raising and clearing the
|
||||
condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of
|
||||
its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate`
|
||||
as well as `drop`. Each is noted on issue 1966.
|
||||
|
||||
@@ -12,8 +12,11 @@ code:
|
||||
- mesh-tools src/main.ts
|
||||
- mesh-catalog modules/mesh-catalog
|
||||
- mesh-tools node-tools/internal/runtime (a module's state, ADR 0201)
|
||||
updated: 2026-10-12
|
||||
- mesh-sdk (the typed bus-state entry in `requires` and its binding, ADR 0595)
|
||||
updated: 2026-11-03
|
||||
decisions:
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0307"
|
||||
- "ADR 0240"
|
||||
- "ADR 0233"
|
||||
@@ -227,7 +230,7 @@ service" versus "one worker per machine".
|
||||
| credential | sealed, per consumer | none | none | none | none |
|
||||
| reply | — | none | none, or an event later | a report | awaited |
|
||||
| retention | — | age and size | work queue, explicit ack | **last per subject** | none |
|
||||
| declared | `provides`/`requires` | `emits`/`consumes` | seat `accepts` / `uses` | the mesh's own; a module's `state` / `reads` | `serves` |
|
||||
| declared | `provides`/`requires` | `emits`/`consumes` | seat `accepts` / `uses` | the mesh's own; a module's bus state in `requires` (ADR 0595), the old `state` / `reads` until it moves | `serves` |
|
||||
|
||||
**Job** is the one ADR 0041 had no room
|
||||
for. Its table has events at 1:many and provisions at 1:1; a module submitting work to a service
|
||||
@@ -278,6 +281,36 @@ module's assignment** — what a module stored is data
|
||||
(ADR 0030) — and one whose
|
||||
declaration is gone is reported, never removed by the mesh.
|
||||
|
||||
**A module asks for its bus state in `requires`, and reads no other module's.** *Amended 2026-11-03, ADR 0595
|
||||
and ADR 0596, from research 082.* The `state` and `reads` of the two paragraphs above, and the sections `logs`,
|
||||
`streams` and `objects` beside them, are four vocabularies for one need: what a module keeps on the bus. They are
|
||||
replaced by one form, a **provision** of the bus module, as a database is postgres's:
|
||||
|
||||
- **An entry of `requires`, in its typed long form** (ADR 0595 §1): the local name, the storage type, the template
|
||||
(state, events, work queue, log, objects, ledger) and the template's options, within their bounds; a ledger also
|
||||
names its type (its schema, its operations each with its ledger effect, its indexes). The templates, their fixed
|
||||
settings and their options are [design 25](25-the-bus-on-nats.md) § 12.1. mesh-sdk's catalog package carries the
|
||||
entry's type and its strict decoding; every rule that judges it lives in the indexer's judge (ADR 0595 §8).
|
||||
- **The binding is what the module receives**: the names on the bus for each local name, written into its
|
||||
membership by the grants part once the bus module says `created` (design 25 § 12.2). The module still names only
|
||||
locally (§ 1). A module whose binding is missing at start fails loudly, naming the entry.
|
||||
- **One name, one form.** A module asks for each entry once: an old section and the new form are never both used for
|
||||
one local name, and one name serves one storage type only.
|
||||
- **No readers.** The new form has no `reads`, and the judge refuses one (ADR 0596 §2). Only the owner's account
|
||||
reaches its bus state. **A module's events are subscribed to; its data is asked for**: what another module needs
|
||||
of a module's kept data it asks through that module's tools or seat verbs (§ 4's tool relationship, `serves`),
|
||||
and what a module lets others hear is its declared events, heard through `consumes` (ADR 0363, unchanged; ADR 0596
|
||||
§1). A bucket's layout is never a contract another module depends on; a tool or a declared event is.
|
||||
- **The move is a module at a time** (ADR 0595 §11, ADR 0596 §3), each when it is next touched, keeping its local
|
||||
names and its old settings as options so its data stays. A reader moves after its owner: the owner declares the
|
||||
event or gains the tool its readers need first, and the judge refuses a moved module's subscription to an event
|
||||
its owner does not declare. An old section is refused once no module but the foundation's uses it.
|
||||
|
||||
What follows for the paragraphs above: for a module that has moved, ADR 0201 §2 (another module's state read by
|
||||
naming it in `reads`) no longer holds, and the bucket "every instance of the owner writes; a reader reads and
|
||||
watches" becomes "every instance of the owner writes and reads". The runtime's `get`, `put`, `delete`, `keys`,
|
||||
`watch` and `history` stay the calls of the template state.
|
||||
|
||||
**A module declares the data it holds.** *Added 2026-10-06,
|
||||
ADR 0233.*
|
||||
State on the bus is one kind of data; the rest lives on a machine's disks or with a provider, and the
|
||||
@@ -602,6 +635,34 @@ billing existing under that name.
|
||||
- **A module reaches only the state it declared.** The composer's test: an owner's runtime may write
|
||||
its buckets, a reader's may only read, and nothing else is granted; the runtime's test over a real
|
||||
bus: a name not issued and a reader's write are refused with the reason.
|
||||
- **Bus state is asked for once, in one form, and read by its owner alone** (ADR 0595 §8, ADR 0596). The judge's
|
||||
unit tests: a `reads` in the new form, a local name used twice across storage types or in an old section and the
|
||||
new form, an unknown template and an option outside its bound are each refused, naming the field. Live: a moved
|
||||
module receives its binding after `created`; another module's account reading its entries is refused; a moved
|
||||
reader hears its owner's declared event and asks its owner's tool. The rest is [design 25](25-the-bus-on-nats.md)
|
||||
§ 12.7.
|
||||
- **State is current at once.** The runtime's test over a real bus: a watch hands the current values
|
||||
without deletions, then an end-of-current marker, then changes. Live: a machine assigned after a put
|
||||
reads it at start.
|
||||
|
||||
## Reviewed by architectural domain
|
||||
|
||||
The amendment of 2026-11-03 to ADR 0595, ADR 0596 and ADR 0597 (designs 25 § 12, 32 § 4 and 55 together) was
|
||||
reviewed by the four reviewers by architectural domain side by side (playbook 01), over three rounds, and the lead
|
||||
ruled on every finding. What was found, and what answers it:
|
||||
|
||||
| Domain | Found | Answered by |
|
||||
|---|---|---|
|
||||
| security | the author could be words the module chose; a person's dangerous tools had no role, mark or path to the process; the caller travelled as a plain field | design 25 § 12.3 (marks, the process's own tool subject, the identity seat's signed token), § 12.4 (the tool runner stamps the caller it proved; the move's authors marked imported) |
|
||||
| seams | the bus module asked for its own events in the new form, which it would have to create itself (ADR 0595 §10); a person's tool had no join to the process; a verdict was written twice with no order | design 25 § 12.3 (its events on its old `streams` section; the process's own tool subject); design 55 § 2.1 (the `review` operation first, `reviews` caught up from it) |
|
||||
| running qualities | overdue creation was undefined and raised by nobody; the database's rollback write lived in memory; a forward delivery after a rollback could miss acceptances and verdicts; no size estimate of the heads | design 25 § 12.2 (five minutes, the watchman, durable consumers, `check` at start); design 55 § 8.1 step 6 (`database.sequence`, the check at every start before the drop), § 2.1 (the heads' estimate) |
|
||||
| health | the bus module's health would have fired on a quiet mesh; conditions had no clearing; `database-behind` had no clearing | design 25 § 12.2 and § 12.3 (each condition with what clears it); design 55 § 8.1 step 6 |
|
||||
| events and logs | an event lost when the module stopped after `apply`; the bus module's events had no reader | design 55 § 1 (each event sent with its operation's sequence as id, the state `sent`); design 25 § 12.3 (`history` of one entry) |
|
||||
| testing | the bus module had no test that starts it whole; live tests would have filled the live bus; the budget was not counted | design 25 § 12.7 (the nats module's integration test, live drift only by a manifest change, the budget paragraph); design 55 § 10 |
|
||||
| upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 |
|
||||
| tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 |
|
||||
|
||||
**Left to the operator**, because each changes an accepted record: a margin between raising and clearing the
|
||||
condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of
|
||||
its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate`
|
||||
as well as `drop`. Each is noted on issue 1966.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog, hq]
|
||||
updated: 2026-11-02
|
||||
updated: 2026-11-03
|
||||
decisions:
|
||||
- "ADR 0290"
|
||||
- "ADR 0321"
|
||||
@@ -10,6 +10,9 @@ decisions:
|
||||
- "ADR 0424"
|
||||
- "ADR 0592"
|
||||
- "ADR 0593"
|
||||
- "ADR 0595"
|
||||
- "ADR 0596"
|
||||
- "ADR 0597"
|
||||
---
|
||||
|
||||
# 55 — The knowledge base
|
||||
@@ -23,14 +26,26 @@ written one section at a time in a draft, and reviewed one section at a time. A
|
||||
never a file. § 1, § 2, § 4, § 5, § 8.1 and the fifth slice of § 9 say how; until it is built, the module keeps its
|
||||
records in the store as before.
|
||||
|
||||
**Amended, 2026-11-03** (ADR 0597, ADR 0595 and ADR 0596, from research 082; the operator, 2026-11-02 21:53: "the
|
||||
writes are the record"). The knowledge base is the first **ledger**: a record is an instance of a ledger type, every
|
||||
change to it is an operation kept in the ledger's history, and its head is the ledger's state, derived from those
|
||||
operations by the tool runner's `apply` and never written by the module. A section's text lives only in the
|
||||
`write-section` operation that wrote it, with its digest; the head names each section by its title, digest and that
|
||||
operation's sequence. So the object bucket `sections`, the state `names`, the stream `versions` and the module's own
|
||||
commit rule fall away (ADR 0597 §12, changing ADR 0592 §1 to §12, §17, §25, §32, §33). Its kept data is asked for in
|
||||
`requires` by template (ADR 0595), and no other module reads it: another module asks the knowledge base's tools or
|
||||
hears its events (ADR 0596). The ledger itself, the same for every module, is designed in
|
||||
[design 25](25-the-bus-on-nats.md) § 12; how a module asks for it, in [design 32](32-what-a-module-declares.md) § 4.
|
||||
§ 1, § 1.1, § 2.1, § 4, § 8.1, § 9 and § 10 say what changes here.
|
||||
|
||||
**The mesh keeps its decision records and research efforts whole in a module of its own, `mesh-knowledge`, one
|
||||
instance on the control-node, holding the seat `knowledge-base`: each record's number, status, topic, typed links,
|
||||
its clauses with their own status, and its reasoning in titled sections, with every version kept, all of it on the
|
||||
bus. A record is written section by section, checked when it is submitted, reviewed section by section by the
|
||||
knowledge base's own judgement and accepted there; the operator's Approve is asked when a record needs it. Agents
|
||||
ask what holds, and why, in one bounded call.**
|
||||
(ADR 0290 and ADR 0592,
|
||||
from research 045.)
|
||||
(ADR 0290, ADR 0592 and ADR 0597,
|
||||
from research 045 and research 082.)
|
||||
|
||||
A **clause** is one numbered statement a decision record makes, with its own status. A **section** is one titled
|
||||
part of a record's reasoning. The **knowledge base** is the module that holds the records; the seat it holds is
|
||||
@@ -42,7 +57,7 @@ read the reasoning behind each. A session writes a research effort of more than
|
||||
session on another node takes the draft over and submits it, and every section is judged.
|
||||
|
||||
This document designs the first slice in detail, tracked by issue 425, and the fifth, the records on the bus
|
||||
(ADR 0592). The slices after the first are in
|
||||
(ADR 0592), kept as a ledger (ADR 0597). The slices after the first are in
|
||||
[§ 9](#9-the-slices-after-the-first), which also designs the third: designs and the glossary's entries become records,
|
||||
and the knowledge base works out each design's state (ADR 0423, ADR 0424).
|
||||
|
||||
@@ -53,22 +68,28 @@ and the knowledge base works out each design's state (ADR 0423, ADR 0424).
|
||||
|
||||
mesh-knowledge (seat knowledge-base, held once for the mesh)
|
||||
├ its process, launched by the node runtime
|
||||
├ on the bus, written by it alone (§ 2)
|
||||
│ state heads one key per record: its fields, clauses, links, sections as title and digest
|
||||
│ state drafts one key per record being written: its author and its sections as title and digest
|
||||
├ on the bus, asked for in requires by template (ADR 0595), read by it alone (§ 2)
|
||||
│ ledger records one instance per record (decision/0290, research/082):
|
||||
│ history every operation, in order, each section's text in its write-section
|
||||
│ state the head, derived by apply: fields, clauses, links, sections as
|
||||
│ title, digest and sequence
|
||||
│ snapshot the latest head per record, never expiring
|
||||
│ state drafts one key per record being written: its author and its sections as title,
|
||||
│ digest and the sequence of their write-section
|
||||
│ state asked one key per part asked of the judge and not yet answered: ticket, node, when
|
||||
│ state names per digest: how many versions and drafts name it, and since when none has
|
||||
│ state findings the whole-set findings standing now, and the told marks (told.<topic>)
|
||||
│ stream versions one message per version: who, when, why, its mark, the head as it stood
|
||||
│ state sent per record, the last operation whose event was sent
|
||||
│ stream reviews one message per part reviewed: the digest judged, the verdict, the findings
|
||||
│ objects sections every section's body, once, named by its sha256 digest
|
||||
├ in its memory the search index, built from the bus at start and kept by watching heads
|
||||
├ written through the tool runner's apply on the control-node, the only publisher on the
|
||||
│ ledger's history (ADR 0597 §3, §9)
|
||||
├ in its memory the search index, built from the bus at start and kept by watching the
|
||||
│ ledger's state
|
||||
├ verbs ask, get, list, holds, links, history, findings, progress,
|
||||
│ reserve, open, write, remove, order, submit, drop, take-over, propose,
|
||||
│ withdraw, retract, import, return
|
||||
│ served by the control-node's tool runner
|
||||
├ events reserved, opened, written, removed, ordered, submitted, dropped, taken-over, proposed,
|
||||
│ part-reviewed, reviewed, accepted, changed, swept, moved, checked, formed,
|
||||
│ part-reviewed, reviewed, accepted, changed, moved, checked, formed,
|
||||
│ imported, drafted, confirmed, returned
|
||||
├ consumes the router's warrants: the operator's Approve for a record that needs it (§ 5)
|
||||
├ consumes claude-code's judged: the judge's answer to a part (§ 5)
|
||||
@@ -90,32 +111,56 @@ The module is written in Go against the SDK. It holds no checkout of this reposi
|
||||
the records module, as the issue tracker's import does. Until the move of § 8.1, its records are in a database in
|
||||
the store; from the move on, it requires no database (ADR 0592 §1, changing ADR 0290 §7).
|
||||
|
||||
**A write is whole or refused, and `submit` commits when its version is stored** (ADR 0592 §10). Every change to
|
||||
`heads`, `drafts`, `asked`, `names` and `findings` is a compare-and-set on the key's revision, retried at most three
|
||||
times and then refused with the reason. The commit point of `submit` is the version message stored on `versions`: a
|
||||
failure before it is stored refuses the call, and nothing is taken. Once it is stored, the call answers that the
|
||||
version is taken; the head's update against its revision and the event are then completed at once, retried, and at
|
||||
the next start for any head behind its last stored version. So nothing an author was told was refused takes effect
|
||||
later. A reservation is a head created only if the key is absent, so two sessions reserving at once get two numbers
|
||||
or one refusal.
|
||||
**A record is changed by an operation, and the head follows from it** (ADR 0597 §1 to §8). Every change to a record
|
||||
is one operation the module hands to `apply`: among them `reserve`, `propose`, `write-section`, `submit`,
|
||||
`take-over`, `review` (a part's verdict), `ask` and `warrant` (the operator's ask and answer), `accept` and
|
||||
`withdraw`. The module checks its own rules first (an `accept` needs the operator's Approve where the record needs
|
||||
it, § 5; only the author writes a draft, § 4), then calls `apply`, which checks the operation against the record's
|
||||
type, appends it to the instance's history with the sequence it expects, derives the head by the operation's effect,
|
||||
and writes the head and its snapshot. The author is the account the bus proved for the caller of the module's verb:
|
||||
the module names the tool call it serves, and the tool runner stamps the caller it proved for it, never a name the
|
||||
caller or the module gave (ADR 0597 §7; [design 25](25-the-bus-on-nats.md) § 12.4).
|
||||
|
||||
**Every operation's event is sent once, after a restart too.** `apply` emits nothing; the module sends the event of
|
||||
each operation it applied (`submitted`, `reviewed` and the rest) with the operation's sequence as the message's
|
||||
id, so the bus drops a second send within its duplicate window, and keeps the last sequence it sent per record in a
|
||||
state of its own, `sent` (one key `<kind>.<number>`). The duplicate window of its event stream and of `reviews` is
|
||||
one day, far longer than a start (40 seconds at most, § 10); a module stopped between a send and its `sent` key for
|
||||
longer than a day sends that one event again, which a consumer already takes as a bus delivered at least once. A
|
||||
missed send is never chosen over a second one. At start it sends the events of every later operation in each record's history, so a
|
||||
module stopped between `apply` and its event loses none, and a consumer of `mesh-knowledge.reviewed` never waits for
|
||||
one that was never sent.
|
||||
|
||||
**A write is whole or refused, and an operation commits when its history takes it** (ADR 0597 §5, §6, changing
|
||||
ADR 0592 §10). The commit point of every change to a record is the operation appended to the ledger's history: a
|
||||
failure before it is appended refuses the call, and nothing is taken; a racing writer is refused by the bus, which
|
||||
expects the history's last sequence for the instance. Once it is appended, the call answers that it is taken, with
|
||||
its sequence; the head is derived by `apply` as an update of the revision it read, and a head behind its history is
|
||||
caught up by the next `apply` (ADR 0597 §6). A full state bucket answers that the operation is taken and the head behind,
|
||||
never that it was refused. So nothing an author was told was refused takes effect later. A reservation is an
|
||||
operation `reserve` on an instance with no history, so two sessions reserving at once get two numbers or one
|
||||
refusal. The module's other state (`drafts`, `asked`, `findings`) keeps the compare-and-set on the key's revision,
|
||||
retried at most three times and then refused with the reason (ADR 0597 §12).
|
||||
|
||||
**The search index** is a full-text index in the module's own memory, written with the pure Go library bleve. At
|
||||
start the module reads every head and the bodies of every current version into it, then keeps it current by watching
|
||||
`heads`, noting the last revision of `heads` it took in. `ask` and `holds` answer from it, ranked by title, clauses
|
||||
start the module reads every head from the ledger's state and the bodies of every current version from their
|
||||
`write-section` operations into it, then keeps it current by watching the ledger's state, noting the last sequence it
|
||||
took in (ADR 0597 §12, changing ADR 0592 §11). The index is the module's own, in its memory; the ledger's declared
|
||||
index (ADR 0597 §4) is not used for it, because a full-text index is not a key-value bucket. `ask` and `holds` answer from it, ranked by title, clauses
|
||||
and sections as before, and refuse with "index not built" while it is built; `get`, `list` and `propose` answer from
|
||||
`heads` at once (ADR 0592 §11). The integration test measures the start; the estimate is 20 to 40 seconds and about
|
||||
the ledger's state at once (ADR 0592 §11). The integration test measures the start; the estimate is 20 to 40 seconds and about
|
||||
100 MB of memory for today's 736 records. Nothing but the module writes it, and nothing outside it reads it.
|
||||
|
||||
### 1.1 Its tools, health and events
|
||||
|
||||
Every piece of state has a tool that reads it, and every act the module does by itself can be done by hand
|
||||
(ADR 0592 §32):
|
||||
(ADR 0592 §32, changed by ADR 0597 §12: the sweep and the digest check become the ledger's repairs):
|
||||
|
||||
| Tool | Reads or does |
|
||||
|---|---|
|
||||
| read a head | one key of `heads`, whole |
|
||||
| read a version | one message of `versions`, with its sections' titles, digests and authors |
|
||||
| read a section | one object of `sections`, by its digest, checked against it |
|
||||
| read a head | one instance's state in the ledger, whole, with the sequence of the last operation it holds |
|
||||
| read a record's history | the instance's operations in order, each with its sequence, author and when; a version is its `submit` |
|
||||
| read a section | one `write-section` operation, by its sequence, its text checked against its digest |
|
||||
| read a draft | one key of `drafts`, with its author and since when |
|
||||
| read a review | the part verdicts of one version, from `reviews` |
|
||||
| in-flight parts | the keys of `asked`: each part waiting on the judge, its ticket and since when; ask one again |
|
||||
@@ -123,20 +168,20 @@ Every piece of state has a tool that reads it, and every act the module does by
|
||||
| fill | each bucket's and stream's size and count against its cap |
|
||||
| rebuild the index | the search index, built again from the bus |
|
||||
| review again | asks one part's review again, for a version still proposed |
|
||||
| sweep | lists the objects no version or draft names and since when; sweeps them now; dry run |
|
||||
| integrity | checks every named digest has its object; restores a body from given text once its digest matches |
|
||||
| the ledger's repairs | the tool runner's `rebuild` (a head from the history or the snapshot), `snapshot` and `lag` (how far each head is behind its history), served for the module's own ledger (ADR 0597 §10) |
|
||||
| integrity | checks every section a head or draft names resolves to a `write-section` whose text hashes to its digest |
|
||||
| form-only | lists, dry-runs and runs the form-only versions of § 2.2 |
|
||||
| move, and check the move | § 8.1, each with a dry run |
|
||||
|
||||
**Health** (ADR 0592 §33) is unhealthy, naming the cause, when a bucket or stream does not answer, when the index is
|
||||
behind the latest revision of `heads`, when a digest some version names has no object (counted, each named), and
|
||||
when a bucket or stream is at its cap. It names the source it serves from (the database or the bus) and the result
|
||||
behind the ledger's state, when a head lags behind its history (the ledger's lag, ADR 0597 §12, changing
|
||||
ADR 0592 §33), and when a bucket or stream is at its cap. It names the source it serves from (the database or the bus) and the result
|
||||
of the move's last check, and counts drafts idle more than a week, records the form-only check refused, and parts
|
||||
waiting more than 30 minutes. A part waiting more than 30 minutes is asked of the operator once through the operator
|
||||
channel, and the ask clears when the part is judged; the module stays healthy meanwhile, because a record waiting on
|
||||
its review is not the module failing (issue 508). The condition at 75 percent of a cap is the grants part's (§ 2.1).
|
||||
its review is not the module failing (issue 508). The condition at 75 percent of a cap is the bus module's (ADR 0595 §6; [design 25](25-the-bus-on-nats.md) § 12).
|
||||
|
||||
**Events** (ADR 0592 §34) are kept 30 days; what a later reader needs is also in the version message. Each carries:
|
||||
**Events** (ADR 0592 §34) are kept 30 days; what a later reader needs is also in the `submit` operation, in the ledger's history. Each carries:
|
||||
|
||||
| Event | Carries |
|
||||
|---|---|
|
||||
@@ -148,7 +193,6 @@ its review is not the module failing (issue 508). The condition at 75 percent of
|
||||
| `taken-over` | the record, the previous author, the new author, the reason |
|
||||
| `part-reviewed` | the record, the version, the part, its digest, the verdict |
|
||||
| `reviewed` | the record, the version, every part's verdict: one event per version, as before |
|
||||
| `swept` | the digest removed, since when nothing named it |
|
||||
| `moved` | the record, its counts on the bus |
|
||||
| `checked` | the move's check: records compared, every difference |
|
||||
| `formed` | the record, the version, the names changed |
|
||||
@@ -164,7 +208,7 @@ None is named `drafted`, which stays the event of a clause the import's judgemen
|
||||
parts by title; for a design, its `to-be` section and, where something of it runs, its `as-is` section (§ 9);
|
||||
- for a decision, its **clauses** (§ 3);
|
||||
- its **links**, typed, each stored once and read from both ends;
|
||||
- its **versions**: every change is a new version with who made it, when, and why in one line; `history` lists them
|
||||
- its **versions**: every `submit` is a new version with who made it, when, and why in one line; `history` lists them
|
||||
and gives the difference between any two. A progressive insight is a version marked as one, dated, saying what stood
|
||||
before. A form-only version (§ 2.2) is marked `form`.
|
||||
|
||||
@@ -180,66 +224,81 @@ None is named `drafted`, which stays the event of a clause the import's judgemen
|
||||
|
||||
### 2.1 Where it is kept
|
||||
|
||||
A record is kept in three of the bus's own forms, so that a new version costs only what changed (ADR 0592 §1 to §9):
|
||||
A record is kept as one instance of the knowledge base's ledger `records` (ADR 0597 §11), so that a new version
|
||||
costs only the sections it changes; the forms of ADR 0592 §1 to §9 that it replaces are named in its §12.
|
||||
|
||||
```
|
||||
heads (state) versions (stream) sections (object bucket)
|
||||
┌─────────────────────┐ ┌──────────────────────────┐ ┌────────────────────────┐
|
||||
│ decision.0290 │ │ decision.0290 version 7 │ │ 3f9a… "Context" text │
|
||||
│ title, status, … │ │ who, when, why, mark │ │ 81c2… "Options" text │
|
||||
│ clauses, links │ │ the head as it stood: │──────▶ │ 0d77… "Context" text, │
|
||||
│ current: 8 │ │ Context 3f9a… │ │ as changed in 8 │
|
||||
│ sections: │ ├──────────────────────────┤ │ … │
|
||||
│ Context 0d77… │──────▶│ decision.0290 version 8 │──────▶ └────────────────────────┘
|
||||
│ Options 81c2… │ │ Context 0d77… │ each body once, named by
|
||||
└─────────────────────┘ │ Options 81c2… │ its sha256 digest
|
||||
└──────────────────────────┘
|
||||
records: history (stream, one subject per instance) records: state (key-value, derived)
|
||||
┌───────────────────────────────────────────────┐ ┌─────────────────────────────────┐
|
||||
│ 101 decision/0290 propose fields, … │ │ decision/0290 │
|
||||
│ 102 decision/0290 write-section "Context" │ │ title, status, clauses, links │
|
||||
│ text, digest 3f9a… │ apply │ current sections: │
|
||||
│ 103 decision/0290 write-section "Options" │ ───────▶ │ Context 3f9a… 102 │
|
||||
│ text, digest 81c2… │ │ Options 81c2… 103 │
|
||||
│ 104 decision/0290 submit sections 102, 103 │ │ pending sections: none │
|
||||
│ 230 decision/0290 write-section "Context" │ │ after operation 231 │
|
||||
│ text, digest 0d77… │ └─────────────────────────────────┘
|
||||
│ 231 decision/0290 submit sections 230, 103 │ records: snapshot (one per instance,
|
||||
└───────────────────────────────────────────────┘ the latest head, never expiring)
|
||||
```
|
||||
|
||||
- **The head** is one key in the state `heads`, `<kind>.<number>`: the record's fields, its clauses with their status
|
||||
and source, its links, the operator's ask and warrant, its current and pending version, the latest verdict of each
|
||||
part's review (§ 5), and its sections in order, each a title and a digest. A key's value stays under 256 KiB
|
||||
(ADR 0201). A reserved number is a head at `reserved`, with who reserved it, when, for what and when it expires.
|
||||
- **A version** is one message on the stream `versions`: who wrote it (the caller the bus proved), when, why, its
|
||||
mark, who wrote each section, the reason of any take-over since the version before, and the head as it stood. It never copies a section's body. The stream keeps its history: every message,
|
||||
with no age limit, and when it reaches its cap it refuses a new message rather than dropping its oldest, with a
|
||||
condition raised at 75 percent of the cap. A module's stream may declare that retention; before, a stream was kept at
|
||||
most 30 days (ADR 0592 §6, changing ADR 0363 §1). The stream `reviews` keeps its history the same way. The
|
||||
condition at 75 percent is raised by the grants part (mesh-grants), in one place, for every history stream and
|
||||
object bucket of every module (ADR 0593 §10, changing ADR 0592 §16).
|
||||
- **A section's body** is one object in the bucket `sections`, named by the sha256 digest of the body: a section
|
||||
unchanged in a new version costs nothing. The module checks that a body hashes to its name each time it reads one.
|
||||
The state `names` keeps, per digest, how many versions and drafts name it and since when none has; an object no
|
||||
version and no draft names is removed a day after the last stopped naming it, each removal the event `swept`. The bucket is storage of the bus, as the object bucket of ADR 0486 is. Any module
|
||||
may declare an object bucket of its own in its manifest, with a local name and a cap, created as its state buckets
|
||||
are, kept with no age limit and refusing a new object when full (ADR 0592 §5).
|
||||
- **A draft** is a key in the state `drafts` (§ 4). **A part's verdict** is a message on the stream `reviews`, and a
|
||||
part asked of the judge and not yet answered a key in the state `asked` (§ 5). **The whole-set findings** standing
|
||||
now are keys in the state `findings`, and what was told to the operator once is a key `told.<topic>` there.
|
||||
- **The head** is the instance's state, `<kind>/<number>`: the record's fields, its clauses with their status and
|
||||
source, its links, the operator's ask and warrant, its current and pending version, the latest verdict of each
|
||||
part's review (§ 5), and its sections in order, each a title, a digest and the sequence of the `write-section`
|
||||
that wrote it, never the text. A section's title is held to 200 bytes, so an entry is at most about 300 bytes,
|
||||
and a head with 64 pending and 64 current sections holds at most about 40 KiB of them, within a state value's
|
||||
256 KiB (ADR 0597 §11). A reserved number is a head at `reserved`, with who reserved it, when, for what and when it
|
||||
expires. It is derived by `apply` and never written by the module.
|
||||
- **A version** is the operation `submit`: who wrote it (the caller the bus proved), when, why, its mark, the
|
||||
reason of any take-over since the version before, and the version's sections (title, digest and sequence each),
|
||||
which its effect sets as the head's current sections. It never copies a section's text. Who wrote each section is
|
||||
the author of its `write-section`.
|
||||
- **A section's text** is in the `write-section` operation that wrote it, with its digest, at most 40 KiB within a
|
||||
history entry's 256 KiB: a section unchanged in a new version costs nothing, since no operation is written for it.
|
||||
`apply` refuses a `write-section` whose digest does not match its text, and the module checks the text against
|
||||
the digest each time it reads one. A dropped draft's operations stay in the history, named by no version
|
||||
(ADR 0597 §11).
|
||||
- **The history** is kept for good: the ledger template's history refuses a new operation when full, rather than
|
||||
dropping its oldest, and the bus module raises its condition at 75 percent of the cap (ADR 0595 §3, §6). Each
|
||||
instance's snapshot, the latest head on a subject of its own, makes a rebuild fast (ADR 0597 §8).
|
||||
- **A draft** is a key in the state `drafts` (§ 4). **A part's verdict** is the operation `review` on the record's
|
||||
history first, then a message on the stream `reviews`; a `review` with no message on `reviews` is sent at start
|
||||
with the operation's sequence as its id, so the consumers of `reviews` miss none and hear none twice. A part
|
||||
asked of the judge and not yet answered a key in the state `asked` (§ 5). **The whole-set findings** standing
|
||||
now are keys in the state `findings`, and what was told to the operator once is a key `told.<topic>` there. These
|
||||
keep their own forms (ADR 0597 §12), asked for in `requires`: the states by the template state; `reviews`, kept for
|
||||
good and heard only where granted (ADR 0592 §6, §8), as a bare stream with its own configuration (kept for good,
|
||||
refusing when full, a grant), because no template both keeps for good and takes a grant (ADR 0595 §2); the bus module's read of fill every minute
|
||||
covers it like any entry that refuses when full.
|
||||
|
||||
**Caps** (ADR 0592 §25). Each is set from the move's dry run, within its form's bound: a stream at most 8192 MiB
|
||||
(ADR 0363), a state at most 64 MiB (ADR 0201), an object bucket at most 8192 MiB (ADR 0592 §5). The starting figures, from
|
||||
today's 83 MB for 736 records with every version copying every section, are:
|
||||
**Caps** (ADR 0592 §25, its object bucket's cap lapsing by ADR 0597 §12). Each is set from the move's dry run, within
|
||||
its template's bound (ADR 0595 §3): a history at most 8192 MiB, a state or a ledger's state at most 64 MiB. The
|
||||
starting figures, from today's 83 MB for 736 records with every version copying every section, are:
|
||||
|
||||
| Bucket or stream | Cap |
|
||||
|---|---|
|
||||
| `versions` (stream) | 1 GiB |
|
||||
| `reviews` (stream) | 512 MiB |
|
||||
| `sections` (object bucket) | 1 GiB |
|
||||
| `drafts` (state) | 64 MiB |
|
||||
| `heads`, `asked`, `names`, `findings` (state) | 64 MiB, ADR 0201's bound |
|
||||
| Kept data | Template | Cap |
|
||||
|---|---|---|
|
||||
| `records` (history) | ledger | 1 GiB |
|
||||
| `records` (state) | ledger | 64 MiB |
|
||||
| `reviews` | a bare stream, its own configuration | 512 MiB |
|
||||
| `drafts` | state | 64 MiB |
|
||||
| `asked`, `findings`, `sent` | state | 64 MiB, the template's bound |
|
||||
|
||||
A write refused at a cap names the cap, and the module's health turns unhealthy naming it.
|
||||
|
||||
**Who may read each** follows the mesh's own forms, read-only (ADR 0592 §8, §9). `versions` and `reviews` are
|
||||
restricted streams (ADR 0363): a module hears them only through its own `consumes` and a `grant` in mesh-knowledge's
|
||||
trusted config value `readers`, which names the web page that shows records once it exists and nothing today. The
|
||||
object bucket `sections` is read the same way, through a `reads` entry and a `grant` of `readers`: ADR 0592 extends
|
||||
ADR 0363's grant to a module's object bucket, and the grants part composes a read-only grant on that one bucket
|
||||
(ADR 0593 §10).
|
||||
`heads` is read by a module that names it in `reads`; `drafts`, `asked`, `names` and `findings` by mesh-knowledge
|
||||
alone. No secret is kept in any of them. A grant cannot limit a reader to some objects of a bucket
|
||||
(ADR 0488), which is why reading is announced per bucket and never per record.
|
||||
**The ledger's state is the tightest cap.** 64 MiB is already the template's top. A head holds no text, only fields,
|
||||
clauses, links and its sections' titles and digests; estimated at most 8 KiB for a usual record, the 736 heads of
|
||||
today need about 6 MiB, and about 1000 once designs and glossary entries become records (slice 3, § 9) about 8 MiB.
|
||||
Those are estimates; the move's dry run measures the real total, and a total above 32 MiB is told to the operator
|
||||
before the move.
|
||||
|
||||
**Nobody else reads any of it** (ADR 0596 §2, ADR 0597 §9, changing ADR 0592 §8 and §9). Only the knowledge base's
|
||||
own account reads its ledger's history, snapshot and state and its other kept data; the ledger's history is published
|
||||
on only by the tool runner's ledger credential. Another module asks the knowledge base's verbs (`get`, `history`,
|
||||
`ask`) or hears its events (`reviewed`, `accepted` and the rest); the reads of `heads` and `sections` that ADR 0592
|
||||
let another module declare lapse once those verbs answer what each reader needed. `reviews` stays a restricted event
|
||||
family (ADR 0363 §4), heard only by a module granted it in mesh-knowledge's trusted config value `readers`, which
|
||||
names nothing today. A draft is answered by the verbs to its author only, and a version's bodies as `get` answers
|
||||
them, so the text in the history is no more open than ADR 0592 kept it. No secret is kept in any of it.
|
||||
|
||||
### 2.2 A section is a part with a title
|
||||
|
||||
@@ -304,9 +363,11 @@ version (ADR 0592 §17):
|
||||
```
|
||||
|
||||
A draft is a key in the state `drafts`: its author, when it was opened, the version it starts from, the record's
|
||||
fields and clauses, and its sections in order as title and digest. Each `write` stores the section's body in the
|
||||
object bucket `sections` and its digest in the draft, so the knowledge base holds the only copy of a draft and a
|
||||
session on any node can read it. A draft holds at most 64 sections and 1 MiB; one untouched for a week is counted in
|
||||
fields and clauses, and its sections in order as title, digest and the sequence of the `write-section` that wrote
|
||||
each. Each `write` appends a `write-section` operation with the section's title, text and digest to the record's
|
||||
history at once, through `apply`, and names its sequence in the draft (ADR 0597 §11, changing ADR 0592 §17), so an
|
||||
unsubmitted draft's text is already in the history, the knowledge base holds the only copy of a draft, and a
|
||||
session on any node can read it through the verbs; `submit` carries the draft's sections into the version. A draft holds at most 64 sections and 1 MiB; one untouched for a week is counted in
|
||||
the health, and one idle for 14 days is dropped and its author told by `dropped`.
|
||||
|
||||
**A draft and a proposal have one author** (ADR 0592 §19). Only the author writes a draft or revises a proposal;
|
||||
@@ -454,32 +515,53 @@ field by field and section by section, and the result is written on issue 426.
|
||||
### 8.1 The move off the store
|
||||
|
||||
The records leave the store once, and the database is dropped only after a check and the operator's yes
|
||||
(ADR 0592 §12 to §16):
|
||||
(ADR 0592 §12 to §16; the forms the records are copied into are those of ADR 0597 §12):
|
||||
|
||||
1. **Order.** Two deliveries, in order (ADR 0593 §10, changing ADR 0592 §16): first the mesh-sdk release sdk1
|
||||
(issue 1585), which carries the manifest's field types for `objects` and `retention: history`, their checks in
|
||||
the indexer's judge, and in the grants part the creation of the object buckets and history streams, the
|
||||
read-only grants of `readers` on them and the condition at 75 percent for every such bucket and stream in one
|
||||
place; then mesh-knowledge. An indexer whose judge predates these keys refuses a manifest that has them, so
|
||||
mesh-knowledge cannot be delivered before the release. The claude-code module's `judged` event is
|
||||
1. **Order.** The ledger is built first, in the order of ADR 0595 §10: mesh-sdk's typed `requires`, binding and
|
||||
package of the new calls; the indexer's judge's rules; the bus module's process, verbs and events; the tool
|
||||
runner's calls, `apply` among them, with its ledger credential per module; then mesh-knowledge moves, asking for
|
||||
its ledger, states and stream in `requires`. An indexer whose judge predates the new form refuses a manifest
|
||||
that uses it, and the placement holds the module off a machine whose tool runner does not read the typed
|
||||
binding, so mesh-knowledge cannot be delivered before them. The release sdk1 (issue 1585, ADR 0593 §10), which
|
||||
created object buckets and history streams through the grants part, is no longer needed by the move: the
|
||||
knowledge base asks for no object bucket and no stream `versions`. The claude-code module's `judged` event is
|
||||
delivered beside, and is not needed for the move (§ 5).
|
||||
2. **Copy.** The move copies everything the database holds onto the bus, every record, version, clause, link,
|
||||
review, finding, ask with its warrant, reservation and told mark: each version's sections into `sections` by
|
||||
digest, each version as a message on `versions`, each record's head into `heads`, each standing finding into
|
||||
`findings`, each told mark as `told.<topic>`. A clause keeps its number, status and source as the database holds
|
||||
review, finding, ask with its warrant, reservation and told mark: each record's versions as operations on its
|
||||
instance in the ledger, in order (a `write-section` for each section a version changed, then its `submit`, then
|
||||
the record's review, ask, warrant and acceptance), through `apply`, so each head is derived and never copied; each operation is stamped with the knowledge base's own
|
||||
account and keeps the database's author as a separate field marked imported, which `history` shows as the author
|
||||
of an imported version;
|
||||
each standing finding into `findings`, each told mark as `told.<topic>`. A clause keeps its number, status and source as the database holds
|
||||
them (ADR 0321). It emits `moved` per record, writes nothing twice, and a second run changes nothing and says so.
|
||||
A dry run lists what it would write and the size each bucket and stream will need, from which the caps are set.
|
||||
3. **The window.** From the move's final copy through the check to the switch, every writing verb is refused with
|
||||
its reason ("the knowledge base is moving"). Reading verbs answer throughout.
|
||||
4. **Check.** A tool compares the database and the bus record by record: for each record the count of each of
|
||||
those, the digest of every section of every version, computed from the database's text and read from the bus,
|
||||
that each body hashes to its name, and a digest of every review's, finding's and ask's content. It emits
|
||||
those, the digest of every section of every version, computed from the database's text and read from its
|
||||
`write-section` on the bus, that each text hashes to its digest, that each head derived equals the head the
|
||||
database holds, and a digest of every review's, finding's and ask's content. It emits
|
||||
`checked` with every difference, and the health shows its last result.
|
||||
5. **Switch.** Only once the check shows no difference, the module answers from the bus. The switch is a key on the
|
||||
bus, so a restart never falls back to the database. A difference is told to the operator, and the module stays on
|
||||
the database.
|
||||
6. **Rollback.** Until the database is dropped, the module writes each change to the database too, so a rollback of
|
||||
the delivery reads a current database. A rollback is safe only until the drop.
|
||||
6. **Rollback.** Until the database is dropped, the module writes each change to the database too, after `apply`
|
||||
has taken its operation, so a rollback of the delivery reads a current database. What the database holds is
|
||||
kept as a key in the state `findings`, `database.sequence`: the last history sequence written to it. At start,
|
||||
and after a write the database refused, the module catches the database up from the history from that sequence
|
||||
on, so a restart between `apply` and the database write loses nothing. A database still behind after three tries
|
||||
raises the condition `database-behind`, naming each record it lacks and saying that a rollback now would lose
|
||||
them, and never answers the author that the write was refused; the module's health is unhealthy while it stands,
|
||||
the drop (step 8) is refused while it stands, and the catch-up tool copies the records again from the ledger and
|
||||
clears it. A rollback is a person's act, and the condition is what tells them it is unsafe. **After a rollback**,
|
||||
the older module wrote to the database alone, and not only versions: acceptances, verdicts, asks, warrants,
|
||||
reservations and changed clauses too. So until the drop, every start of the module refuses every writing verb
|
||||
and runs the check of step 4 before it serves writes; a difference it finds is copied from the database onto the
|
||||
ledger as operations, and the check runs again, so writes are served only once it is clean. A record changed on
|
||||
both sides (an operation the database never took, and a change the older module made) never checks clean: it is
|
||||
told to the operator at once, naming the record and both changes, so the refused writes read as a decision
|
||||
waiting and not as a stall. A rollback is safe
|
||||
only until the drop. This settles the rollback ADR 0597 §13 left to the design.
|
||||
7. **Tell and ask.** Once the check is clean, the operator is told its result and asked, once, whether the database
|
||||
may be dropped, because dropping it removes data.
|
||||
8. **Drop.** On the operator's yes, the delivery after it removes the module's requirement of a database, and the
|
||||
@@ -494,7 +576,7 @@ The records leave the store once, and the database is dropped only after a check
|
||||
| 2 | the decision records and research efforts leave this repository; path mentions become "ADR NNNN" and "research NNN"; playbooks 01 and 02 changed | this repository holds no decision record, and `get` answers every one | issue 426 |
|
||||
| 3 | designs and the glossary's entries as records, moved in one delivery, and each design's state worked out by the knowledge base (§ 9.1 to § 9.3; ADR 0423, ADR 0424) | `get` answers design 47 with its sections and links, a word's entry answers from the knowledge base, and a design's state follows its issues' deliveries without anyone writing it | issue 428 |
|
||||
| 4 | meeting notes, drawings and blog posts | not decided; needs its own decision | issue 429 |
|
||||
| 5 | the records on the bus, written and reviewed by section, the move off the store and the titles (§ 1, § 2, § 4, § 5, § 8.1; ADR 0592), delivered as the mesh-sdk release sdk1, then mesh-knowledge, with claude-code's `judged` event beside | the acceptance tests of the fifth slice in § 10, live; the knowledge base requires no database | the issues its hand-off opens (playbook 04) |
|
||||
| 5 | the records on the bus, written and reviewed by section, the move off the store and the titles (§ 1, § 2, § 4, § 5, § 8.1; ADR 0592), kept as the first ledger (ADR 0597), delivered after the ledger's build (ADR 0595 §10), then mesh-knowledge, with claude-code's `judged` event beside | the acceptance tests of the fifth slice in § 10, live; the knowledge base requires no database | the issues its hand-off opens (playbook 04) |
|
||||
|
||||
Numbers from the issue tracker, which `reserve` needs, are the tracker's own slice, tracked by issue 424. This
|
||||
repository keeps no copy of the records, generated or otherwise (issue 435, answered by the operator). Playbooks
|
||||
@@ -592,10 +674,10 @@ milestones and nothing of how a delivery runs; it only listens for the announcem
|
||||
| An answer stays within its bound | the module's test over the full import of `main` at a recorded commit: every `ask`, `get` and `holds` of a fixed question list |
|
||||
| A record written in this repository meanwhile writes its clauses | `records.py`, check `clauses`, on every hq pull request, until the records left this repository (issue 426); since then `records.py`, check `no-records`, refuses any record written here |
|
||||
| Live | the acceptance sentence, asked from a session outside this repository |
|
||||
| A record is kept in the bus's forms, a section once by its digest, and read only as announced | one integration test that starts the module whole against a real bus server: a record of three sections and a second version changing one give one head, two version messages and four objects; a module with no `grant` in `readers` is refused the streams and the object bucket |
|
||||
| A record is kept as a ledger instance, a section's text once in its operation, and read by no other module | one integration test that starts the module whole against a real bus server: a record of three sections and a second version changing one give one head, two `submit` operations and four `write-section` operations, the changed section's text found only in its operation; another module's account reading the ledger's history, snapshot or state, the head among them, is refused (ADR 0597 How it is checked, clauses 9, 11, 12) |
|
||||
| Search answers from the index as it did from the store | a unit test over a fixed set of records and a fixed question list; the integration test restarts the module and asks again |
|
||||
| A draft is written by section and has one author, and a take-over tells the previous one | unit tests of each verb of § 4, of a refusal naming the author, and of `take-over` emitting `taken-over`; a take-over refused while the operator's Approve is waited for |
|
||||
| A stream that keeps its history, and an object bucket of a module's own | in the integration test: a history stream at its cap refuses a new message and keeps its oldest; an object no version or draft names is removed after a day, one still named is kept; unit tests of registration refusing a name already taken |
|
||||
| A ledger that keeps its history | in the integration test: the history at its cap refuses a new operation and keeps its oldest; a draft's written section is in the history before `submit` and named by no version after `drop`; a section title over 200 bytes is refused |
|
||||
| A long record is judged whole, one part at a time | unit tests: the frame never passes 16 KiB and says what it cut, with twenty clauses of 2000 bytes and a 40 KiB Context; every question stays under 60 KiB with a 40 KiB section and that frame, and the head part with 40 KiB of clauses; a section over 40 KiB is refused saying split it; an unchanged section keeps its review, a changed frame asks it again |
|
||||
|
||||
**The fifth slice's tests, one per failure path** (ADR 0592; the review by architectural domain's testing ruling). Unit tests are
|
||||
@@ -607,14 +689,14 @@ table-driven; one integration test starts the module whole against the real bus
|
||||
| two writes race on one draft | unit: the second compare-and-set loses, is retried, and is refused with the reason past its tries |
|
||||
| `submit` races `take-over` | unit: one wins; the other is refused naming the new author or the new version |
|
||||
| two reservations race | unit: two numbers, or one refused; never one number twice |
|
||||
| a write cut between the stream and the head | unit: the call answered that the version is taken; a start completes the head from the stored version and sends the event once |
|
||||
| a failure before the version is stored | unit: the call is refused, and no head, event or version follows later |
|
||||
| a write cut between the history and the head | the ledger's integration test (design 25 § 12.7): the call answered that the operation is taken; the next `apply` derives the head. The module's own integration test: stopped after `apply`, restarted, it sends the event once |
|
||||
| a failure before the operation is appended | unit: the call is refused, and no head, event or version follows later |
|
||||
| a change through the bus during the move | unit: read back from the database, so a rollback of the delivery reads a current database (ADR 0592 §14) |
|
||||
| a section changed after the operator's Approve | unit: the record is asked again; a version with every section unchanged is not (ADR 0592 §21) |
|
||||
| a named digest has no object | unit: health unhealthy, naming it; the integrity tool restores it from given text |
|
||||
| a body does not hash to its name | unit: the read refuses it, naming the digest |
|
||||
| a full object bucket | integration: a write refused naming the cap; health unhealthy |
|
||||
| a full history stream | integration: `submit` refused with the head unchanged |
|
||||
| a head behind its history | unit: health unhealthy naming the lag; the ledger's `rebuild` clears it |
|
||||
| a text does not hash to its digest | unit: `apply` refuses the `write-section`, and a read refuses it, naming the digest |
|
||||
| a full ledger state | integration: `submit` answers taken with the head behind; health unhealthy naming the cap |
|
||||
| a full ledger history | integration: `submit` refused with the head unchanged |
|
||||
| a judge timeout for one part | unit: that part stays asked and the version proposed, saying which; the others keep their verdicts |
|
||||
| a restart with parts asked | integration: the parts in `asked` are collected again and judged |
|
||||
| `reviewed` once per version | unit: one `reviewed` after the last `part-reviewed`, carrying every verdict |
|
||||
@@ -622,13 +704,18 @@ table-driven; one integration test starts the module whole against the real bus
|
||||
| a second run of the move | unit: changes nothing and says so |
|
||||
| the move's check | unit: a changed review, a missing finding and a missing ask, each named |
|
||||
| writes during the switch | unit: every writing verb refused with its reason; reading verbs answer |
|
||||
| an older judge | the indexer's judge test (release sdk1): a manifest with `objects` or `retention: history` refused by a judge that does not know them, accepted by one that does |
|
||||
| the grants part's forms | the grants part's test (release sdk1), against a real bus server: an object bucket and a history stream created for their module; a read-only grant on that one bucket for a module named in `readers`, removed at the next composition once withdrawn; the condition at 75 percent raised and cleared below |
|
||||
| an older judge | the indexer's judge test (ADR 0595 §8): a manifest asking for a ledger in `requires` refused by a judge that does not know the template, accepted by one that does |
|
||||
| the bus module's forms | the bus module's test (design 25 § 12), against a real bus server: the ledger, states and stream created for their module, `reviews` heard by a module granted it in `readers` and by no other; the condition at 75 percent raised and cleared below |
|
||||
| a reference | unit: "record › title › row" resolves to the section; a nested title is refused |
|
||||
| a form-only version | unit: accepted when only names and links changed and each target resolves; refused otherwise |
|
||||
| an unproven caller | unit: `open`, `write`, `submit`, `propose` and `take-over` refuse it |
|
||||
| the index behind | unit: health unhealthy while the index's revision is behind `heads`; `ask` refuses "index not built" |
|
||||
| the start | integration: the time to a built index measured and reported |
|
||||
| the index behind | unit: health unhealthy while the index's sequence is behind the ledger's state; `ask` refuses "index not built" |
|
||||
| a forward delivery after a rollback | unit: an acceptance and a verdict made by the older module are found by the check at start, the writing verbs refused meanwhile, the changes copied and checked, then writes served; a record changed on both sides is told to the operator |
|
||||
| a restart between `apply` and the database write | unit: the module is stopped after `apply` and before the database write, restarted, and the database matches the history; health showed it behind meanwhile |
|
||||
| the database refuses past its tries | unit: `database-behind` names the record; the drop is refused while it stands; the catch-up tool clears it |
|
||||
| a restart between `apply` and its event | unit: the module is stopped after `apply` of a `review`, restarted, and sends one `reviewed`; a second start sends none |
|
||||
| the move as operations | unit: each record's versions copied as `write-section` and `submit` operations in order; the derived head equals the database's; a second run appends nothing |
|
||||
| the start | integration: the time to a built index measured and reported, failing above 40 seconds, the estimate's top |
|
||||
| A section is a part with a title | a table test, one case per refused name and text of § 2.2; a form-only version whose text changed beyond its links is refused |
|
||||
| Every bucket has a tool, and health says the index | the manifest test compares tools and events with the code; a unit test of the health before and after the index is built |
|
||||
|
||||
@@ -669,12 +756,17 @@ done:
|
||||
6. After the titles' form-only versions, no record holds a section named like a file, `history` shows each as a
|
||||
change of form, and `get` of "research 062 › Direction" answers that section.
|
||||
7. The fill tool shows each bucket and stream against its cap; the second version of a record with one changed
|
||||
section adds one object, not one per section.
|
||||
section adds one `write-section` and one `submit`, not one operation per section.
|
||||
8. During the move's window a `write` is refused saying the knowledge base is moving; after the switch a restart
|
||||
still serves from the bus; the health names the source and the check's result.
|
||||
9. A part's review waiting on the judge survives a restart of the module, and one past 30 minutes reaches the
|
||||
operator once.
|
||||
10. Every test of the table above passes in the one check of the pull requests, within the test budget.
|
||||
11. As a ledger (ADR 0597 How it is checked, clauses 11 and 12): `get` answers the same digest for every record
|
||||
before and after the move; `ask` answers from bodies read from their operations; `mesh-knowledge.reviewed` is
|
||||
emitted once per review; the module's manifest asks for no stream `versions`, no object bucket `sections` and
|
||||
no state `names`, and the bus holds none for it once the move's check is clean; its tools and health name the
|
||||
ledger's repairs and lag, and no digest store; the consumers of `reviews` still hear it.
|
||||
|
||||
## What it leaves out
|
||||
|
||||
@@ -699,3 +791,25 @@ review of ADR 0592's fourth version beside), and the lead ruled on every finding
|
||||
|
||||
The knowledge base's review of the fourth version found that the reference form read as nested sections: ADR 0592
|
||||
§30 and § 2.2 say sections never nest.
|
||||
|
||||
### The amendment of 2026-11-03
|
||||
|
||||
The amendment of 2026-11-03 to ADR 0595, ADR 0596 and ADR 0597 (designs 25 § 12, 32 § 4 and 55 together) was
|
||||
reviewed by the four reviewers by architectural domain side by side (playbook 01), over three rounds, and the lead
|
||||
ruled on every finding. What was found, and what answers it:
|
||||
|
||||
| Domain | Found | Answered by |
|
||||
|---|---|---|
|
||||
| security | the author could be words the module chose; a person's dangerous tools had no role, mark or path to the process; the caller travelled as a plain field | design 25 § 12.3 (marks, the process's own tool subject, the identity seat's signed token), § 12.4 (the tool runner stamps the caller it proved; the move's authors marked imported) |
|
||||
| seams | the bus module asked for its own events in the new form, which it would have to create itself (ADR 0595 §10); a person's tool had no join to the process; a verdict was written twice with no order | design 25 § 12.3 (its events on its old `streams` section; the process's own tool subject); design 55 § 2.1 (the `review` operation first, `reviews` caught up from it) |
|
||||
| running qualities | overdue creation was undefined and raised by nobody; the database's rollback write lived in memory; a forward delivery after a rollback could miss acceptances and verdicts; no size estimate of the heads | design 25 § 12.2 (five minutes, the watchman, durable consumers, `check` at start); design 55 § 8.1 step 6 (`database.sequence`, the check at every start before the drop), § 2.1 (the heads' estimate) |
|
||||
| health | the bus module's health would have fired on a quiet mesh; conditions had no clearing; `database-behind` had no clearing | design 25 § 12.2 and § 12.3 (each condition with what clears it); design 55 § 8.1 step 6 |
|
||||
| events and logs | an event lost when the module stopped after `apply`; the bus module's events had no reader | design 55 § 1 (each event sent with its operation's sequence as id, the state `sent`); design 25 § 12.3 (`history` of one entry) |
|
||||
| testing | the bus module had no test that starts it whole; live tests would have filled the live bus; the budget was not counted | design 25 § 12.7 (the nats module's integration test, live drift only by a manifest change, the budget paragraph); design 55 § 10 |
|
||||
| upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 |
|
||||
| tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 |
|
||||
|
||||
**Left to the operator**, because each changes an accepted record: a margin between raising and clearing the
|
||||
condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of
|
||||
its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate`
|
||||
as well as `drop`. Each is noted on issue 1966.
|
||||
|
||||
Reference in New Issue
Block a user