Designs 51, 26, 32: answer the second review by architectural domain (ADR 0611, caller identity, node-engine reports, families and retention, Go references); add each design's review record

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-03 18:08:00 +01:00
parent 20157fcafe
commit 9845b78e5b
3 changed files with 104 additions and 34 deletions
+8
View File
@@ -395,3 +395,11 @@ checked as their tables say:
| The uplink's step-back comes from the seat | 0287: the composition tests for the uplink and the resolver on one machine pass with the case in the collision check removed. |
| Holdings are derived, and the overview lists every seat | 0118: the `seats` command test, including an unheld seat. |
| A build source on the seat records no address; an unheld seat refuses only self-hosted builds | 0111's tests. |
## Reviewed by architectural domain
The amendment of 2026-11-03 for research 081 (ADR 0605 to 0611; *A seat's definition*) was reviewed with designs 51
and 32 by the four reviewers by architectural domain; the findings and what answers them are listed in design 51's
*Reviewed by architectural domain*. For this design: only the trunk's definition is the contract (security), the
indexer learns out-of-step types from the check's verdict event and runs no generator (seams), findings go to the
condition keeper (health), a pull request's entries are removed when it closes (running qualities).
@@ -498,24 +498,30 @@ complete, because a team that believes it is complete stops reviewing for the ca
(ADR 0609 §4.) A config value's declaration may name its old name, `renamed-from`. On the merge, mesh-config moves the
value it holds under the old name to the new name in its own state, without the operator giving it again, emits
`renamed` (module, old name, new name, when) and records it. This design decides what ADR 0609 leaves open:
`renamed` (module, old name, new name, when; its family `config.events`, kept seven days) and records it. This design decides what ADR 0609 leaves open:
- **Which values**: a module's own config values only, never a secret, never another module's value. A rename keeps
the value's trust: a value set without the operator's Approve stays untrusted under its new name (ADR 0348 §8), and
the checker refuses a rename that would change a value's trust.
- **How long the old name is read**: until every node-engine reports applied a build of the module that reads the new
name, as the node-engine seat's reports answer; until then mesh-config answers both names. A later change may drop
- **How long the old name is read**: until every node-engine whose node has a want of the module reports applied a
build of it that reads the new name: mesh-config hears the node-engine seat's `applied` events, and asks each node's
`node-engine.haves` at its start; until then mesh-config answers both names. A later change may drop
`renamed-from` only after that, and the checker refuses it before. The checker also refuses `renamed-from` in any
manifest until a build of mesh-config that understands it is delivered.
manifest until a build of mesh-config that understands it is delivered, which it learns from the node-engine seat's
`applied` events for mesh-config. `config_renames` names, per rename, the nodes still holding the old name open.
- **A refused move** (the old name holds nothing, or a secret) is mesh-config's finding, from which the condition keeper
raises the condition. **Tools of mesh-config**: `config_renames` (what moved, from which name, when) and
`config_rename_again` (move one value by hand when a move was refused).
### A value no longer read (from 2026-11-03)
A config value or secret no manifest reads any more is marked retiring by mesh-config, which emits `retiring` and asks
the operator once, through the operator channel, whether to remove it; it is removed only on the operator's Approve.
`config_retiring` lists what is retiring and since when.
A config value or secret no manifest on any trunk reads any more (mesh-config learns it from the indexer's
`indexer_config_readers`, on each of the indexer's events that a manifest was taken in) is marked retiring only once no
node-engine reports applied a build that reads it, as the rename's window is (§ above). mesh-config then emits
`retiring` (family `config.events`, kept seven days) and asks the operator once, through the operator channel, whether
to remove it, naming any node that still read it until lately; it is removed only on the operator's Approve. An ask
unanswered for seven days is asked once more, and then written as a finding. `config_retiring` lists what is retiring,
since when, and its ask.
### The mesh delivery cases this design carries
@@ -727,3 +733,10 @@ ruled on every finding. What was found, and what answers it:
condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of
its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate`
as well as `drop`. Each is noted on issue 1966.
The amendment of 2026-11-03 for research 081 (ADR 0606, ADR 0609; the package, rename, retiring and delivery-case
sections of § 8) was reviewed with designs 51 and 26 by the four reviewers by architectural domain; the findings and
what answers them are listed in design 51's *Reviewed by architectural domain*. For this design: a rename keeps a
value's trust (security), `packages` is a section of its own, not `uses`, and mesh-config hears the node-engines'
`applied` events (seams), a value retires only once no node-engine runs a build reading it (security), and `renamed`
and `retiring` have a family and an ask that is asked again (events).
@@ -31,9 +31,10 @@ extending ADR 0189.)
## What is published, and how (from 2026-11-03)
**Every registry is a seat; a build module publishes into it over the bus; the builder gives every version.**
(ADR 0605, ADR 0606, ADR 0607, ADR 0610, from research 081.) What follows is the to-be shape; the sections from
(ADR 0605, ADR 0606, ADR 0607, ADR 0610, ADR 0611, from research 081.) What follows is the to-be shape; the sections from
*The parts* on keep what the registries hold, and stand. Every interaction below is a seat verb, a module's tool or an
event: no module reads or writes another's bus state (ADR 0596).
event: no module reads or writes another's bus state (ADR 0596). A module asks another's data through its tools, and
asks work of another by an event of its own (ADR 0610 §1).
### The four registry seats
@@ -47,18 +48,24 @@ event: no module reads or writes another's bus state (ADR 0596).
Each is held once for the mesh. The last two replace the seat `mesh-artifact-store` and front the one artifact store;
while one module holds both, both deliver the provision `artifact-store`, and splitting them between two holders takes a
decision of its own. `mesh-artifact-store` stays served until every node-engine reports applied a build that reads the
new names (ADR 0610 §4); a sleeping node holds it open. `cargo-package-registry` stays named, without a holder, until a
new names (ADR 0610 §4); a sleeping node holds it open. `distribution` closes it itself: it hears the node-engine
seat's `applied` events (and asks each node's `node-engine.haves` at its start), stops serving the old name once every
node whose declaration names an artifact reports the new build, and its tool `registry_old_name` lists the nodes still
holding it open. `cargo-package-registry` stays named, without a holder, until a
build module publishes cargo packages.
**One contract, a seat definition** (design 26, *A seat's definition*). Verbs: `publish`, `versions`, `remove` (a
tagged version only; the holder itself refuses `remove` of a version with no tag), and on `container-image-registry`
`mirror` and `tags`. Events: `published`, `refused` (added to ADR 0605 §2's two, additively) and `removed`, each
carrying the name, version, kind, tag, digest, fingerprint, commit and holder, and `refused` its reason. A holder stores
carrying the name, version, kind, tag, digest, fingerprint, commit and holder, and `refused` its reason; each seat's
events are its own family, `<seat>.events`, kept seven days. A holder stores
under the version it is given and counts nothing.
**Who may call which verb is the holder's to say**, in a trusted config value of the holder listing its callers, the
form ADR 0363 gives a restricted family: `publish` the build modules; `mirror` mesh-build-image; `remove` the builder.
Declaring `invokes` alone grants nothing. The holder answers any other caller with a refusal naming the verb.
Declaring `invokes` alone grants nothing. The holder checks the caller identity the tool runner proved and stamps on
the request (design 25 § 12.4), never a name in the request's body, and answers any other caller with a refusal naming
the verb.
### Publishing
@@ -74,18 +81,20 @@ Declaring `invokes` alone grants nothing. The holder answers any other caller wi
builder (durable consumer: entry, done key), indexer, status view, checker
```
- **The file travels in the verb.** `publish` opens with every part's digest (an image's layers, a package's file,
a bundle's blob); the holder answers the digests it lacks, and the build module sends only those, in chunks no
larger than the bus's message limit, into the holder's own store. This is how ADR 0605 §3's hand-off holds under ADR
0596: no store is reached by two modules. The holder hashes every part it received and refuses one whose bytes do
not match its digest; a part it never received completed is dropped after 24 hours. The holder's tool
`registry_incoming` lists what is in transit, its fill against a 20 GiB cap, and drops one by hand; past 80 % of the
cap the holder writes a finding.
- **The file travels in parts through the seat** (ADR 0611). `publish` opens a transfer, naming it and the digest of
every part (an image's layers, a package's file, a bundle's blob); the holder answers the digests it lacks. The build
module sends only those through `publish-part` (transfer, part digest, sequence, last), each at most 1 MiB, with
mesh-sdk's bus helper for sending in parts, added to mesh-sdk's runtime in step 2's rebuild. A `publish` opening a
transfer already open answers the parts held, so a cut transfer goes on from there. The parts land in the holder's
own object store `incoming`, declared in its manifest with a cap of 20 GiB and a lifetime of 24 hours; a transfer with
no part for 24 hours is dropped and its parts deleted. The holder hashes every part and refuses one whose bytes do not
match its digest. At 80 % of the cap it writes a finding (cleared below 80 %); at the cap it refuses a new transfer
with `refused` naming the cap. Its tool `registry_incoming` lists open transfers and the fill, and drops one by hand.
- **The holder asks the builder**, through `builder_versions`, whether the name, version and digest are recorded, and
refuses loudly otherwise (ADR 0605 §4). A rebuild of a held fingerprint answers the held version and stores nothing.
- **How a publish ends**: the holder emits `published` or `refused`. The build module waits for one of them, at most
10 minutes, narrating "waiting on <seat>" at least every five minutes; past the bound it writes the key refused,
naming the seat. A `published` that arrives after that is still heard by the builder on its durable consumer: the
naming the seat, and the builder writes the finding for a refused publish. A `published` that arrives after that is still heard by the builder on its durable consumer: the
entry turns `published` and the builder writes the done key then, so a late store is never lost.
- **What is published when**: a package or an image from a trunk build under its version, or as a tagged version; a
bundle or an archive from every done key by its digest alone, a candidate's included, and at the trunk's claim the
@@ -106,28 +115,32 @@ Declaring `invokes` alone grants nothing. The holder answers any other caller wi
- **The builder numbers every trunk build** of a package, image, bundle or archive at its claim: the package series
its manifest declares (design 32; `0.1` when it declares none) and the next count for that name, under
compare-and-set. Numbers follow the order of the forge bridge's merge events: a claim of a later merge waits until
every earlier merge of the same name is numbered. A fingerprint already numbered keeps its number.
every earlier merge of the same name whose fingerprint the indexer still holds is numbered (a fingerprint replaced
by a later merge before its claim is skipped, design 47 *After the merge*); a wait past 30 minutes writes a finding
naming the merge it waits on. A fingerprint already numbered keeps its number.
- **The builder's state `versions`** holds one key per name, tag and version, with its fingerprint, commit, digest,
time and state: `numbered`, `published`, `refused` with its reason, or `removed` with the time, the reason (a
branch's end, retention, the sweep) and the event that removed it. History one, cap 1 GiB; past 80 % the builder
writes a finding. Only the builder reaches it; others call `builder_versions` or hear its events. The build log
writes a finding, cleared below it. Only the builder reaches it; others call `builder_versions` or hear its events. The build log
carries each change too.
- **The floor**: on a name's first claim, or when `versions` holds nothing for it, the count starts above the highest
of the seat's `versions`, the package's highest git tag (the forge bridge's tool `bridge_tags`) and the highest
pseudo-version a consumer requires (the indexer's tool `indexer_requirements`). The builder numbers nothing for that
name until all three answered, and writes a finding naming the silent one meanwhile. Today that gives Go `v0.1.14`
name until all three answered, and writes a finding naming the silent one meanwhile, cleared once all three answer. Today that gives Go `v0.1.14`
and npm `0.1.18`; a name with none starts at `0.1.0`.
- **Events of the builder**: `numbered` (name, version, tag, fingerprint, commit, head when it answers an ask), on its
own family, kept seven days; a waiter on a build's end does not wake on it. **Tools of the builder**:
`builder_versions` (a name's versions in order, one call), `builder_publish_again` (one fingerprint under its number,
after a holder failed; when the build's file is gone it builds the fingerprint again under the same number; it
refuses a version refused for other bytes), `builder_tag` (ask a tagged version), `builder_tag_asks` (open asks and
after a holder failed; when the build's file is gone it builds the fingerprint again under the same number, and the
new digest replaces the recorded one only if no `published` was ever emitted for that number, else it takes a new
number; it refuses a version refused for other bytes), `builder_tag` (ask a tagged version), `builder_tag_asks` (open asks and
their state) and `builder_untag` (remove a branch's tagged versions).
### Tagged versions
- **Asked by an event, never by a push** (ADR 0610 §1). The checker emits `tag-wanted` (repository, branch, head,
tag) for each head of a delivery group member that declares a package; a person asks through `builder_tag`. The
tag), on a family of its own, `checker.asks`, kept seven days, apart from its verdicts, so a waiter on a verdict does
not wake on it, for each head of a delivery group member that declares a package; a person asks through `builder_tag`. The
builder consumes the event on a durable consumer and keeps the ask in its own state `tag-asks`, removed when its
branch is merged or deleted. An ask the builder has not answered within five minutes is narrated in the build log
with what it waits on.
@@ -145,12 +158,16 @@ Declaring `invokes` alone grants nothing. The holder answers any other caller wi
The retention of *The package registry* below, and the artifact sweep, keep working, now through the seats: each
removal emits `removed` and the builder marks its entry, so `versions` never says `published` for bytes that are gone.
The Go seat keeps what the npm seat keeps: every referenced version and the newest five of each package series.
The Go seat keeps what the npm seat keeps: every referenced version and the newest five of each package series. A
Go version is referenced when a `go.mod` on a repository's default branch requires it; the forge module reads every
`go.mod` as it reads lockfiles, and a `go.mod` it could not read stops a real run before anything is deleted, as an
unread lockfile does.
### Health and conditions
Observers write findings; the condition keeper raises and clears the conditions from them (ADR 0564). Findings: a
publish refused (the holder; cleared when it is stored), a registry seat not answering (each holder's health check,
publish refused (the holder when it refuses, the builder when a build module's bound ran out; cleared when that version
is stored, or when a later version of the name is published), a registry seat not answering (each holder's health check,
which calls `versions` on each seat it holds and probes the read side every minute, so a dead seat is seen with no
publish running), a removal not done (the builder), a floor source silent (the builder), a store past 80 % (the holder,
the builder). A build narrates its publish: asked, waiting on a seat, stored, refused.
@@ -159,9 +176,11 @@ the builder). A build narrates its publish: asked, waiting on a seat, stored, re
Each step is its own delivery, and no credential goes before its replacement works (ADR 0605 §7).
1. The holders serve every verb of the four seats with their caller lists; the builder's `versions`, `tag-asks`, its
1. A node-engine that reads both reference forms and both seat names; the holders serve every verb of the four seats
with their caller lists, and `incoming`; the builder's `versions`, `tag-asks`, its
events and tools; the forge bridge's `bridge_tags` and the indexer's `indexer_requirements`.
2. Every build module publishes through the seats: mesh-sdk's first numbered publish, images, bundles, archives and
2. Starts only once every node-engine reports step 1's build applied (a sleeping node holds it). Every build module
publishes through the seats: mesh-sdk's first numbered publish, images, bundles, archives and
upstream copies, each read back; delivered with the move of mesh-sdk's seat contracts into `mesh-contracts` (design
26), so every module is rebuilt once for both. The checker emits `tag-wanted`. Until step 3, Go users fetch from the
forge's git, where the builder's tags let them take the numbered version.
@@ -169,7 +188,8 @@ Each step is its own delivery, and no credential goes before its replacement wor
4. The three credentials go, and `package-version.sum`. The old seat name retires after it, on the node-engines'
reports.
Steps 1 to 3 are rolled back by reverting their pull request; step 4 by reverting it, which mints the build modules'
Steps are rolled back in reverse order. Steps 1 to 3 are rolled back by reverting their pull request; step 4 by
reverting it, which mints the build modules'
credentials again. **On a fresh mesh** the foundation's bootstrap (research 075) pushes the holders' own images with a
one-off credential of the bootstrap, discarded once the holders serve `publish`.
@@ -189,11 +209,40 @@ test).
after the build's file is gone builds again under the same number; with the seat's holder stopped at a first claim
nothing is numbered and the finding names it; two merges claimed in reverse order are numbered in merge order; a
retention removal turns the entry `removed`.
- **Holder (gitea, distribution).** A caller not on the caller list is refused for each verb; a part whose bytes do
- **Builder, added in round 2.** Two merges close together, the first replaced before its claim: the second is
numbered at once; a build module's bound running out writes the builder's finding, and a later version of the name
clears it; `builder_publish_again` after a `published` takes a new number.
- **Holder (gitea, distribution).** A caller not on the caller list is refused for each verb, and one module naming
another as the caller in the request's body is refused; a transfer cut in the middle resumes from the parts held,
and one left 24 hours is dropped; at the cap a new transfer is refused naming the cap; a version a `go.mod` requires
survives retention, and an unreadable `go.mod` stops the run; a part whose bytes do
not match its digest is refused; only the missing layers are sent; `remove` of a version with no tag is refused;
with no publish running, a stopped Go seat writes its finding and clears it when it answers; `registry_incoming`
with no publish running, a Go seat that stops answering while its holder runs writes its finding and clears it when it
answers (a holder down whole is its module's own health check's); `registry_incoming`
lists and drops.
- **Vault.** Reverting step 4 mints the build modules' credentials again.
- **Vault.** Reverting step 4 mints the build modules' credentials again; the bootstrap's one-off credential is gone
once the holders serve `publish`.
- **Live.** Step 2 waits while one node-engine has not reported step 1's build; `registry_old_name` names the nodes
holding the old name open.
## Reviewed by architectural domain
The amendment of 2026-11-03 (ADR 0605 to 0611, research 081's graduation, designs 51, 26 and 32 together) was reviewed
by the four reviewers by architectural domain side by side (playbook 01), and the lead ruled on every finding. Two
findings reached a decision and became records: ADR 0610 (a module's bus state is its own, ADR 0596, so the checker
asks by an event and holders ask the builder's tool; retention; the old seat name closes on what node-engines applied)
and ADR 0611 (the file travels in parts through the seat). What was found, and what answers it:
| Domain | Found | Answered by |
|---|---|---|
| security | caller lists named only on the caller's side; the handed object's bytes unchecked; no caller identity; pull request seat definitions served as contracts; the bootstrap's credential | *Who may call which verb* (holder-side lists, the identity the tool runner proved); *Publishing* (parts hashed); design 26 (only the trunk's entry is the contract); *The move* (a one-off bootstrap credential) |
| upgrade | the steps moved no image, bundle or archive before their credentials went; Go users between steps 2 and 3; no node-engine reading the new forms before step 2 | *The move* (every move in step 2, a node-engine reading both forms in step 1, step 2 waiting on the node-engines' reports, git tags for Go, reverse-order rollback) |
| seams | five reaches into another module's bus state; a shared hand-off store; how a publish ends; how the indexer learns out-of-step types; no named path for the node-engines' reports | ADR 0610, ADR 0611; *Publishing* (`published` or `refused`); design 26 (the check's verdict event); `applied` and `node-engine.haves` |
| tools | no read or drop tool for asks, transfers, the old name's window | `builder_tag_asks`, `registry_incoming`, `registry_old_name`, design 32's `config_renames` and `config_retiring` |
| running qualities | the versions value growing; the floor with a part down; merge order and a replaced merge; windows closing on wants; retention against "never removed"; a Go reference undefined; the in-transit cap | *The versions* (one key per version, a cap, the floor's wait, merge order skipping a replaced fingerprint); *Retention* (a Go reference); *Publishing* (the cap refused) |
| testing | failure paths untested; the test budget | cases of each module's one whole-module integration test, listed under *Acceptance tests* |
| health | conditions raised by their observers; no health check of a seat between publishes; a refused publish with no clearing | findings for the condition keeper (ADR 0564); the holder's health check; *Health and conditions* (each finding with what clears it) |
| events and logs | removal and `published` failing silently; a publish silent for 10 minutes; events with no family or retention | the builder's durable consumers and acknowledgement rule; narration every five minutes; each event's family and seven days |
## The parts