From 9845b78e5b55cc862f01f0c0c3f92290a5edf77e Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 3 Nov 2026 18:08:00 +0100 Subject: [PATCH] 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 --- 03-DESIGN/01-to-be/26-the-seats.md | 8 ++ .../01-to-be/32-what-a-module-declares.md | 27 +++-- .../51-the-registries-keep-what-is-named.md | 103 +++++++++++++----- 3 files changed, 104 insertions(+), 34 deletions(-) diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 688ba837..58cb32b4 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -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). diff --git a/03-DESIGN/01-to-be/32-what-a-module-declares.md b/03-DESIGN/01-to-be/32-what-a-module-declares.md index 37054c73..e8d68d5e 100644 --- a/03-DESIGN/01-to-be/32-what-a-module-declares.md +++ b/03-DESIGN/01-to-be/32-what-a-module-declares.md @@ -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). diff --git a/03-DESIGN/01-to-be/51-the-registries-keep-what-is-named.md b/03-DESIGN/01-to-be/51-the-registries-keep-what-is-named.md index d3a61983..1211d32c 100644 --- a/03-DESIGN/01-to-be/51-the-registries-keep-what-is-named.md +++ b/03-DESIGN/01-to-be/51-the-registries-keep-what-is-named.md @@ -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, `.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 " 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