|
|
|
@@ -0,0 +1,495 @@
|
|
|
|
|
---
|
|
|
|
|
layer: to-be
|
|
|
|
|
status: in-progress
|
|
|
|
|
design: 47
|
|
|
|
|
milestone: G
|
|
|
|
|
updated: 2026-11-11
|
|
|
|
|
decisions:
|
|
|
|
|
- "ADR 0672"
|
|
|
|
|
- "ADR 0668"
|
|
|
|
|
- "ADR 0667"
|
|
|
|
|
- "ADR 0666"
|
|
|
|
|
- "ADR 0664"
|
|
|
|
|
- "ADR 0626"
|
|
|
|
|
- "ADR 0619"
|
|
|
|
|
- "ADR 0596"
|
|
|
|
|
- "ADR 0595"
|
|
|
|
|
- "ADR 0573"
|
|
|
|
|
- "ADR 0564"
|
|
|
|
|
- "ADR 0539"
|
|
|
|
|
- "ADR 0500"
|
|
|
|
|
- "ADR 0429"
|
|
|
|
|
- "ADR 0404"
|
|
|
|
|
- "ADR 0283"
|
|
|
|
|
- "ADR 0259"
|
|
|
|
|
- "ADR 0235"
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 47, milestone G: develop's fifth batch, and round 3's notes on §516 to §559
|
|
|
|
|
|
|
|
|
|
One file of milestone G's part of [design 47](../47-delivery-from-commit-to-delivered.md); the part opens at
|
|
|
|
|
[G.md](G.md). Its design lines are numbered across every file of the part and cited as "design 47 G §n".
|
|
|
|
|
|
|
|
|
|
*§560 to §584 answer develop's fifth batch of questions (lead-47's input of 2026-11-11, after round 3 of the review
|
|
|
|
|
of [G-12](G-12-develop-answers-fourth.md) and [G-14](G-14-recovery-module-and-review.md)), the reviewers' round-3
|
|
|
|
|
notes on §516 to §559, and the knowledge base's review of ADR 0672 (the recovery module). Six carry the operator's
|
|
|
|
|
answers of 2026-11-11 at lead-47's terminal, each line saying so: §562 (the recovery module's move), §563 (a put-off
|
|
|
|
|
dump), §568 (the builder's retry on an event), §570 (refused asks listed) and §575 (a failed build's words). The
|
|
|
|
|
rest are of internal form only: they change nothing the operator sees, nobody's permissions and no data kept or
|
|
|
|
|
removed. A note that corrects a line of G-12 or G-14 is written in that line; here only what is new.*
|
|
|
|
|
|
|
|
|
|
## The recovery module
|
|
|
|
|
|
|
|
|
|
- §560 **Every event of the recovery module says its act, and its caller wakes on its own act.** (§554, §516, §551)
|
|
|
|
|
Every event of the seat `mesh-recovery` carries `act`, the id `snapshot-kept` or `restore-kept` answered with
|
|
|
|
|
`accepted`, or a hand tool's own act id (§566), and `at`, the UTC time it was said in RFC 3339. Each event's other
|
|
|
|
|
fields:
|
|
|
|
|
- `ended.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint`, `snapshot`, `outcome` (`done` or `failed`),
|
|
|
|
|
`error` when failed, and on `done` the point whole (§516).
|
|
|
|
|
- `restore-started.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint` and `snapshot`, said before the
|
|
|
|
|
restore reads the dump's item events and before any aside is taken (§563).
|
|
|
|
|
- `restored.<module>.<kept>`: `module`, `kept`, `form`, `fingerprint`, `snapshot`, `outcome`, `error`, `aside` and
|
|
|
|
|
`missing` (§516).
|
|
|
|
|
- `set-aside.<module>.<kept>` and `aside-discarded.<module>.<kept>`: the aside's `id`, `name` and `size`.
|
|
|
|
|
- `point-removed.<module>.<kept>` as §551 has it; and `expiry-ended`, a subject without parts, with `removed`, the
|
|
|
|
|
number of points the run removed, 0 included.
|
|
|
|
|
- **Every act ends with an event under its `act`.** `snapshot-kept` and `recovery_take` end with `ended`;
|
|
|
|
|
`restore-kept`, `recovery_restore` and `recovery_restore_aside` (§566) with `restored`; `recovery_aside` with
|
|
|
|
|
`set-aside`; `recovery_discard_aside` with `aside-discarded`; a run of §551's removal (at the start, once a day,
|
|
|
|
|
at a `snapshot-kept` or by `recovery_expire_points`) with `expiry-ended`, also when it removes nothing;
|
|
|
|
|
`recovery_retry` with the ending event of the act it asks again, under an `act` of its own; `recovery_cancel`
|
|
|
|
|
(§566) with the cancelled act's ending event, `outcome` `failed` and `error` `cancelled`.
|
|
|
|
|
- **An act a restart cut off** (accepted and written in the directory, never ended, §554) is ended at the module's
|
|
|
|
|
start, after the start's repair of asides (§516, §564), with its own ending event under its `act`, `outcome`
|
|
|
|
|
`failed` and `error` `stopped`.
|
|
|
|
|
- **A restore runs** from its `restore-started` until a `restored` with the same `act`; the backup holder reads it
|
|
|
|
|
so (§554, §563).
|
|
|
|
|
- **The caller wakes on its own act.** The node agent told `accepted` wakes only on an `ended` or a `restored`
|
|
|
|
|
whose `act` is the one it was given; a `form` and a `fingerprint` alone wake nothing, since a retried act's old
|
|
|
|
|
`failed` carries the same ones. Told `busy` with the running act's `act`, it wakes on that act's ending event
|
|
|
|
|
and asks again, and its new answer gives the `act` it waits on next.
|
|
|
|
|
- **The 90-minute bound** of the node agent's wait (§554) counts from its first ask for that point or restore,
|
|
|
|
|
every `busy` and every ask again within it. Past it, the node agent's have says the wait and the watchman's
|
|
|
|
|
`delivery-waits` raises it.
|
|
|
|
|
|
|
|
|
|
This replaces §554's list of events. Why: an event a caller cannot tie to its own call wakes it on another's
|
|
|
|
|
failure, and an act whose end is never said holds its caller unseen (round 3, health and events, items 1 and 2;
|
|
|
|
|
running and testing, item 3). How it is checked: unit (mesh-recovery's contract), each event's fields read; whole
|
|
|
|
|
(recovery module), a retried act's caller not woken by the first act's `failed`, an expiry run that removes nothing
|
|
|
|
|
says `expiry-ended` with `removed` 0, an act cut off by a restart ends `failed` with `stopped`; unit (node agent),
|
|
|
|
|
`busy`, then the other act's ending event, then an ask again within one bound, and the bound passed after repeated
|
|
|
|
|
`busy` answers.
|
|
|
|
|
- §561 **The recovery module's and the backup holder's findings, each with its scope, id and clearing.** (§554) Each
|
|
|
|
|
is written by mesh-sdk's finding writer (§461) as the key `<scope>.<id>.<kind>` (ADR 0564 §2), each token escaped
|
|
|
|
|
as a key's name is (§73), and said on the part's finding stream (§383, §529):
|
|
|
|
|
- `points-filling`: the recovery module's, scope `mesh-recovery`, id the node it runs on. It is written when the
|
|
|
|
|
directory of its data item `points` passes the fill mark of its cap (§564), measured after each act and at each
|
|
|
|
|
of its disk readings, naming the size, the cap and the five largest points and asides. It is cleared when a
|
|
|
|
|
measure finds it below the mark.
|
|
|
|
|
- `recovery-waits`: the recovery module's, scope the module whose act waits, id the act's `act`. It is written
|
|
|
|
|
60 minutes after the act began waiting on the dump of the bus's item (§563), naming the item and since when. It
|
|
|
|
|
is cleared when the act starts its work, or ends.
|
|
|
|
|
- `backup-waits`: the backup holder's, scope its node, id the item that waits (`<module>.<item>`, one escaped
|
|
|
|
|
token). It is written 60 minutes after the item was put off (§563), naming the restore's `act`. It is cleared
|
|
|
|
|
when the item starts again.
|
|
|
|
|
- `aside-unrestored`: the recovery module's, scope the module, id the aside's `id`. It is written when its health
|
|
|
|
|
check finds an aside whose record ends at `removed` and that no running act holds (§554). It is cleared when that
|
|
|
|
|
aside's record ends at `restored` or the aside is discarded.
|
|
|
|
|
|
|
|
|
|
The recovery module and the backup holder of G are observers on the condition keeper's list of sources (§387,
|
|
|
|
|
§388), written into it in G's change. Why: a finding no one can clear stays open for ever, and the four findings
|
|
|
|
|
of §554 named no scope, id or clearing (round 3, health and events, item 4). How it is checked: whole (recovery
|
|
|
|
|
module, backup holder, the keeper), one whole test writes and then clears each: the points directory filled past
|
|
|
|
|
the mark, then an aside discarded; an act held by a dump past the bound (a setting, §557), then started; a dump
|
|
|
|
|
held past it, then started; an aside left at `removed` with no running act, then restored by
|
|
|
|
|
`recovery_restore_aside`.
|
|
|
|
|
- §562 **The recovery module moves as any module that holds data; its points and asides hold no move.** (§554) The
|
|
|
|
|
operator, 2026-11-11 about 14:15 CET at lead-47's terminal: the recovery module may be moved off its node at any
|
|
|
|
|
time, and the restore points and asides in its directory do not hold the move. A placement that moves it is made as
|
|
|
|
|
for any module that holds data. The assigner asks for the move a person must allow before it writes the want (ADR
|
|
|
|
|
0539 §14). The old node's want is written as removed, and its data item stays on that node's disk, with its copies
|
|
|
|
|
in that node's repository, until the operator answers (ADR 0429 §6). On its new node the recovery module starts
|
|
|
|
|
with an empty directory: `kept-points` lists nothing of the old node's, and a `restore-kept` of a point left there
|
|
|
|
|
answers `missing`. Its `from-backup: yes` then asks the new node's backup holder, which holds no copy of it, so
|
|
|
|
|
`restored` says `failed` with `restore point missing` and §300 holds the revert. The placement verb asks the
|
|
|
|
|
recovery module nothing, and §554 no longer refuses a move while a point younger than 90 days is listed. Why: the
|
|
|
|
|
operator's answer (the knowledge base's review of ADR 0672, R6). How it is checked: unit (assigner), a placement
|
|
|
|
|
that moves the holder of `mesh-recovery` while points are listed is made, asked as ADR 0539 §14 has it; whole
|
|
|
|
|
(recovery module), on an empty directory a restore of an unknown point says `restore point missing`.
|
|
|
|
|
- §563 **When a dump and a restore start at one moment, the dump gives way.** (§554) Each side says its start before
|
|
|
|
|
it reads the other's:
|
|
|
|
|
- **The backup holder** of G, for the item of the holder of `mesh-broker` (read from the seat list's definition
|
|
|
|
|
and the catalog half's claim, §554), says `item-started`, then reads `mesh-recovery`'s `restore-started` and
|
|
|
|
|
`restored`, last per subject, once more. When a restore runs (§560), it ends the item at once with `item-ended`,
|
|
|
|
|
`outcome` `put-off` and `restore` naming that act, having read nothing of the bus, and starts the item again when
|
|
|
|
|
it hears `restored` with that `act`.
|
|
|
|
|
- **The recovery module** says `restore-started`, then reads the item events of the bus's item. While an
|
|
|
|
|
`item-started` has no `item-ended`, it waits, and takes no aside.
|
|
|
|
|
|
|
|
|
|
Since each side says its start before it reads, at least one side sees the other, and the backup holder always
|
|
|
|
|
gives way, so the two never overlap. A dump that has run since before the restore began is waited for. The backup
|
|
|
|
|
holder ends every item of its own that it said started and never ended, at its start, after its read of §556, with
|
|
|
|
|
`item-ended`, `outcome` `failed` and `error` `stopped`, so no restore waits on a dump that no longer runs. Every
|
|
|
|
|
`item-started` and `item-ended` carries `at`, and `item-ended` its `outcome` (`done`, `failed` or `put-off`).
|
|
|
|
|
- **A put-off dump is that night's backup.** The operator, 2026-11-11 about 14:05 CET at lead-47's terminal: an
|
|
|
|
|
item ended `put-off` is neither a failed night nor a missed one (ADR 0235 §13, §21 hold): the item runs when
|
|
|
|
|
`restored` is heard, and the night is the outcome of that run. The item's last good backup ages as before, so
|
|
|
|
|
`backup-stale` is raised only by its own age rule, never for the put-off itself. `backed-up` (§302) lists a
|
|
|
|
|
put-off item with the restore's `act` and since when; `backup-waits` names it after 60 minutes (§561).
|
|
|
|
|
|
|
|
|
|
Why: §554 left open which side gives way when both start at once (round 3, running and testing, item 1); how a
|
|
|
|
|
put-off item counts is the operator's answer (the knowledge base's review of ADR 0672, R5). How it is checked:
|
|
|
|
|
whole (backup holder, recovery module, the real bus), a dump and a restore asked at one moment never overlap and
|
|
|
|
|
the item runs after `restored`; a put-off item raises no `backup-stale` and is not counted failed; a backup holder
|
|
|
|
|
restarted during an item ends it `failed` with `stopped`, and a waiting restore then starts.
|
|
|
|
|
- §564 **The cap is a setting, an act that would pass it is refused before it removes anything, and a failed act
|
|
|
|
|
leaves no half-made point or useless aside.** (§554, §557)
|
|
|
|
|
- **The settings.** The cap of the data item `points` is the recovery module's setting `points-cap`, 10 GiB unless
|
|
|
|
|
set, and its fill mark the setting `points-filling-mark`, 80 % unless set, both read at its start, as §557 has a
|
|
|
|
|
bound.
|
|
|
|
|
- **Before it writes.** Before it writes anything, `snapshot-kept`, `recovery_take` and the aside of
|
|
|
|
|
`restore-kept` add up the bytes the bus states for each bus object they take. An act that would pass the cap
|
|
|
|
|
ends `failed` with `error` `cap`, naming the cap, that size and the directory's fill, before any aside is taken
|
|
|
|
|
and so before any live object is removed. A `snapshot-kept` that ends so holds the move as a failed backup does
|
|
|
|
|
(§296). A point or an aside that passes the cap while it is taken (the stream grew) ends its act the same way.
|
|
|
|
|
- **On a failure.** An act that ends `failed` removes its point under a temporary name (§516) then, not only at
|
|
|
|
|
the next start. An aside whose record ends at `set aside` (the live objects were never removed) is removed when
|
|
|
|
|
its restore fails or stops before the removal, and at the start, since the live data was never touched.
|
|
|
|
|
|
|
|
|
|
Why: the cap is a bound a whole test sets (§557), and a restore refused for size after the removal would leave the
|
|
|
|
|
module's data only in an aside (round 3, running and testing, item 2 and its notes). How it is checked: whole
|
|
|
|
|
(recovery module, with small settings), a snapshot passing the cap ends `failed` and holds the move; a restore whose
|
|
|
|
|
aside would pass the cap is refused with the live data untouched; `points-filling` written past the mark (§561); a
|
|
|
|
|
failed point leaves no temporary name; an aside left at `set aside` by a stopped restore is removed at the start.
|
|
|
|
|
- §565 **The grants part reads the restore points' names through `kept-points`, and composes without them until it
|
|
|
|
|
answers.** (§554) The bus names of the recovery module's account include those a restore point names (§554). The
|
|
|
|
|
grants part reads them by its `invokes` of `seat:mesh-recovery.kept-points`, a kept call (ADR 0668 §1). It asks
|
|
|
|
|
again on every `ended` with `outcome` `done` of `mesh-recovery`, which it consumes (ADR 0668 §4), so a point's
|
|
|
|
|
names are granted before any restore of it. Until `kept-points` first answers, the grants part composes the
|
|
|
|
|
recovery module's account from the catalog half's kept entries alone (§443) and says so in `grants_first_views`
|
|
|
|
|
(§556). The edges between the two (kind 5, the grants part has acted for the recovery module's account; kind 6,
|
|
|
|
|
the recovery module serves `kept-points`) are a cycle of delivery edges, delivered as one step (§49). Why: the
|
|
|
|
|
account must follow the points without the grants part reading another part's state (ADR 0596; round 3, seams and
|
|
|
|
|
tools' notes). How it is checked: unit (grants part), an account composed from the catalog half alone while
|
|
|
|
|
`kept-points` is unanswered, and a point's old-section names added after an `ended`.
|
|
|
|
|
- §566 **Two more hand tools: put an aside back, and cancel a waiting act.** (§554, §516) Each is of class `act`,
|
|
|
|
|
with callers `operator` and `sessions`, checked by the identity of who asked (§554), written first in the hand-act
|
|
|
|
|
log of the seat `mesh-hand-acts` with the caller the bus proved and `why`, and an act of its own that runs one at a
|
|
|
|
|
time (ADR 0672):
|
|
|
|
|
- `recovery_restore_aside` (`module`, `kept`, `id`, `why`) restores an aside under the live names it was taken from,
|
|
|
|
|
for an aside whose record ends at `removed` that no running act holds (the case `aside-unrestored` names, §561).
|
|
|
|
|
It refuses, naming them, while live objects of those names exist, and it ends with `restored`.
|
|
|
|
|
- `recovery_cancel` (`act`, `why`) ends an accepted act that has not begun its work (one waiting on the dump, §563)
|
|
|
|
|
with its ending event, `outcome` `failed` and `error` `cancelled`. It refuses an act that has begun, naming its
|
|
|
|
|
step, since a restore cut off after its removal is put back only by its own failure path (§516).
|
|
|
|
|
|
|
|
|
|
Why: a module's tools recover what a person may need to recover (*Tools are how a module is spoken to*; round 3,
|
|
|
|
|
seams and tools' notes). How it is checked: whole (recovery module), an aside left at `removed` put back by
|
|
|
|
|
`recovery_restore_aside` and refused while the live names exist; a waiting act cancelled and a running one refused.
|
|
|
|
|
- §567 **A restore point is checked by the digest of its bytes before it is restored.** (§516) A point's record
|
|
|
|
|
carries `digest`, the `sha256:` digest of each bus object's snapshot file, written when the point is renamed whole
|
|
|
|
|
(§516). `kept-points` lists it, and `ended` carries it with the point. Before a restore takes any aside, it computes
|
|
|
|
|
the digest of the bytes it is about to restore, whether from its own directory or brought back by the backup
|
|
|
|
|
holder (`from-backup: yes`). It compares that digest with the one `ended` carried when the seat's stream still
|
|
|
|
|
holds that point's `ended`, and with the record's otherwise. A mismatch ends the act `failed` with `restore point
|
|
|
|
|
damaged`, naming the point and the bus object, and §300 holds the revert. Why: a point brought back from the backup
|
|
|
|
|
was restored unchecked (round 3, security and upgrade's notes). How it is checked: whole (recovery module, backup
|
|
|
|
|
holder), a point whose file was altered after it was taken is refused before any aside is taken.
|
|
|
|
|
|
|
|
|
|
## The builder
|
|
|
|
|
|
|
|
|
|
- §568 **A transport fault in the builder's own read ends no build key, and a key's error says which kind of fault it
|
|
|
|
|
is.** (ADR 0664 §1 to §3, ADR 0668 §1 to §3; issue 1612, met again 2026-11-11)
|
|
|
|
|
- **The builder's own reads.** The builder's reads of the build seats' state `steps` (its own step keys, §467,
|
|
|
|
|
§481) are kept calls (ADR 0668 §1, §2). A read that meets a transport fault, of ADR 0664 §1's classes or the
|
|
|
|
|
read's own deadline, changes nothing the builder holds and writes no build key. The builder's watch of `steps`
|
|
|
|
|
takes up again from its last revision once the bus answers, and a read it made at its start is made again then.
|
|
|
|
|
Past five minutes it writes `call-unanswered` (ADR 0668 §3). A build key is written `failed` only from a step
|
|
|
|
|
key's end (ADR 0664 §3).
|
|
|
|
|
- **The words of a failed key.** A step that ended failed because its third run met a transport fault (ADR 0664
|
|
|
|
|
§2) gives the build key an `error` that opens `transport fault:`, then the class (`no responders`, `name lookup`,
|
|
|
|
|
`connection` or `deadline`), the step's seat and node, and the cause of its last run in the build module's words.
|
|
|
|
|
Every other failure keeps its own opening (`not stored:`, `not pinned:`, `version taken:`, `refused kind:` or the
|
|
|
|
|
build's own words) and never opens `transport fault:`. The builder's read tools show the error whole, so a reader
|
|
|
|
|
tells a passing cause from a real one without the journal.
|
|
|
|
|
- **What builds such a key again.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal, decided by the
|
|
|
|
|
record of the builder's retry (number to come), which changes ADR 0664 §3: a key whose error opens `transport
|
|
|
|
|
fault:` is built again, once, as a new revision of the key (ADR 0429 §18), on the first event after its failure
|
|
|
|
|
that says its cause passed. Two events say so: the node-engine seat's `running.<node>.<module>` saying `running`
|
|
|
|
|
for a module whose catalog graph entry claims the build seat the failed step ran on (§546), on any node; and the
|
|
|
|
|
node-engine's `manifest-reads-changed` of the node the failed step ran on (ADR 0668 §4), which it says when that
|
|
|
|
|
node is heard again. The builder hears both through its consumers of the node-engine seat, read as §556 has it.
|
|
|
|
|
It builds a key again at most once per event heard, and only while the key's newest revision is that failure, so
|
|
|
|
|
one event starts one revision. A revision that fails again on a transport fault is said as every failed key is,
|
|
|
|
|
its error opening `transport fault:` again, and waits for the next such event. A key failed for any other cause
|
|
|
|
|
is never built again on these events. `builder_build_again` (ADR 0668 §5) and the builder's next start build it
|
|
|
|
|
again too, as before. The builder's read tool of a key names the event that built its newest revision again.
|
|
|
|
|
|
|
|
|
|
Why: a read the builder made met `no responders` and failed a key, which then stayed failed for about 45 minutes
|
|
|
|
|
(issue 1612, 2026-11-11), against ADR 0664 §3 (a key is not written failed while its step runs on); the retry on an
|
|
|
|
|
event is the operator's answer. How it is checked: unit (builder), a `no responders` read of `steps` leaves the key
|
|
|
|
|
`building` and the next revision ends it; a third-run transport fault's key error opens `transport fault: no
|
|
|
|
|
responders`, and a compile error's does not; a `running` of a holder of that build seat builds the key again once,
|
|
|
|
|
and a second `running` while the new revision builds starts nothing; a node's `manifest-reads-changed` builds again
|
|
|
|
|
a key failed on that node; a key failed on a compile error is not built again on either event.
|
|
|
|
|
|
|
|
|
|
## The order of a change
|
|
|
|
|
|
|
|
|
|
- §569 **A cycle with a build edge is refused, whatever its other edges, and a grant composed from a first view is an
|
|
|
|
|
edge.** (§49, §558, §374) The indexer's cycle rule (§49) reads every edge of the six kinds in one graph per watched
|
|
|
|
|
branch. A cycle whose edges are all delivery edges (kinds 2, 4, 5 and 6) is delivered as one step. A cycle with at
|
|
|
|
|
least one build edge (kind 1 or 3) is refused, whatever its other edges, at the check and at take-in, naming every
|
|
|
|
|
artifact on it in order with each edge's kind and the manifest word it came from: no build on such a cycle can
|
|
|
|
|
start before a delivery that waits for that build (ADR 0666 §14). Two kinds of edge enter that graph:
|
|
|
|
|
- **A seat verb a build calls.** When a build module's step calls a seat verb while it builds (the image build
|
|
|
|
|
module calling `container-image-registry`'s `mirror` for a base image), its manifest names it in `invokes`, and
|
|
|
|
|
the indexer derives an edge of kind 3 from that seat's holders to every artifact the build module builds that
|
|
|
|
|
the call serves (each image artifact that names a base image).
|
|
|
|
|
- **A grant from a first view.** A binding the grants part writes for a holder from its first view of a served
|
|
|
|
|
answer (design 25 *bindings-first-view* §4 and §9, being written on the branch `design/25-bindings-from-the-list`)
|
|
|
|
|
is an edge of kind 5 from the grants part to that holder. Where the grants part names a field of that answer
|
|
|
|
|
(§558), an edge of kind 6 runs from the answer's seat holder to the grants part.
|
|
|
|
|
|
|
|
|
|
The circle of 2026-11-11 was such a cycle: the bus module's image waits, by kind 3, for the registry holder's
|
|
|
|
|
delivery; the registry holder waits, by kind 5, for its binding `mirrors` from the grants part; and the grants part
|
|
|
|
|
waited, by kind 6 on `mesh-broker`'s `list` with `binding`, for the bus module's new version. It is refused at its
|
|
|
|
|
check, naming the three artifacts. Design 25 *bindings-first-view* §9 removes the kind-6 edge: the grants part
|
|
|
|
|
composes a binding from the fields `list` already answers, so it relies on no new field and names none (§558).
|
|
|
|
|
G's delivery then puts the grants part before the bus module's build. Why: the circle mixed a build edge and a
|
|
|
|
|
delivery edge, and no rule saw it (issue 1966, notes 203 and 204; the operator, 2026-11-11 13:30 CET, on design
|
|
|
|
|
25's §9). How it is checked: unit (indexer's graph), a cycle of kinds 3, 5 and 6 is refused naming its three
|
|
|
|
|
artifacts and their kinds; a cycle of kinds 5 and 6 alone is one step; a build module's `invokes` of `mirror` gives
|
|
|
|
|
a kind-3 edge to its image artifacts.
|
|
|
|
|
|
|
|
|
|
## Asks to the operator
|
|
|
|
|
|
|
|
|
|
- §570 **An ask the router refuses is heard by its asker, and no asker asks for longer than its level allows.**
|
|
|
|
|
(ADR 0259 §3, §4) The kind behind it is an asker left waiting on an ask the router never took (issue 1610; met
|
|
|
|
|
again 2026-11-11, when the bus module's three `nats_recreate` asks were refused at take-in and their tool kept
|
|
|
|
|
saying the operator had not answered).
|
|
|
|
|
- **The router says the refusal.** The router (the holder of `operator-channel`) says every ask it refuses at
|
|
|
|
|
take-in, for each cause ADR 0259 §4 gives as it holds, on that asker's
|
|
|
|
|
`mesh.seat.operator-channel.event.decided.<asker>`. The event carries `outcome` `refused`, the ask's `id`, `why`
|
|
|
|
|
in the router's words and `at`, as an ask's end without a warrant is said (ADR 0259 §3).
|
|
|
|
|
- **Every asker ends its request on it.** mesh-sdk's asker client gives its caller `refused` with `why` as soon as
|
|
|
|
|
it hears it. An asker's tool or act that waits on that ask answers `refused` with why, never "not answered yet";
|
|
|
|
|
the bus module's `nats_recreate` and `nats_drop` are among them.
|
|
|
|
|
- **The lifetime is held where every ask is made.** mesh-sdk's asker client, through which every asker publishes
|
|
|
|
|
an ask, refuses before it publishes an ask whose expiry passes the bound of its highest option level (`approve`
|
|
|
|
|
24 hours, `destroy` 10 minutes, ADR 0259 §4), naming the level and the bound. The bus module's keeper asks for
|
|
|
|
|
its level's bound in place of its fixed 12 hours (`AskLasts`).
|
|
|
|
|
- **The operator sees a refused ask.** The operator, 2026-11-11 about 14:15 CET at lead-47's terminal: the router
|
|
|
|
|
keeps each ask it refuses at take-in in its bucket `asks` under `<asker>.<id>` as a closed record, with `outcome`
|
|
|
|
|
`refused`, `why` in the router's words and `at`, for 30 days like every closed record (ADR 0259 §3). The seat's
|
|
|
|
|
`asks` lists it with that outcome and words, as an ask that ended.
|
|
|
|
|
|
|
|
|
|
Why: ADR 0259 §4 refuses such an ask on the decided subject and bounds a destroy option at 10 minutes, and neither
|
|
|
|
|
reached the asker; the list is the operator's answer. How it is checked: unit (mesh-sdk's asker client), a table
|
|
|
|
|
over the levels in which an expiry at the bound is published and one past it is refused before it is published,
|
|
|
|
|
and a `refused` that ends the request with why; the code review refuses an ask built outside the client; whole
|
|
|
|
|
(router, bus module), a refused ask is said on `decided`, `asks` lists it `refused` with the router's words, and
|
|
|
|
|
`nats_recreate` answers `refused` with those words.
|
|
|
|
|
|
|
|
|
|
## A module's packages
|
|
|
|
|
|
|
|
|
|
- §571 **A mesh package's entry in a lock file is written by the build, never by hand.** (ADR 0573 §2, §4; §47,
|
|
|
|
|
§478, §504) A module's `package.json` names a mesh package (one whose name §504 maps to a catalog artifact) with the
|
|
|
|
|
range `*`, or with the oldest version it accepts (ADR 0573 §2). Its committed `package-lock.json` may hold any entry
|
|
|
|
|
for that package, or none.
|
|
|
|
|
- **What the build writes.** The Node and TypeScript build module removes every entry of a mesh package from the
|
|
|
|
|
workspace's lock before it installs. It then writes the entry from the version the planner read and the
|
|
|
|
|
integrity the package registry gives for that version (ADR 0573 §2). In a delivery group's check where a member
|
|
|
|
|
publishes the package, it writes the entry from that member's head, packed for the check, with the packed
|
|
|
|
|
tarball's integrity (ADR 0573 §4, ADR 0500 §4). Every other entry is installed as the lock names it and checked
|
|
|
|
|
against its integrity (*Every dependency comes by the version its lock file names*).
|
|
|
|
|
- **Before its first publication.** A package of which no version is published yet (`npm-package-registry`'s
|
|
|
|
|
`versions` answers none, as for the packages G itself publishes first) is reached only in a group's check, from
|
|
|
|
|
its member's head. Outside a group, the edge of kind 1 (§47) builds the dependent after the package's first
|
|
|
|
|
publication, which G's delivery orders.
|
|
|
|
|
- **The check's lock rule** never refuses a mesh package's entry, nor its absence.
|
|
|
|
|
|
|
|
|
|
Why: ADR 0573 §2 has the build write a mesh package's version into the workspace, and a lock made by hand holds
|
|
|
|
|
the integrity of a locally packed tarball, which the published one never matches (develop question of the forge
|
|
|
|
|
module's developer, 2026-11-11). How it is checked: unit (Node build module), a lock naming a local tarball's
|
|
|
|
|
integrity for a mesh package is built with the registry's version and integrity; a lock without the entry is built
|
|
|
|
|
alike; a non-mesh entry with a wrong integrity is refused; whole (G's group check), the forge module's tools are
|
|
|
|
|
built against the head of the member that publishes the package, packed.
|
|
|
|
|
|
|
|
|
|
## The builder's forms
|
|
|
|
|
|
|
|
|
|
- §572 **The builder's process answers a package's name, and its tools ask it.** (§478, §504) The mapping from a
|
|
|
|
|
package's name to its module and artifact is the builder process's own, from its view of the catalog half (§348).
|
|
|
|
|
The seat `mesh-builds`' `reserved`, given `name`, is answered by the builder's process from that view. The
|
|
|
|
|
builder's tools bundle holds no catalog view: `builder_versions`, asked by a name, calls `reserved` (or, for a
|
|
|
|
|
module, the builder's own read verb) through its `invokes` and answers what the process answers. Why: one mapping
|
|
|
|
|
and one view, in the part that keeps them (ADR 0596 §2; develop question BL7's name part). How it is checked: unit
|
|
|
|
|
(builder), `reserved` with an npm name and with a Go module path answered from the view; whole (builder),
|
|
|
|
|
`builder_versions` by name answers the same as `reserved`.
|
|
|
|
|
- §573 **The build seat's ask carries `version`.** (§370, §458) The ask of each build seat (`node-build-go`,
|
|
|
|
|
`node-build-node` and the image build seat) gains the field `version`: the version `MAJOR.MINOR.PATCH` the builder
|
|
|
|
|
reserved for the build (§369), or empty for a build that reserves none (§371, §373). The field is part of the build
|
|
|
|
|
seats' contract package, beside the ask's other fields. The builder fills it when it asks, and each build module
|
|
|
|
|
reads it and passes it as `MESH_VERSION` (§370), passing none when it is empty, so the build stamps none (§458). A
|
|
|
|
|
build module of before G ignores the field and stamps nothing. Why: §370 named no field (develop question of the
|
|
|
|
|
builder's developer on CT5). How it is checked: unit (build seats' contract), `version` read and an empty one
|
|
|
|
|
read as none; whole (Go and TypeScript build modules), a build asked with a version carries it.
|
|
|
|
|
- §574 **`toolchain-agreed` carries `digests` as a list.** (§472, §495) The event's field `digests` is a list, each
|
|
|
|
|
item `{digest, nodes}`: the `sha256:` digest of the toolchain program and the nodes that reported it at the agreed
|
|
|
|
|
version, sorted by digest, with each item's `nodes` sorted. It replaces the field `digest`, which the event no
|
|
|
|
|
longer carries. Why: §472 names one digest per group of nodes, and a list keeps two digests of one version apart
|
|
|
|
|
(develop question of the contracts developer). How it is checked: unit (mesh-builds' contract), the list read, and
|
|
|
|
|
an event carrying `digest` alone refused.
|
|
|
|
|
|
|
|
|
|
## What a node-engine says of a build
|
|
|
|
|
|
|
|
|
|
- §575 **A want whose build is not done says how the build stands.** (§524) The operator, 2026-11-11 about 14:05 CET
|
|
|
|
|
at lead-47's terminal: where the node-engine's have says that a want's build is not done, it says how the build
|
|
|
|
|
stands, read from the build key it already reads for the want's artifacts. A failed build reads `the build of
|
|
|
|
|
<module> at <fingerprint> failed: <the build key's error>`, the error whole, `transport fault:` included (§568). A
|
|
|
|
|
build that runs reads `the build of <module> at <fingerprint> is being built`. The have's phase is `held`, and its
|
|
|
|
|
`wait-held` carries the same words, which the watchman's `delivery-waits` names. "Is not done" is said no more. Why:
|
|
|
|
|
the operator's answer; a failed build said as not done left the operator and the watchers waiting for a build that
|
|
|
|
|
had ended. How it is checked: unit (node-engine), a want whose build key is failed says `failed:` with the key's
|
|
|
|
|
error, and one whose build runs says `is being built`.
|
|
|
|
|
|
|
|
|
|
## The tool runner, and kept data at run time
|
|
|
|
|
|
|
|
|
|
- §576 **A bundle reads its tool runner's capability words through one call.** (§488, §545, §559) The tool runner
|
|
|
|
|
answers the runtime call `mesh/capabilities` with `{words}`, the capability words it states (§545). mesh-sdk's
|
|
|
|
|
bundle, in Go and in TypeScript, makes that call once, at its first call that needs a word, and keeps the answer
|
|
|
|
|
for as long as it runs. A bundle lives no longer than the tool runner that started it (its channel is that tool
|
|
|
|
|
runner's standard input and output), so after a restart of the tool runner the bundle is started again and reads
|
|
|
|
|
the words again. A tool runner of before G answers `mesh/capabilities` as an unknown call, which the bundle reads as
|
|
|
|
|
no word stated, so a call of a family G adds fails at once, naming its word and the node (§488). A bundle learns
|
|
|
|
|
its node from `MESH_NODE`, which the tool runner sets in the environment of every bundle it starts, read alike in Go
|
|
|
|
|
and in TypeScript. Why: §488 named no channel (develop question of the mesh-sdk developer). How it is checked: unit
|
|
|
|
|
(tool runner), `mesh/capabilities` answers the stated words; unit (mesh-sdk, both languages), the words read once,
|
|
|
|
|
an unknown-call answer read as none, and the node read from `MESH_NODE`.
|
|
|
|
|
- §577 **A part reaches the kept data its `requires` declares through its binding, never by an old name.** (§529;
|
|
|
|
|
ADR 0595 §1, §9; ADR 0619 §2)
|
|
|
|
|
- **A bundle** reaches it only through the tool runner's `mesh/` calls by its local name (ADR 0595 §9), which the
|
|
|
|
|
tool runner resolves in the module's membership binding.
|
|
|
|
|
- **A process with an account of its own** reaches it by its binding: mesh-sdk's process package gains `Binding`,
|
|
|
|
|
which takes a local name and gives the entry's bus names (bucket, stream and subjects, object stores). It asks the
|
|
|
|
|
seat `mesh-broker`'s `show` for the module and the local name at the process's start, as a kept call (ADR 0668 §1)
|
|
|
|
|
that the process's `bus` names in `invokes`. It asks again when it hears `mesh-broker`'s `recreated` or `dropped`
|
|
|
|
|
for that entry, which the process consumes. Until `show` answers, the process acts on nothing that needs the entry
|
|
|
|
|
and its health says it waits, naming the entry (ADR 0283).
|
|
|
|
|
- **The old name** (mesh-sdk's opening of an essential bucket by its declared name) is for a foundation part's old
|
|
|
|
|
`state` section alone (ADR 0619 §2). A part that declares an entry in `requires` opens it by `Binding`; the forge
|
|
|
|
|
bridge's `performed` is among them, moved so in G's change.
|
|
|
|
|
- **The validator's rule for the finding words** (§529) accepts the old stream `<module>-findings` only for a part
|
|
|
|
|
on the foundation list (ADR 0619 §3 as ADR 0666 §4 changes it). It refuses that stream for every other part, whose
|
|
|
|
|
finding stream it requires in the template form: `finding-events`, by the template `events`, its entry listing
|
|
|
|
|
`finding-written.*`, `finding-cleared.*` and `findings-stated.*`.
|
|
|
|
|
|
|
|
|
|
Why: a part's kept data is the bus module's to name and the part's to ask for (ADR 0595 §1, §7), and §529 left the
|
|
|
|
|
run-time path and the validator's row open (develop question of the watchman's developer). How it is checked: unit
|
|
|
|
|
(mesh-sdk's process package), `Binding` answered from `show`, asked again on `recreated`, and waiting while
|
|
|
|
|
unanswered; unit (forge bridge), `performed` opened by its binding; unit (manifest validator), a foundation part's
|
|
|
|
|
old finding stream accepted, another part's refused and its template form accepted.
|
|
|
|
|
|
|
|
|
|
## The indexer's forms
|
|
|
|
|
|
|
|
|
|
- §578 **`manifest-digest` carries its prefix, and the key does not.** (§506, §445) The catalog graph entry's field
|
|
|
|
|
`manifest-digest` is `sha256:` followed by the 64 hex digits, as every digest a record names. The module's value
|
|
|
|
|
and §445's `manifests` key stay the 64 hex digits as on main, since a key's name takes no `:` (§73), and mesh-sdk's
|
|
|
|
|
catalog client gives the bare digits as today. The indexer writes all three from the one digest it computed at take-in.
|
|
|
|
|
Why: one digest in two forms, the field's as the records write a digest and the key's as a key's name allows
|
|
|
|
|
(develop question of the indexer's developer). How it is checked: unit (indexer), the entry's field after its
|
|
|
|
|
prefix equals the key's name and the module's value.
|
|
|
|
|
- §579 **The indexer keeps the records pinning each revision, and removes a revision only on an event.** (§153,
|
|
|
|
|
§178, §216)
|
|
|
|
|
- **Where.** The indexer, a foundation part, keeps its old `state` section `pins`: one key per catalog graph
|
|
|
|
|
revision, `<branch>.<revision>` (the watched branch and the revision's id, each escaped as a key's name is, §73),
|
|
|
|
|
whose value is the list of delivery record ids that pin it. A list of ids is kept rather than a count, so a
|
|
|
|
|
`pinned` heard twice pins once. At about 20 revisions pinned a day for 90 days, about 1,800 keys of about 0.2 KiB
|
|
|
|
|
each, it is capped at 4 MiB (an estimate).
|
|
|
|
|
- **Applying `pinned` and `freed`.** The indexer adds or removes a record's id by compare-and-set, and only then
|
|
|
|
|
acknowledges the event on its durable consumer of `mesh-delivery`'s events (§153). A `freed` said while it was
|
|
|
|
|
stopped is applied when it comes back, and one applied twice changes nothing.
|
|
|
|
|
- **Current.** A revision is current while it is the newest revision the indexer took in on its watched branch,
|
|
|
|
|
whichever watched branch that is, or the newest revision of an open candidate (its `candidates.<group>`). It stops
|
|
|
|
|
being current when a newer revision of its branch is taken in, or when the candidate's `heads-replaced` is heard
|
|
|
|
|
(its heads replaced, or its pull request closed) or the candidate is taken in.
|
|
|
|
|
- **Removal.** When a revision's list is empty and it is not current, the indexer removes its revision object and
|
|
|
|
|
its key, and its entry objects as §179 has it. It removes one only on applying `freed` or on the revision ceasing
|
|
|
|
|
to be current, never on a timer.
|
|
|
|
|
- **Inputs.** A catalog graph revision names no input revision. "The input revisions it names" (§178) are an
|
|
|
|
|
assignment graph revision's catalog revisions, which a delivery record pins itself (§153), so the indexer keeps
|
|
|
|
|
each by its own list alone. The assigner keeps its pins of assignment graph revisions in the same form, in its own
|
|
|
|
|
kept data in the form §529 gives its part.
|
|
|
|
|
- **Its tools.** `indexer_pins` reads each kept revision with the records that pin it, whether it is current and
|
|
|
|
|
why, and the store's fill against its cap. `indexer_consumer` reads each of the indexer's durable consumers: its
|
|
|
|
|
stream, name, acknowledged sequence, the messages pending, and the last event applied with its `at`. Both are
|
|
|
|
|
of class `read`.
|
|
|
|
|
|
|
|
|
|
Why: §153 and §178 said what is kept, not where or how it is counted (develop questions IX17 and IX18). How it is
|
|
|
|
|
checked: unit (indexer), a revision pinned twice by one record and freed once is removed; a `freed` applied twice
|
|
|
|
|
changes nothing; a revision of a second watched branch kept while current; a candidate's revision removed on
|
|
|
|
|
`heads-replaced`; whole (indexer, gate), a `freed` said while the indexer was stopped is applied after its start.
|
|
|
|
|
|
|
|
|
|
## Observers with more than one bundle, and the forge module's forms
|
|
|
|
|
|
|
|
|
|
- §580 **Each finding kind belongs to one bundle, and a statement covers only its own kinds.** (§383, §384, §485)
|
|
|
|
|
A module whose bundles write one state `findings` (the forge module has four: its TypeScript tools, its Go package
|
|
|
|
|
registry, its forge and its agent token) gives each finding kind to exactly one bundle. That bundle's mesh-sdk
|
|
|
|
|
writer is made with the kinds it owns, and refuses a `write` or a `clear` of any other kind, naming it. Each bundle
|
|
|
|
|
judges and states its own kinds from its own truth (§384); no bundle reads the whole state to state another's
|
|
|
|
|
findings. Its `findings-stated` carries `kinds`, the kinds it states, and the writer's statement deletes only keys of
|
|
|
|
|
those kinds. The keeper sets its view of that observer, node and set of kinds from the statement, and clears only
|
|
|
|
|
conditions of those kinds that the statement does not name. A statement with no `kinds` (a writer of a part with
|
|
|
|
|
one bundle) covers every kind, as before. Why: a statement that deletes every key it does not state would clear
|
|
|
|
|
another bundle's findings (develop question of the forge module's developer, GIT14). How it is checked: unit
|
|
|
|
|
(writer, both languages), a statement with `kinds` deletes no key of another kind, and a write of another kind is
|
|
|
|
|
refused; whole (keeper), two bundles' statements on one node each keep the other's conditions open.
|
|
|
|
|
- §581 **A `seen` key's 30 days are judged on each step end, with no timer.** (§487) The forge module writes `at` in
|
|
|
|
|
each `seen` key's value, the time of the `step-ended` that wrote it. On each `step-ended` it handles, and at its
|
|
|
|
|
start, it deletes the `seen` keys whose `at` is more than 30 days old, at most 100 for each event, the oldest first,
|
|
|
|
|
so the bus keeps no key time-to-live and no template option is added. The forge module being a foundation part
|
|
|
|
|
(§584), its state `failed-steps` is in its old `state` section, and a bundle reaches it through the tool runner's
|
|
|
|
|
state call by its name, as every old section is reached. Why: §487's 30 days needed
|
|
|
|
|
a trigger, and an event the module already handles is one (develop question of the forge module's developer). How
|
|
|
|
|
it is checked: unit (forge module), a `seen` key older than 30 days is deleted at the next `step-ended`, and a
|
|
|
|
|
younger one is kept.
|
|
|
|
|
- §582 **`reserved` by name answers a released version from the catalog.** (§381, §478, §369) Given `name` and
|
|
|
|
|
`version` with no reservation of that version (§369 deletes a reservation once its version is released), the
|
|
|
|
|
builder finds the module from the name (§572) and asks the seat `mesh-catalog`'s `versions` for that module, as a
|
|
|
|
|
kept call in its `invokes`. When the catalog holds that version released, `reserved` answers `released` with the
|
|
|
|
|
artifacts and the digests the release recorded, and the registry holder answers its publish as held when the
|
|
|
|
|
digest is the release's, storing nothing (§381). A version neither reserved nor released, or a name no entry
|
|
|
|
|
carries, is answered `error`, naming which, and the holder refuses the publish. Why: the holder knows only the name,
|
|
|
|
|
and the module is the builder's own mapping (develop question of the forge module's developer). How it is checked:
|
|
|
|
|
unit (builder), `reserved` for a released version answers `released` with its digests, and an unknown version
|
|
|
|
|
`error`; whole (registry holder, builder), a publish of a released version at its digest stores nothing.
|
|
|
|
|
- §583 **A manifest's first `version` is §368's.** The `version` G writes into the forge module's and the image
|
|
|
|
|
registry module's manifests is the `MAJOR.MINOR` of the version §372 fills for each, read through
|
|
|
|
|
`builder_versions`, and `0.1` for a module with none published (§368); their old values (`"1"` and `"2"`) carry
|
|
|
|
|
nothing over. Why: §368 answers it (develop questions GIT15 and MAN1). How it is checked: as §368.
|
|
|
|
|
- §584 **The forge module and the image registry module are foundation parts.** (§6, §529, §569; ADR 0619 §1 to §3)
|
|
|
|
|
The operator, 2026-11-11 about 14:30 CET at lead-47's terminal: ADR 0619 §1 is followed as written. The bus
|
|
|
|
|
module's build calls both before the bus module can answer, the forge for its source and the image registry's
|
|
|
|
|
`mirror` for its base image, so both are foundation parts.
|
|
|
|
|
- **The list.** The manifest validator's list (§6, ADR 0619 §3) gains the holder of the seat `git` (the forge
|
|
|
|
|
module, `gitea`) and the holder of `container-image-registry` (the image registry module, `distribution`) in G's
|
|
|
|
|
change.
|
|
|
|
|
- **Their kept data.** Each keeps its kept data in its old sections (ADR 0619 §2): its state `findings` and its
|
|
|
|
|
finding stream `<module>-findings` as §529 has a foundation part declare them, and every other state in its old
|
|
|
|
|
`state` section, the forge module's `failed-steps` among them (§581). The image registry module's `holds`,
|
|
|
|
|
`windows` and `mirrors` go back from their template entries to its old `state` section under the names they had
|
|
|
|
|
there. All three are empty on the bus (no key was ever written), so nothing is carried. Their template entries,
|
|
|
|
|
no longer declared, become orphans that `orphans` lists, removed only by the operator's `drop` (ADR 0595 §4, §5),
|
|
|
|
|
as §529 has it for the healers' old stream.
|
|
|
|
|
- **The delivery order.** The running image registry, of its build before G, serves `mirror` only once its bindings
|
|
|
|
|
are composed, and both the bus module's new build and the image registry's own new build ask it for their base
|
|
|
|
|
images. So the grants part with design 25 *bindings-first-view* §9 is delivered first, alone, and composes the
|
|
|
|
|
running registry's three bindings from `list`'s fields (on the branch `design/25-bindings-from-the-list`). Then
|
|
|
|
|
the image registry's and the bus module's builds of G are built through the running registry's `mirror` (edges of
|
|
|
|
|
kind 3, §569), and delivered by the edges of §49. Once the image registry of G runs, its state is in the old
|
|
|
|
|
sections, which the grants part creates as it creates every foundation part's, and no binding of a first view is
|
|
|
|
|
needed for it.
|
|
|
|
|
|
|
|
|
|
Why: the operator's answer; the list and the order follow ADR 0619 §1 and §3 and §569. How it is checked: unit
|
|
|
|
|
(manifest validator), both manifests of G's heads accepted with their old sections, and a template entry in either
|
|
|
|
|
refused; the catalog's validation at G's heads refuses neither; the delivery plan of G puts the grants part's
|
|
|
|
|
delivery before both builds.
|