Compare commits

..
Author SHA1 Message Date
jochen 506d00cf74 Name fix time and fix budget in the glossary (issue 403, ADR 0313)
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery ready: it delivers once merged
mesh/delivery-group group fix/403-fix-time checking: composed check out of date: novox/mesh-controller@7ca5a45ecc5a was judged against trunk ?, the trunk is no…
ADR 0313 makes the span from the move to fixing to delivered the operator's
target; the delivery budget stays a measure inside it, and its leaf figure is
three minutes since ADR 0304.
2026-10-12 15:50:13 +02:00
mesh-admin 285ead0043 Merge pull request 'Give ADR 0313 a home in design 47, the delivery budget it extends' (#330) from chore/adr-0313-home-in-design-47 into main 2026-10-12 13:36:01 +00:00
jochen f63e60a4a1 Give ADR 0313 a home in design 47, the delivery budget it extends
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
The knowledge base's whole-set cited rule (to-be 55, ADR 0313's own review)
refuses to accept a record nothing cites. Design 47 already builds on
ADR 0282's delivery budget, which ADR 0313 extends with the operator's
wider fix-time target, so it is the record's home.
2026-10-12 15:29:38 +02:00
mesh-admin 992c075444 Merge pull request 'to-be 55: a drafted clause may be changed, and a clause another record links keeps its number (issue 494, ADR 0321)' (#329) from design/55-a-drafted-clause-may-be-changed into main 2026-10-12 12:18:23 +00:00
jochen 12e59b3c42 to-be 55: a drafted clause may be changed, and a clause another record links keeps its number (issue 494, ADR 0321)
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
Names ADR 0321 in its home, so the knowledge base's cited rule holds through
the design that carries it.
2026-10-12 14:15:03 +02:00
mesh-admin a3bde3657d Merge pull request 'To-be 47: a covered stop reads covered and stays listed; a one-machine module is covered (issue 480)' (#328) from design/47-a-covered-stop into main 2026-10-12 11:56:36 +00:00
mesh-admin 361d503034 Merge pull request 'Say in to-be 27 how a value several modules share is set once (issue 415, ADR 0309, ADR 0319)' (#327) from design/415-shared-value-trust into main 2026-10-12 11:54:01 +00:00
jochen e8cff1646a Carry ADR 0309 and ADR 0319 in one paragraph of to-be 27, replacing the 0309 paragraph alone (issue 415)
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-12 13:50:33 +02:00
jschoubben 94e7e044b9 Say in to-be 47 that a covered stop reads covered and stays listed, and that a one-machine module is covered (issue 480)
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
2026-10-12 13:50:29 +02:00
mesh-admin d0e23b1dbf Merge pull request 'Give ten uncited decisions their homes in the designs they change (issue 504)' (#324) from fix/504-homes-for-uncited-decisions into main 2026-10-12 11:45:28 +00:00
mesh-admin f3cee3e7c9 Merge pull request 'to-be 55: a failed whole-set rule is a warning to the operator and leaves the module healthy; cited is judged in the review (issue 504)' (#325) from fix/504-whole-set-rule-is-a-warning into main 2026-10-12 11:45:03 +00:00
jochen fe4f62c712 to-be 55: a failed whole-set rule is a warning to the operator and leaves the module healthy, and cited is judged in the review (issue 504)
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
The operator decided on 2026-10-12, on issue 504, that a rule of the
record set that fails is a fault of the records, not the module failing:
health says only whether the module works (issue 508's principle), so a
finding must not hold a walk at the module's first-node gate.
2026-10-12 13:28:12 +02:00
jochen 23ef14d0ba Give ten uncited decisions their homes in the designs they change (issue 504)
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
The knowledge base's cited rule failed for ADR 0303, 0306-0312, 0314 and 0315: accepted,
and nothing cited them. Each design that owes the decision now names it in its decisions
and says it in prose, so the records can name these designs as their homes.
2026-10-12 13:24:48 +02:00
11 changed files with 283 additions and 42 deletions
+8 -2
View File
@@ -387,8 +387,14 @@ planner, the walk). **Downstream of** Module, Core, Health and repair and Data.
Not a *core component* (ADR 0236's controller, node-engine, tool runner and bus, each with a health probe):
the core modules add mesh-delivery and the resolver.
- **delivery time** — from a delivery's merge to every node of its deploy plan that is heard from running its
modules' builds and reporting them healthy. Its **delivery budget** is what it is held to: five minutes for
a leaf module, ten for a core module (ADR 0282). Not the walk's time, which ends at the send.
modules' builds and reporting them healthy. Its **delivery budget** is what it is held to: three minutes for
a leaf module (ADR 0304), ten for a core module (ADR 0282). Not the walk's time, which ends at the send. A
measure inside the fix budget, not the operator's target (ADR 0313).
- **fix time** — from an issue's first move to `fixing` to the moment the last of its fixes is delivered: the
operator's measure of a fix, the agent's work, the review and the checks included, the delivery time its last
part. Its **fix budget** is what it is held to: five minutes for a leaf module, ten for a core module, counted
from the move to `fixing`; an issue whose fixes move no module has none (ADR 0313). Not the delivery time,
which starts at the merge.
- **release** — a person's word that a held delivery goes on; the verb `mesh-delivery.release`. Used in
this sense only.
- **rollback** — putting a node back on the build it ran before, by something other than the build being
+18 -8
View File
@@ -3,8 +3,9 @@ layer: to-be
status: in-progress
code:
- mesh-controller
updated: 2026-10-10
updated: 2026-10-12
decisions:
- "ADR 0315"
- "ADR 0001"
- "ADR 0005"
- "ADR 0006"
@@ -64,11 +65,20 @@ applications** — first-party, shipped with everything else, and running on the
anything else does. A task does not need to know a node exists, and *being ours does not make
something infrastructure*. `ai` is folded into `config`: a provider licence is an ordinary grant.
**`record` is an open question rather than an eighth entry.** Contexts integrate through it
(ADR 0008), which makes it load-bearing,
and *where it lives* is unresolved, tracked by issue 414 since
research 006 closed — putting it in the foundation risks recreating the circularity the tier design just
removed. Listing it here would settle by naming what has not been settled by arguing.
**`record` is not an eighth entry: it is the bus** (2026-10-12, ADR 0315). Contexts integrate through
it (ADR 0008), and where it lives was left open by research 006, for fear that putting it in the foundation
would recreate the circularity the tier design removed. ADR 0315 writes down what later records built: the
record is the bus's, the EVENTS stream, where each module says the events it declares (ADR 0041), and the
key-value buckets each module declares for its current state, which another module names in `reads` to read
(ADR 0201). Asking another context is a call to its verbs over the bus, request and reply, and is not the
record; a secret, and whatever a context keeps out of its buckets, is asked for. The controller's own records
are read from its record mirrors (ADR 0289) or asked of its verbs, never from its store. The record lives in
the foundation's bus, not in the store, which stays each context's private database reached only with that
context's own credential, so no store holds a record shared between contexts and the circularity does not
return. No module owns the record whole: each event and each bucket belongs to the module that declares it,
which alone writes it. *How it is checked:* where each rule was decided — the runtime's refusal of a bucket
not issued or a write to one only read (ADR 0201), one credential per context's store (ADR 0008), and the
mirrors' events counted live and compared with the store by probe D17 (ADR 0289).
**One of the seven is built.** `inventory` owns a database of that name and holds the node records;
the rest do not exist. What it takes to run any of them — the language, and what must already be
@@ -225,8 +235,8 @@ every public name.
- ~~**The contexts themselves.**~~ **Decided** — seven, by
ADR 0006.
What remains open is narrower and named there: **where the record lives**, which research 006
leaves unresolved because the foundation is the one place it must not go.
What remained open, **where the record lives**, is decided by ADR 0315: on the bus, in the EVENTS
stream and the buckets each module declares, never in a store (above).
- **How far it may be split.** One deployable today. Splitting a context out costs the single
interface a surface depends on
(research 011).
@@ -2,8 +2,11 @@
layer: to-be
status: in-progress
code: [mesh-controller internal/catalogue]
updated: 2026-10-08
updated: 2026-10-12
decisions:
- "ADR 0312"
- "ADR 0319"
- "ADR 0309"
- "ADR 0262"
- "ADR 0225"
- "ADR 0112"
@@ -151,6 +154,24 @@ ADR 0107).
never created, owned or removed by the mesh. The module requires read or read-write access. Where
the data is, is an operator value on the assignment.
*A read-write access writes through the path's own group (2026-10-12, ADR 0312, extending ADR 0051; the
build is tracked by issue 537):* when a module declares a `read-write` access, the node-engine reads the group
that owns the path as it applies, and makes the identity the module's code runs as a member of it: a
container's process gains it as a supplementary group, and an account that runs the module's tools is put in it
as ADR 0252 puts an account in a group. The module then writes the path as the path's group may, and as nothing
else. The path itself is never changed: no owner, group or mode is set on it or under it, and a read-write
access whose path's group cannot write is stated unhealthy, saying that the operator decides. A `read` access
grants no group. A path owned by group 0, or by a group that would let the identity act as root, is refused by
name. A group the node-engine granted is taken back when no declared read-write access asks for it any more,
never one the identity held before (ADR 0252 §3). A container is recreated with the group, and a service of
the machine's own service manager that runs the module's tools as an account is restarted once the account
holds it; a unit of the account's own service manager, or a login session, waits for a new login as ADR 0252
§5 and ADR 0254 say. The tool runner's account so gains the groups of every read-write access of the modules it
serves: the boundary is the account's, not the module's. *How it is checked:* the node-engine's tests ADR 0312
lists (the group given and the path's owner, group and mode unchanged; a read access granting nothing; an
unwritable group unhealthy and group 0 refused; the group taken back; the container recreated and the system
unit restarted holding the group).
**A port** is what ADR 0038 already decided: the
module says which port its software uses, and the node-engine answers with where the machine put it.
@@ -212,6 +233,26 @@ gains a setting by shipping its current value as the default, and `settings` lis
source: the default, the mesh, or the node. The first module to use it is the notifier, whose font size
and width are preferences. *How it is checked:* the table in ADR 0262.
*A value several modules share is set once (2026-10-12, ADR 0309, changing ADR 0164 §7 and §8 and ADR 0262
§3, §7, §8 and §10, and ADR 0319, changing ADR 0164 §4 and §9 and ADR 0309 §2, §3, §5 and §6; the build is
tracked by issue 415):* a module keeps every setting it takes, and a preference may also name a mesh-wide
**shared name**, such as `desktop.font-size`. Unset in every layer of its own module, the preference
reads the shared value, and only when that is unset too its default; read from the bottom, last
winning: the default, the shared value, the module's mesh-wide layer, the node's. So a person sets
the interface font size once and every module that names it follows, while any one module or node
can still be set apart in its own layer. A shared value is a fourth place of change beside the three
of ADR 0164 (a new default no longer reaches an assignment whose shared value is set), and the plan
of a change to it names every assignment, of every module that reads it,
whose effective value moves. It is set and cleared through the `settings` verb without a module, and
both `settings` listings name the shared name as a value's source beside the default, the mesh and the
node. **A shared value reaches no trusted setting** (ADR 0319): a trusted setting may not declare a
shared name, and registration refuses one that does, so a shared value is never trusted and needs no
warrant; a trusted setting is set in its own module's layer, on the operator's warrant (ADR 0277) or at
the terminal, as before. A trusted setting that a stored manifest still declares with a shared name
never reads the shared value, and both listings say why beside it. Whether a shared value is a
number, a colour or a string waits for the typed-settings work (issues 346 and 410). *How it is
checked:* the tables in ADR 0309 and ADR 0319.
*The operator's value travels only where it is asked for (2026-09-30,
[issue 173](../../04-ISSUES/173-a-modules-settings-reach-every-fact-it-contributes/00-report.md)):*
a setting overrides a key a contribution or a served fact declares and adds none; a file keeps taking
@@ -12,8 +12,9 @@ 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-07
updated: 2026-10-12
decisions:
- "ADR 0307"
- "ADR 0240"
- "ADR 0233"
- "ADR 0201"
@@ -260,6 +261,17 @@ them on the bundle's channel — refusing, with the reason, a name it was not is
bucket it only reads. A watch hands the current values first, then every change: a bundle that starts
late, or starts again, has the whole of the state before it has any of the news.
*A key's kept values are read too (2026-10-12, ADR 0307, extending ADR 0201).* A bucket keeps as many past
values per key as its owner declared, and the runtime serves them: `history`, given a state and a key, answers
every value the bucket still keeps for that key, oldest first, each with its revision, whether it was a put or
a delete, and when it was written; an empty answer for a key it holds nothing for. It is a read like `get`:
served to the owner and to every reader, refused with the reason to a module the bucket was not issued to, and
it writes nothing. The Go SDK's `State(name)` carries it as `History(key)`, beside `Get`. Its first user is the
issue tracker's switch to its log (ADR 0300 §6), which carries every value its bucket still keeps; that work is
tracked by issue 502. *How it is checked:* the runtime's tests (several values and a delete answered oldest
first, with revision, operation and time; an empty answer for an unknown key; a reader served, a module the
bucket was not issued to refused, nothing written) and the SDK's tests of `History`.
The owner says how many past values a key keeps and how long a value lives, as a seat says how long
its backlog survives (§3); the mesh caps a value's size and a bucket's. **A bucket outlives its
module's assignment** — what a module stored is data
@@ -2,8 +2,9 @@
layer: to-be
status: in-progress
code: [mesh-host, mesh-controller, mesh-catalog]
updated: 2026-10-09
updated: 2026-10-12
decisions:
- "ADR 0310"
- "ADR 0268"
- "ADR 0252"
- "ADR 0203"
@@ -88,6 +89,7 @@ WP4 the service manager's module, finished (mesh-catalog) independ
WP5 assign and prove (operator-gated) needs WP1–WP3 merged and rolled
WP6 a module puts the account in a group (mesh-host, mesh-controller, mesh-catalog) issue 247
WP7 the control-node withholds execute (mesh-controller, mesh-catalog) ADR 0268
WP8 a node's files and accounts are read (mesh-sdk, mesh-catalog, mesh-controller) ADR 0310, issue 418
```
WP1, WP2 and WP4 are independent, and are built in parallel on one feature branch per repository
@@ -334,3 +336,33 @@ out, the control-node's value is set to `withhold` and zsh is pushed to it. Then
2. the control-node's `zsh_config` answers `execute` as not served, and says why;
3. `mesh_search` for `node-login-shell execute` names the other three nodes and not the control-node;
4. a call to the control-node's `node-login-shell.execute` is refused, and the other nodes still answer.
## WP8 — A node's files and accounts are read through `node-reads`
*`mesh-sdk`, `mesh-catalog`, `mesh-controller`. ADR 0310, extending ADR 0268; tracked by issue 418.*
ADR 0268 §5 has narrow read tools take the common reads over from `execute`, so that an approval is asked for an
act and not for a read, and ADR 0286 §44 sets the approval mark on `execute` only once those reads have a tool on
every node. Two had none: reading or searching a file, and looking up an account or a group.
- **A node seat, `node-reads`, on every node**, the control-node included, held by the tool runner; the
controller holds the seat's definition beside the other node seats.
- **Three reads:** a file read bounded in size, a search by fixed string bounded in matches and in depth, and a
lookup of an account or a group. Each verb says what it replaces (ADR 0245): `cat`, `grep`, `getent passwd`,
`getent group` and `id`. The bounds are set by the build and stated in each verb's description.
- **It reads as an account of its own**, which the mesh creates on every node, in no group that holds secrets,
with no login shell and no home of the operator's, so the node's file modes bound what it sees. `/proc` and
`/sys` are read as far as that account may.
- **Secret places are refused by name** before any read: the vault's directories, every module's own-secret
directory and the known credential files, whatever the file modes would allow.
- **One content rule.** The rule the operator channel applies to what it sends moves into `mesh-sdk`, and both
the operator channel and every `node-reads` answer apply it.
Once the seat answers on every node, the approval mark on `node-login-shell.execute` is set as ADR 0286 §44 says,
under the same issue.
**Proof.** The rows of ADR 0310's *how it is checked*: the controller's seat listing names a holder of
`node-reads` on every node; the tool runner's tests of the bounds, the account and the refusal by name; one
`mesh-sdk` test of the content rule and one in each of its users; `mesh_search` for `cat`, `grep` and
`getent passwd` finding the verbs. Live, the kernel's RAID status file is read through the seat on the home
server, and the agent's credentials file on the laptop is refused.
@@ -2,8 +2,9 @@
layer: to-be
status: in-progress
code: [mesh-catalog, mesh-controller, mesh-host]
updated: 2026-10-10
updated: 2026-10-12
decisions:
- "ADR 0303"
- "ADR 0215"
- "ADR 0173"
- "ADR 0040"
@@ -120,6 +121,20 @@ not write into its include directory. Each contribution is a dependency on the s
so assigning one of them without a window manager is refused. The first versions, which still write
the include files themselves, move to contributions once the controller derives the dependency.
**A package may be declared by several modules** (2026-10-11, ADR 0303, changing ADR 0210 §1, §10 and §11
for packages). Two modules on one node that each need a program may each declare its package, and it is
present while any of them declares it. Declaring a package asserts only that it is present: the node-engine
installs one the machine lacks, never upgrades it, and never removes it because a declaration went away, so a
second declaration changes nothing on the machine and taking one module off leaves the package to the others.
A module that needs a program another seat holder installs may declare that package itself, or still depend on
that seat. One module declaring a package present while another declares it absent (ADR 0180) on one node is
refused at assignment and at composition, naming both. A tool's configuration files and the contributions to
its seat keep their one owner, as above: only the package itself may be declared by several modules. *How it
is checked:* the controller's tests (two declarations of one package are assigned; present against absent is
refused, naming both modules) and the node-engine's tests ADR 0303 requires (a package declared twice is
installed once, one no module declares any more is not removed, an installed one is reported unchanged and not
upgraded).
**Added 2026-10-04.** `triggerhappy` holds `node-hotkeys` on both workstations (ADR 0212). The
laptop model's vendor keys become its contribution. The window-manager fragments of the launcher, the
clipboard, the wallpaper, the bar and the laptop model become `config` contributions to
@@ -4,7 +4,7 @@ status: in-progress
code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab]
updated: 2026-10-12
decisions:
- "ADR 0318"
- "ADR 0314"
- "ADR 0293"
- "ADR 0289"
- "ADR 0285"
@@ -376,23 +376,7 @@ section; to-be 46 §13 builds what follows from it.
(warning); on `condition-cleared`, by editing the first message where the channel can. A silenced
condition sends nothing. Urgent goes to both channels; warning to the desktop notifier when the
operator's session is there, otherwise to Telegram.
*Amended 2026-10-12, ADR 0318:* **history is never news.** On start, every ten minutes, and at once
when an event older than its last reading arrives, the holder reads the open conditions from the
controller and takes them as its state; an event is acted on only when it is newer than that reading
(five seconds of clock slack) and its own `at` is no more than ten minutes old — otherwise it is
recorded as state and never said. A condition open in the holder's state but absent from the
controller's open set ended while the holder was away, and is closed quietly: edited where a channel
can, never sent to wake a desktop. A clearing of something never said says nothing, and a message
still waiting is dropped the moment its clearing is read. Every urgent condition a reading finds open
and unsaid is said once, together, naming all of them. A grace minute follows every start or
reassignment: nothing is sent in it except that one message.
- **Rate:** at most twenty messages an hour; the excess is folded into one message naming them all.
*Amended 2026-10-12, ADR 0318:* a channel holds what it has to say for thirty seconds from the first
item and sends it together — alone as itself, several as one digest, one line each; an urgent item
breaks a thirty-second quiet at once. The desktop keeps a gentler rhythm than Telegram: a warning
reaches it at most once every fifteen minutes, never an urgent one. Reaching the cap is said once, in
its own message, and what it holds back waits for the next digest the hour allows, rather than one
message per excess item.
- **What may leave the mesh:** roles and words. A message carrying an address, a path or anything
shaped like a secret is refused by the holder and raises `channel-refused` instead.
*Amended:* the mesh's own machine names may appear too; a concrete detail travels as a reference only
@@ -548,6 +532,22 @@ the controller's replay R301 and its tests of the rule).
> its cause, so the exception is read from the verbs. Nothing decided changes: ADR 0230's acts stay
> exempt, and pushes by hand — what roll-out by default (ADR 0236) exists to end — still count.
**A condition no healer can fix becomes an issue** (2026-10-12, ADR 0314, extending ADR 0285; the build is
tracked by issue 409). A condition no healer can fix is one whose kind no healer answers, one a healer passes
over because it does not apply, or one handed to the operator after a healer's budget was spent. When such a
condition is raised, passed over or handed on, and no issue links its key (ADR 0285), the issue tracker opens
one itself, from the controller's condition events, with the condition's summary, its evidence and its key, and
links the key at once; the controller sets no link. Every message about the condition then names the issue
through its tracking line. Agent sessions take such an issue as any other: a claiming move that names the
worker, a branch, review and the required checks; no standing agent acts on a condition by itself. What an
agent does about it is written in the issue's notes, and every act it makes by hand on the mesh is also written
to the hand-act log, which S15 reads. ADR 0275 §9's week stands: the tracker opens no issue for a condition
before 2026-10-17 20:17 UTC, and until then such a condition is linked by whoever opens an issue for it. *How
it is checked:* the tracker's tests feeding condition events (one issue opened and linked for each of the three
cases, none for a condition a healer acts on or one already linked, none before the week ends); live, the
messenger's preview of such a condition says "Tracked as issue N", and the issue's notes and the hand-act log
hold what the agent did.
## 8. Staged core upgrades and rollback (rule 8)
**Health, per core component**, as probes in the registry (§4):
@@ -898,4 +898,3 @@ in [to-be 47](47-delivery-from-commit-to-delivered.md), and their progress is tr
- Routing by presence, quiet hours, answering back through a channel, and an external dead-man
service — decided by ADR 0234,
designed in [to-be 46](46-the-conversation-with-the-operator.md).
- A condition that needs judgement handed to an agent as work — tracked by issue 409, since research 017 closed.
@@ -2,8 +2,9 @@
layer: to-be
status: in-progress
code: [mesh-controller, mesh-tools, mesh-sdk, mesh-catalog]
updated: 2026-10-10
updated: 2026-10-12
decisions:
- "ADR 0308"
- "ADR 0286"
- "ADR 0277"
- "ADR 0259"
@@ -289,6 +290,40 @@ Presence facts are events on the bus. The router keeps them as **current state o
machine and per intake kind, overwritten — never as a history, and never in a message's words. A
consumer that wants a timeline of the operator's day is a decision of its own.
### While a delivery walks
*2026-10-12, ADR 0308; the build is tracked by issue 532.*
A delivery restarts what it moves, and those restarts raise conditions that clear once it is done; a router
that restarted used to say again what it had been told to keep quiet (issue 531). So the router holds what a
walk touches and tells the walk's result once.
- **A walk names what it touches.** The controller's walk events say, when a walk starts and again when it
ends with its outcome (delivered, failed or stopped), the modules it moves and the machines it sends them to.
What it touches is those modules on those machines, and every module a moved tool runner or node-engine
carries there.
- **The router holds what an open walk touches.** A message whose subject is a touched module or machine is
held instead of sent, kept with when it was held, and `history` says it was held. A message several open
walks touch is held until the last of them ends or its bound passes.
- **Some messages are never held:** an ask (§6), a secret given or replaced, a message about anything no open
walk touches, and a walk's own failure.
- **A walk's end is told once:** one message with its outcome, the modules and machines it reached and how
long it took, then each held message whose condition is still open, one line each. One whose condition
cleared during the walk is only counted. A walk delivered with nothing held and nothing open is one line, or
nothing on a channel set to say only what needs the operator.
- **A hold has a bound:** twice the largest delivery budget in force among the walk's deliveries
([to-be 47](47-delivery-from-commit-to-delivered.md)): 20 minutes for a core module, 10 for leaf modules at
a five-minute budget and 6 at a three-minute one; 15 minutes, not doubled, for a walk none of whose
deliveries has a budget. Past it, every held message still open is sent with a line saying the walk still
runs and since when, and the cleared ones are counted. A walk that hangs never silences the mesh.
- **A restart says nothing it has said:** a router that restarts says again only what is open and was never
answered, never a silenced message or one withheld for its content.
*How it is checked:* the controller's tests of the walk events, and the router's tests of holding, never
holding, the end message, the bound and the restart, as ADR 0308 lists them; live, the next delivery of the
tool runner gives the operator one message at its end, and the channel's `history` shows what was held and
released.
### The content rule
The router's, applied before anything reaches any holder; §9 says what it allows. A `private` holder
@@ -2,8 +2,10 @@
layer: to-be
status: in-progress
code: [mesh-controller, mesh-catalog modules/mesh-delivery]
updated: 2026-10-11
updated: 2026-10-12
decisions:
- "ADR 0313"
- "ADR 0306"
- "ADR 0299"
- "ADR 0298"
- "ADR 0282"
@@ -102,7 +104,11 @@ delivery merged with no walk answering it within ten minutes is held too, with t
forge says that later merge commit contains it (`gitea_contains`, asked once per pair and kept). Merge
order is only the first filter, and a forge that cannot answer means not yet covered. `mesh/delivery`
says `covered: a later merge that contains it was delivered` as a success. A module that runs on one
machine only gets no "rest" step and is never covered;
machine only is covered too: its walk records the empty rest as sent once its first machine passes
(ADR 0270, as corrected 2026-10-12);
- *a stop whose walk ended without building its modules* (issue 474) stays `stopped`, a final state, and
is read as covered once a later merge covers it by the same test: `mesh/delivery` then says `covered after
its stop` with the covering merge, once, as a success. No state or transition is added (issue 480);
- the covered row is tried before `go`, from `held` and from `ready` past the ten minutes.
**History is retired, not held** (ADR 0250).
@@ -217,6 +223,14 @@ shown and held to no budget. Measured on 51 walks on 2026-10-09 and 10, a leaf w
(7 of 20 within five), a core walk of one tier 5.6 (worst 8.8), and a controller walk dragging two catalogue tiers
14.2 (issue 338).
*A wider measure, around the delivery budget (2026-10-12, ADR 0313, extending ADR 0282).* The delivery budget
above runs from the merge; the operator's own target is wider, the **fix time**, which runs from an issue's
first move to `fixing` to the moment its last fix is delivered. Each issue's class carries a **fix budget** too
(five minutes leaf, ten core, the delivery budget's own figures), and the tracker splits a fix time into four
phases — implementing, review, checks, and merge to running (the delivery time above) — so a fix over its
budget is said loud, naming its longest phase, and clears at the next fix of its class delivered within budget.
The delivery budget stays as written: a measure inside the fix budget, not replaced by it.
**What changes in the walk.**
- **A lone merge is cut at once**: no batch assembling, and no member of its delivery group still unmerged.
@@ -232,6 +246,32 @@ shown and held to no budget. Measured on 51 walks on 2026-10-09 and 10, a leaf w
- **Every phase is kept**: the window, the wait, the build, the send, each reading, the rest's first healthy
report; the plan shows an expected landing time from the last seven days' medians.
*Walks beside each other (2026-10-12, ADR 0306, extending ADR 0304, which narrowed the bullets above to what
a change shares).* Building the walks at once found what those bullets left open, and ADR 0306 decides it:
- **A walk's builds are its own, never another's backlog.** A send composes a module whose build another open
walk owns at the build its node was last sent, and neither carries nor judges it: that build is moved and
judged only by its own walk, so a failed walk puts back only its own builds. What waits on a node with no open
walk to move it is still backlog, carried and judged by the next gated send there (this changes ADR 0236 §17
for builds an open walk owns).
- **A delivery group still assembles.** A merge whose delivery group has a member not yet merged waits for the
merge window whether or not a batch is assembling, so a group's members walk in one batch, in their order.
- **Related means everything the walks would move.** A module in common is any module a walk plans to build or
send, the dependents of what its merges moved included; a change whose modules are not known yet is related to
every change. Two related walks are neither cut beside each other nor judged beside each other on one node.
- **A starved judgement is said.** A leaf judgement set aside by core builds three times in a row raises a
warning naming the walk and the core walks that set it aside.
- **A node's own fault fails every judgement on it.** A node found unwell as a whole (a failed apply, an
unhealthy network or engine) fails every judgement open on it, leaf and core, each walk putting back only its
own builds.
- **A rollback beside another walk's judgement** behaves as it does today; whether it should wait is tracked by
issue 528.
*How it is checked:* the controller's tests ADR 0306 lists, one per clause: an unsent build of walk A left out
of walk B's send and not taken up as backlog while A is open; a group's second member joining its first's batch;
overlapping dependents keeping two walks apart; the warning on the third set-aside; a failed apply failing every
open judgement on its node.
**Who builds what.**
| Part | Owner | What it adds |
@@ -307,7 +347,7 @@ In this order, each by its owner:
| Verb | Answers or does |
|---|---|
| `deliveries` | every delivery not final, and the final ones of the last day, one line each; filtered by state, repository or group |
| `deliveries` | every delivery not final, the final ones of the last day, and every final one `stalled` still lists, one line each; filtered by state, repository or group |
| `show` | one delivery or group whole: plan, transitions, machine steps, order, the walk |
| `groups` | every group with its members in order and its derived state |
| `what-if` | the delivery plan a diffset would have, asked of the controller's planner, kept nowhere |
@@ -6,8 +6,9 @@ code:
- mesh-catalog modules/artifact-store-tools (the store's tools), modules/distribution (its unbuilt code removed)
- mesh-catalog modules/gitea (package retention)
- mesh-catalog modules/docker (image pruning)
updated: 2026-10-08
updated: 2026-10-12
decisions:
- "ADR 0311"
- "ADR 0251"
- "ADR 0189"
- "ADR 0257"
@@ -110,7 +111,8 @@ ADR 0257:
failed one, and the controller keeps a row per copy in `artifact_mirrored`. The copies the existing
build records already named were recorded by the migration that made the table.
- **Kept by the kept builds that stood on it** (`stood-on`), and by nothing else: a definition never
keeps a copy. Otherwise a copy is eligible, like any artifact the mesh made.
keeps a copy. Otherwise a copy is eligible, like any artifact the mesh made. *(A copy of a pinned
third-party image is the exception, below: ADR 0311 §3.)*
- **Held whole.** A copy counts as held only when its index and every platform the index names are
held; otherwise it is copied again. A copy a build copies again is no longer recorded as let go of.
- **An index goes with its platforms only in a confirmed collect.** Before its first delete, it reads
@@ -122,6 +124,35 @@ ADR 0257:
`unrecorded_mirrors`, with what it alone marks. The controller's `mirrors` verb records the ones a
person names, and the sweep then decides about them.
### Pinned third-party images
*2026-10-12, ADR 0311, extending ADR 0257; the build is tracked by issue 421.* A third-party image a module
pins was held only upstream, and an image withdrawn there was found only when a node that lacked it tried to
deploy (issue 113). So the mesh holds a copy of what its modules pin, and checks both sources:
- **Every pinned third-party image is copied** into the artifact store when it is first used, into the
repository of its upstream image (`upstream/<host>/<path>`, as above).
- **Its record names the pin.** The copy's row names, as its reason, the module whose definition pinned the
digest (changing ADR 0257 §6, which named only a build or a person's reason).
- **Kept while a module pins it.** A copy is kept, with the reason `pinned`, while any module's current
definition pins its digest; neither the sweep after a build nor the nightly clean-up lets it go. For these
copies a definition does keep a copy, and one is eligible only when no kept build stood on it and no current
definition pins it (changing ADR 0257 §8, §9 and §20). `artifact_store_references` shows them with the reason
`pinned`.
- **A node pulls from the mesh first**, from the artifact store's copy, and from the upstream only second.
- **A scheduled check pulls every pinned image as an empty node would**, once from the copy and once from the
upstream. An upstream that no longer serves while the copy does raises a warning condition naming the module
and the image; neither serving raises an urgent one; each clears once the image is served again. An upstream
that serves while the copy does not is not decided yet (issue 421); a node then still pulls from the upstream.
- **A module may declare its upstream**, the object store's module first, with its maintained fork, and a view
shows each declared upstream's last release and open advisories, so ADR 0288's triggers of no release for six
months and of an advisory unpatched for 30 days can be read there.
*How it is checked:* the table in ADR 0311 — the controller's copy and its row, the collection test of keeping
by pin, the node-engine's pull from the store with the upstream unreachable, the check's two conditions, the
manifest test of a declared upstream, the view against a recorded answer, and live, the object store's pinned
image held in the store.
### The controller's verbs for the store
- **`artifacts`** reads only. It answers every reference the mesh recorded making, with its state and,
+24 -4
View File
@@ -2,9 +2,10 @@
layer: to-be
status: designed
code: []
updated: 2026-10-11
updated: 2026-10-12
decisions:
- "ADR 0290"
- "ADR 0321"
---
# 55 — The knowledge base
@@ -125,8 +126,17 @@ approval. On `approve`:
On acceptance the record and its clauses move to `accepted` and `in force`, every clause its `changes` and
`supersedes` name moves to `changed` or `superseded`, and `accepted` and `changed` events are emitted. Then the 4
rules that judge the whole record set run (an accepted decision cited by nothing, and the like); a failure raises a
condition naming the rule and the records, and does not undo the acceptance.
rules that judge the whole record set run (an accepted decision cited by nothing, and the like). A failure does not
undo the acceptance. It is a fault of the records, which their authors repair, not of the module: it is told to the
operator once, as a warning naming the rule and the records, and cleared once the rule holds again. The module's
health names it and stays healthy, because health says only whether the module works, and a finding never holds a
walk at the module's first-node gate (the operator's decision of 2026-10-12 on issue 504, as issue 508 decided for a proposal never
reviewed).
The rule `cited` is also judged ahead, in the review, before the judgement: a decision that names no home and that no
accepted record links is not accepted. Its review, by the knowledge base itself, asks changes naming the rule (issue
504). It is not refused at `propose`, because no record can link a number only reserved: a record that cites a new
decision is proposed after it and accepted, and the new decision is proposed again.
An accepted record is changed only by a new record, or by a progressive insight: a new version marked as one, which
is reviewed like a proposal.
@@ -148,6 +158,14 @@ per clause whether the record says exactly that; an accepted clause becomes `con
same way. When every record of a topic has been through both, the operator is told once per topic, with the counts
confirmed and still drafted. `return` with a record's number and a reason sends it back to be drafted again.
**A drafted clause may be changed, and a linked clause keeps its number** (ADR 0321, issue 494). A later record's
`changes` may name a drafted clause, confirmed or not, as it may one in force or changed; only a superseded or proposed
clause is refused. On that record's acceptance the drafted clause is marked `changed`, naming the clause that changed
it, keeps its number and stays `drafted`: it is still never served as in force, and `get` and `ask` say both. Because
a redraft numbers a record's clauses anew, `return` refuses a record any of whose clauses another record changed or
links by number, in a version served or proposed, naming each such link; a record that changes the drafted clause is
the way to correct it. A record no other record links by clause is returned as before.
## 8. The import
`import` takes a commit of this repository (and `dry-run`) and writes every decision record and research effort
@@ -184,10 +202,12 @@ repository keeps no copy of the records, generated or otherwise (issue 435, answ
|---|---|
| A proposal that breaks a per-record rule is refused, naming every reason | the module's table test, one case per rule of § 4 |
| A record is accepted only after the knowledge base's own judgement approves it, and, when it needs the operator, only on the warrant | the module's tests: a proposal whose judgement answers changes, or cannot be had, stays proposed; a `needs operator` record unaccepted without the warrant, accepted with it |
| A whole-set rule that fails is loud | the module's test: an acceptance that leaves a decision cited by nothing raises the condition |
| A decision is not accepted cited by nothing | the module's test: a decision with no home and no record linking it gets changes from the review, with no judgement asked; with a home, or once a record citing it is accepted, it is accepted |
| A whole-set rule that fails is loud, and leaves the module healthy | the module's test: a correction that leaves a decision cited by nothing tells the operator once, as a warning naming the rule and the record, not again after a restart, and clears it once a record cites it; the module's health stays healthy throughout |
| Every version is kept and comparable | the module's test: three versions, `history` lists them, the difference of the first and third is exact |
| The import changes nothing on a second run, and loses nothing | the module's test over a fixture repository; and the comparison of § 8 on issue 426 |
| A drafted clause is never served as in force | the module's test: `holds` over a record with only drafted clauses answers none, and `ask` marks them |
| A drafted clause may be changed, and a linked clause is never renumbered | the module's tests: a record changing a drafted clause is accepted, and the clause is then `changed`, keeps its number, stays drafted and is not served; `return` of a record whose clause another record changed or links by number is refused naming the link, and writes nothing |
| 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 |