From 01a0a3a4d9aa7089200417c12a381a5f1f8cf486 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:02:43 +0100 Subject: [PATCH 01/20] Amend designs 51, 26, 32, 25 and 55 for the registry milestone The registry move, seat definitions, declares, bus state and the knowledge base ship as one milestone (ADR 0612, 0613), ordered by the module graph's edges (ADR 0614), with the foundation parts' first view (ADR 0615) and the overview page (ADR 0616). The designs said four deliveries, a seat-definition source nobody serves, a checker naming mesh-config, and left the bus module's first view, caller and deletion paths open; they now say what the milestone builds. Adds the glossary words it uses, playbook 02's cites link for an abandoned effort, and the hq-design-writer agent definition. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .claude/agents/hq-design-writer.md | 32 +++ 00-META/glossary.md | 32 +++ 00-META/process/01-research.md | 3 +- 00-META/process/02-graduation.md | 9 +- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 137 +++++++++---- 03-DESIGN/01-to-be/26-the-seats.md | 92 ++++++--- .../01-to-be/32-what-a-module-declares.md | 68 +++++-- .../51-the-registries-keep-what-is-named.md | 184 +++++++++++++----- 03-DESIGN/01-to-be/55-the-knowledge-base.md | 91 ++++++++- AGENTS.md | 4 +- 10 files changed, 518 insertions(+), 134 deletions(-) create mode 100644 .claude/agents/hq-design-writer.md diff --git a/.claude/agents/hq-design-writer.md b/.claude/agents/hq-design-writer.md new file mode 100644 index 00000000..ede0564e --- /dev/null +++ b/.claude/agents/hq-design-writer.md @@ -0,0 +1,32 @@ +--- +name: hq-design-writer +description: Writes one set of amendments to hq's to-be designs, glossary and playbooks from accepted decisions and a milestone's findings (hq playbook 02, amending a design; playbook 04 step 4), in a worktree on a branch of its own, ready for the review by architectural domain. Writes only in its worktree; never pushes. +tools: Read, Grep, Glob, Bash, Write, Edit, mcp__mesh__mesh_call, mcp__mesh__mesh_describe, mcp__mesh__mesh_search +model: opus +--- + +You amend hq's designs for the lead who started you. The caller's handover names the designs, the records +they follow, the file that lists the findings to write in, and the branch. You work in a git worktree of hq under +`.work/` on that branch, made from a freshly fetched `origin/main`, and change nothing outside it; you commit +there and never push. + +**First, what the mesh already knows.** Ask the knowledge base for every record the handover names +(`knowledge-base.get` with `reasoning`, and `links`), and `knowledge-base.ask` for the area in plain words: a +record you did not expect may already decide a point. Read each design whole before changing it, hq's glossary +(`00-META/glossary.md`) and playbook 02's steps for amending a to-be design. A clause the records do not +decide is not written into a design as if they did: say it to the caller as an open point. + +**Then write, by the rules of the record.** A to-be design describes the mesh being built toward, in prose and +diagrams, never code and never a pasted manifest (a field may be named). Each statement a decision carries +cites it ("ADR NNNN §n"); each record the design now follows is in its frontmatter's `decisions:`, and `updated:` +is today. Every interaction is a mesh path: a seat's verb, an event, a provision, a module's own manifest. Speak +of the seat first, the module second. A rule the design states says how it is checked. hq is public: role +names, never node names, addresses or paths. A new word goes into the glossary with its definition and its +*Not:* line in the same change; a retired word is never used. + +**Then check.** Run `go test ./...` at the worktree's root (the records, index, cycle and words tests) and fix +what it refuses. Commit in the imperative, about why. + +Report to the caller in under 30 lines: the branch and its head, each file changed with the findings it now +carries, what the tests said, and every point you left open because no record decides it, with your +recommendation. Glossary words only; a guess is said as one. diff --git a/00-META/glossary.md b/00-META/glossary.md index e04f3b60..4d33e14a 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -106,6 +106,9 @@ and repair). `mesh-contracts` for the seat contracts mesh-sdk held, `mesh--contracts` for an area's. A seat's holder implements the contract; the contracts module holds no seat (ADR 0608 §5). *Not:* ~~SDK module~~, ~~domain package~~ +- **contracts marker** — the field `generated-from` of a contracts module's package artifact, naming the directory + of the seat definitions its files are generated from; the judge refuses a module carrying it that claims a seat, + and the build modules' test step regenerates and compares every module carrying it (design 26). - **package series** — the first two numbers of a package's versions (`0.1`), declared in its manifest and raised only by a reviewed change, for a change that breaks its users; a user's manifest names the package series it accepts (ADR 0606 §4, ADR 0609 §1). Bare "series" only where the package is plain (see [Homonyms](#homonyms)). @@ -150,6 +153,13 @@ downstream of Module and Placement. **Decided by** ADR 0262, 0277, 0348. module has moved to their config names (ADR 0348 §1, issue 630). What a "flavor" once varied is a config value or a separate module. *Not:* ~~flavor~~ +- **rename window** — the time a renamed config value is read under both its names: from the merge of its + `renamed-from` until every node-engine whose node has a want of the module reports applied a build reading the new + name, away nodes included; mesh-config answers both names meanwhile, and its seat verb `renames` says each window's + nodes (ADR 0609 §4, design 32 § 8). +- **retiring value** — a config value or secret no manifest on any trunk reads any more and no node-engine runs a build + reading; mesh-config asks the operator once whether to remove it, and removes it only on the Approve, a secret + through the vault's `retire` (design 32 § 8). Not the provision sense of **retire** (Provisioning). - **config candidate** — the config declarations a pull request's head adds or changes against the trunk's manifests, kept by the indexer per open pull request in its state `config-candidates`; mesh-config judges it again for the values proposed for that pull request (ADR 0565 §2, §3). @@ -480,6 +490,11 @@ controller (the planner, the rollout) until release 6 of ADR 0429; from each rel **composition check**; for a delivery group, one verdict on every member's head (ADR 0445, ADR 0447). Before release 4 of ADR 0429 a pull request carried two statuses, one for the modules the change touches and one for the repository's own script (ADR 0238). +- **module graph** — the edges the indexer keeps between modules, from which each delivery part orders a source + revision by its own rule: provisions, bus state, a served verb and a reader's capability are waited on; a package + and a build module's change are triggers that rebuild, never waits; a verb edge on a cycle is left out (ADR 0603 + §1, §2, ADR 0614). Nothing stores the order. + *Not:* ~~build graph~~ - **composition check** — whether the machines a change touches still compose with it, run by the assigner from the modules the indexer and the builder hold, with mesh-sdk's composition, the one the controller composes with @@ -775,6 +790,17 @@ becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252, 0283, 0564. - **bus state** — what a module keeps on the bus, asked for in `requires` as a provision of the bus module, by a template and its options; created by the bus module and reached by the owner's account alone. Another module asks the owner's tools for its data, or hears its declared events (ADR 0595, ADR 0596). +- **`bus-state`** — the provision of the bus module that every bus-state entry of a manifest requires, so the module + graph orders the bus module's build before a module asking bus state (ADR 0614 §5). Provided by the holder of the + seat `mesh-broker`. +- **binding** of bus state — the names on the bus of a module's bus-state entries, which the grants part writes into + the module's membership on the bus module's `created`, rewrites on `recreated` and removes on `dropped`; a module + whose binding is missing at start fails loudly (ADR 0595 §1, §7). Never bare where it could be read as a sticky + binding or a placement's bindings (see [Homonyms](#homonyms)). +- **first view** — what a reader is answered once, at its start, by the owner's verb or tool, before it follows the + owner's events; it never reads the owner's bus state instead, and asks again only on a gap in the events + (ADR 0596 §2, ADR 0615 §3 to §5). + *Not:* ~~initial view~~ - **storage type** — how the bus keeps bus state: the **stream**, the base, or one built on it, the **key-value bucket**, the **object store** or the **ledger**, each with calls of its own (ADR 0595 §2). *Not:* ~~data type~~ @@ -1027,6 +1053,9 @@ Not a domain of the mesh but of the way it is built, listed because its words me - **decision record** — one numbered record in the knowledge base, named "ADR NNNN", never rewritten: **superseded** by a later record, or corrected by a marked **progressive insight**. Always qualified in this repository: bare *record* has other meanings (see [Homonyms](#homonyms)). +- **overview page** — the knowledge base's read-only web page for the operator: the designs in progress with their + milestones and issues, the records accepted in the last seven days and those waiting, and what waits on the + operator, built from its own ledger and three modules' verbs and events (ADR 0616, design 55 § 11). - **clause** — one numbered statement a decision record makes, with its own status (`proposed`, `in force`, `changed`, `superseded`): a later record may change one clause and leave the rest (ADR 0290). Not a *rule*, which is a line of @@ -1124,6 +1153,9 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical. | | the function in mesh-sdk that composes them, which the controller and the assigner both run | **mesh-sdk's composition** | Placement | | | whether the machines a change touches still compose | **composition check** | Delivery | | membership | the subjects a bus account is issued | **membership** | Provisioning | +| binding | the names on the bus of a module's bus state, in its membership | **binding** of bus state | Data | +| | a consumer's tie to the data a provider keeps for it | **sticky binding** | Data | +| | which host ports and addresses a placement gives a module | a placement's **bindings** | Placement | | | an account in a group of the machine | **account group** | Placement | | domain | an area of the mesh that owns a set of concepts and the one word for each (ADR 0244), the sections of this page; the one sense "domain" keeps bare | **mesh domain** | The record | | | one of the eight concerns a direction and a design are reviewed against (playbook 01); never written bare | **architectural domain** | The record | diff --git a/00-META/process/01-research.md b/00-META/process/01-research.md index 24225b51..3af4751e 100644 --- a/00-META/process/01-research.md +++ b/00-META/process/01-research.md @@ -163,7 +163,8 @@ for the operator's Approve on a channel that proves who answered. ## Closing An effort never just stops. It closes through playbook [02](02-graduation.md) as `graduated` or -`abandoned`, always with a `became` link to what it turned into. Nothing is deleted — what was rejected, +`abandoned`: a graduated effort with a `became` link to what it turned into, an abandoned one with a `cites` link to +what replaced it (playbook 02 step 5). Nothing is deleted — what was rejected, and why, is the more expensive half to rediscover. ## How it is checked diff --git a/00-META/process/02-graduation.md b/00-META/process/02-graduation.md index c10628bc..46098a6b 100644 --- a/00-META/process/02-graduation.md +++ b/00-META/process/02-graduation.md @@ -62,7 +62,10 @@ about an hour, and said to the operator if it runs past. milestones, issues, and each milestone built as one feature step (playbook [07](07-feature-branches.md)). 5. **Close the effort.** `knowledge-base.propose` the effort again, whole, with `status: graduated` and a `became` link to the decision record (`"NNNN"`) and to the design document (its path). An effort that - stops without a decision is proposed with `status: abandoned` and a `became` link to what replaced it. + stops without a decision is proposed with `status: abandoned` and a `cites` link to what replaced it: the + knowledge base refuses `became` from a research effort to another research effort (its link-fits rule, design + 55 § 2), so a replacing effort is cited, and `became` stays for the decision and the design an effort turned + into. ## Writing a decision record @@ -140,7 +143,9 @@ A design changes only through a decision. 1. Write the decision record (above). If it reverses an earlier one, it `supersedes` it. 2. Edit the to-be design document, add the record to its `decisions:` as `"ADR NNNN"`, and set `updated:` - to today. Review it by architectural domain before it is merged (playbook [01](01-research.md#review-by-architectural-domain)), and + to today. A set of amendments across several designs (a milestone's findings, a graduation's) is written by the + agent `hq-design-writer` (`.claude/agents/`), started with a handover naming the designs, the records and the + branch. Review it by architectural domain before it is merged (playbook [01](01-research.md#review-by-architectural-domain)), and rewrite its *Reviewed by architectural domain* section. 3. If the amendment came from an issue, give the issue the document path in the issue tracker: `amended-design` on its `issue-tracker.move` to `resolved`, or `issue-tracker.link` with kind diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 2f403e69..2e330753 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -10,9 +10,13 @@ code: - mesh-tools node-tools/internal/bus (a module's state, ADR 0201; the new calls and apply, ADR 0595, ADR 0597) - mesh-sdk (the typed bus-state entry, its binding and the new calls' package, ADR 0595) - mesh-delivery (the judge's rules and the grants part's binding, ADR 0595) -updated: 2026-11-03 +updated: 2026-11-04 decisions: - "ADR 0595" + - "ADR 0615" + - "ADR 0614" + - "ADR 0613" + - "ADR 0612" - "ADR 0596" - "ADR 0597" - "ADR 0289" @@ -690,19 +694,38 @@ modules' names meet. the tool runner reads the membership ──▶ the module's calls reach its own entries, and only those ``` -- **The trigger is the judge's verdict** (ADR 0595 §4), heard as an event that carries the module's entries whole, - so the bus module never reads the indexer's state and never polls. It hears the verdicts on a durable consumer, so - one given while it was down is heard when it is back, and at start it runs `check` over every accepted manifest, - creating what is missing. The judge checks every entry once, at the merge - check and at take-in (ADR 0578; ADR 0595 §8). +- **The trigger is the judge's verdict** (ADR 0595 §4), heard as the indexer's event `manifest-accepted`, which + carries the module's entries whole, so the bus module never reads the indexer's state and never polls. The indexer + says it for every newly accepted manifest, with its entries empty when it asks for none, and the bus module reads an + empty one as "this module asks nothing now", which is what its orphans need. It hears the verdicts on a durable + consumer, so one given while it was down is heard when it is back. **Its first view** at start is the indexer's + tool `indexer_bus_state` (every accepted manifest's bus-state entries, by module and digest), asked once; the + events keep it current after that, and a stream that keeps a verdict only for its age is never its source. It keeps + what it last heard per module in its own state `asked` (an old `state` section, its own kept data staying there, + ADR 0595 §10), from which `list`, `check` and `orphans` answer; a key goes when the module's accepted manifest asks + for nothing, or the first view no longer names the module. Then it runs `check` over what it holds, creating what + is missing. The judge checks every entry once, at the merge check and at take-in (ADR 0578; ADR 0595 §8), and also + each ledger type's operations: a set-entry or a remove names `into` and `key`, `fields` and an index's `on` name + fields of the schema, and `digest-of` names a text field. +- **A module that asks bus state waits for the bus module** (ADR 0614 §5). Each bus-state entry of a manifest is a + requirement on the provision `bus-state`, which the bus module's manifest provides; the module graph reads it as any + requirement, so the assigner holds a consumer's new build until a build of the bus module that offers `bus-state` + runs healthy (ADR 0603 §5). That each entry exists before its user starts is the bus module's own work, above, not + an edge. - **The bus module has a process of its own** beside its tool bundle, with a declared health check. Its credential creates streams and buckets under modules' names only, and deletes only through `drop` and `recreate`. Its health turns unhealthy, naming the cause, when an accepted verdict has waited on its durable consumer, unanswered, for -more than three minutes, or when its last pass over fill, which runs every minute, is more than three minutes old, so the only guard against a store filling never stops without a sound; another module's entry at 75 percent -raises that module's condition and leaves the bus module healthy. -- **The binding travels on `created`** (ADR 0595 §1, §7). The grants part, which issues every membership, hears it - on a durable consumer and writes the binding (the names on the bus) into the module's membership, kept in its own - state `memberships`, so no part writes another's state and a binding outlives the 30 days its event is kept. +more than three minutes, or when its last pass over fill, which runs every minute, is more than three minutes old, so the only guard against a store filling never stops without a sound; another module's entry past the raise mark +raises that module's condition and leaves the bus module healthy. **Its process has a bus account of its own**, +composed by the grants part for the holder of the seat `mesh-broker` (a seat, never a module's name), minted by the +vault as an own secret: JetStream create and update under modules' names only (streams, buckets and object stores +`_*`), their delete subjects, and the seat's subjects for `create`, the seat's `apply`, `recreate` and +`drop`, which no other account holds, the tool runner's included. +- **The binding travels on the bus module's events** (ADR 0595 §1, §7, as their progressive insight of 2026-11-03 + has it, waiting for the operator's Approve). The grants part, which issues every membership, hears them on a durable + consumer: it writes the **binding** (the names on the bus) into the module's membership on `created`, on every + machine the module is assigned, rewrites it on `recreated` and removes it on `dropped`, kept in its own state + `memberships`, so no part writes another's state and a binding outlives the 30 days its event is kept. A module whose binding is missing at start fails loudly, naming the entry; it never starts blind. A `refused` create hands no binding. **A refused or overdue creation is loud**: overdue means no `created` or `refused` within five minutes of an accepted verdict that asks for the entry. The condition is raised by the watchman, which today raises the judge's @@ -710,6 +733,23 @@ raises that module's condition and leaves the bus module healthy. and `refused`; never by the bus module, which cannot raise it while it is down; it names the module and the entry; a later `created` clears it, and so does a later accepted verdict of that module that no longer asks for the entry. +- **A foundation part's first view of another's state is a verb its owner's process answers** (ADR 0615). The four + foundation parts that read each other (the grants part, the tool runner, the indexer and the assigner) stop + reading the indexer's and the assigner's bus state in the milestone of ADR 0612, with the other 19 readers of that + state (ADR 0615 §1). Each owner answers on its own process subject, granted by name to these readers and never + through the tool runner, so the tool runner never asks through itself: the assigner serves `placement` to the + grants part, the tool runner and the indexer, `seat-holders` to the tool runner and `wants` to the grants part; the + indexer serves `manifests` and `modules` to the grants part, the tool runner and the assigner (ADR 0615 §3). Each + reader declares these as verbs it invokes. The owner answers at once from what it holds, as soon as its process is + connected and before it has its own first view of any other part, with the revision of its state it answered at, + so the indexer and the assigner, each asking the other at start, never wait on each other (§4). The reader then + follows the owner's declared events, each carrying the revision of the state it follows, and never asks the verb + again on a timer; a gap in revisions makes it ask once more, said in its log (§5). A reader that starts while its + owner is down raises a condition naming the owner and the verb past its bound and keeps asking until it answers, + never reading the owner's state instead; once it had its first view, it keeps working on what it heard (§6). The + order of owner before reader is kept by the module graph's served-verb edge for the 19 readers (ADR 0614 §2), and, + for the four, whose verb edges lie on cycles the graph leaves out (ADR 0614 §3), by the owner's answer at once and + the reader's wait (ADR 0615 §2). - **Only the owner reaches it** (ADR 0596 §2). The membership grants the owner's account its own entries and no other module's: the new form has no readers. What another module needs of that data it asks through the owner's tools or seat verbs, and what the owner lets others hear is its declared events, subscribed to through @@ -717,9 +757,11 @@ raises that module's condition and leaves the bus module healthy. ### 12.3 The seat's verbs, events and fill -The seat `mesh-broker`, until now a seat with no verbs, gains these (ADR 0595 §5, §6). Where the seat's definition -lives, and so the exact form of each verb's arguments, waits for the research effort on how seats are defined, -tracked on issue 1966; the build's third step does not start before it. +The seat `mesh-broker`, until now a seat with no verbs, gains these (ADR 0595 §5, §6). It is one of the five seats +defined whole in the milestone of ADR 0612, its contract in `mesh-contracts` (ADR 0613 §1; design 26 *A seat's +definition*), which settles where its definition lives (ADR 0595 §12). An entry's arguments are the module, its local +name, its storage type, its template, its options and its configuration; what is held answers the settings held and +the fill (count, bytes, the cap); `drop` and `recreate` carry the warrant and why. | Verb | Does | Who may call it | |---|---|---| @@ -739,7 +781,8 @@ the bus module takes a stream snapshot of the entry. A drifted entry is said, ne that grows is applied without a person. **Events**: `created`, `refused` (with why), `drifted` and `drift-cleared`, `full` and `full-cleared`, `orphaned` -(cleared by a `drop`, or by a manifest asking for the entry again, which emits `created` for it); +(cleared by a `drop`, or by a manifest asking for the entry again, which emits `created` for it), `recreated` and +`dropped` (ADR 0595 §6, as its progressive insight of 2026-11-03 has it, waiting for the operator's Approve); each carries the module, the local name, the bus name, the template, and the settings asked against those held. The bus module's own kept data stays on the old sections (ADR 0595 §10), because it cannot wait for its own process to create it: it declares this stream in its `streams` section, kept 30 days (ADR 0363's bound), created by the grants @@ -749,22 +792,31 @@ live state. The tool `history` of one entry reads them back: every event the bus settles where they are kept, which ADR 0595 §12 left to the design. **Fill is the bus module's one periodic read** (ADR 0595 §6): every minute, for every entry that refuses when full, -because the bus says nothing as a store fills. The condition `full` is raised from 75 percent of the cap and cleared -below it. The grants part's `mesh-grants.history-full` moves to it: the new condition is raised first, then the old -key cleared. A bare stream that refuses when full is read the same way. **The conditions**, each naming the module -and the entry: `full` (cleared below 75 percent), `drifted` (cleared by `drift-cleared`), `orphaned` (cleared by a +because the bus says nothing as a store fills. The condition `full` is raised at a raise mark and cleared below a +lower clear mark, so an entry sitting at the mark does not flap; the marks are settings of the bus module, by default +a raise at 75 percent of the cap and a clear below 70 (ADR 0595 §6, as its progressive insight of 2026-11-03 has it). +The grants part's `mesh-grants.history-full` moves to it: the new condition is raised first, then the old key cleared; +no manifest on main asks for a history stream or objects today, so the move loses nothing. A bare stream that refuses +when full is read the same way. **The conditions**, each naming the module and the entry: `full` (cleared below the +clear mark), `drifted` (cleared by `drift-cleared`), `orphaned` (cleared by a `drop` or by a manifest asking for it again) and a refused create (cleared by a later `created`). **Its tools** (tools-are-how-a-module-is-spoken-to): the four reading verbs, marked reads, answer every entry the bus module created and its fill, and `history` its events for one entry; `create` and `apply`, marked acts, and `recreate` and `drop`, marked dangerous and so granted only by name (ADR 0386 §6, ADR 0387), are its tools for a person. Such a tool, in the bus module's bundle, holds no grant of those verbs' subjects, and neither does the tool -runner's account: it hands the request to the process on the process's own tool subject, carrying the caller as -the token the identity seat signed (ADR 0386 §8), whose signature the process checks, so no account granted that -subject can name another caller; the process acts only for a caller whose role grants that tool, for `recreate` and `drop` only once -the operator's warrant is held, and answers what it did. The bus seat's `apply` (a difference that grows) and the +runner's account: it hands the request to the process on the process's own tool subject. The caller travels as the +token the identity seat signs (ADR 0386 §8), whose signature the process checks, so no account granted that subject +can name another caller. **Until that token exists** (design 58 builds the identity seat), the process's tool subject +may be published on by tool runners' accounts alone, and the process trusts the caller the tool runner proved and +stamped (ADR 0386 §7), never a name in the request's body; `recreate` and `drop` act only on the operator's warrant, +which is the guard that holds either way. The process acts only for a caller whose role grants that tool, for +`recreate` and `drop` only once the operator's warrant is held, records each as a hand act (`mesh-hand-acts`), and +answers what it did. **The mesh deletes kept data only through `drop` and `recreate`** (ADR 0595 §4, as its +progressive insight of 2026-11-03 has it): the bus module's older remove tools (its stream, bucket and consumer +removals, ADR 0564) refuse an entry of the new form, naming `drop`'s tool, and keep working for the old sections. The bus seat's `apply` (a difference that grows) and the ledger's `apply` (§ 12.4) are two verbs on two subjects, granted apart; this design writes them qualified. Its health -names the process's last pass, the entries it could not create, and the entries over 75 percent. +names the process's last pass, the entries it could not create, and the entries past the raise mark. ### 12.4 The ledger @@ -806,9 +858,12 @@ key-value buckets, and asked for with the template ledger. - **Every instance has a snapshot** (§8): the latest state on a subject of its own, never expiring, written by every `apply`. It makes a kept ledger's rebuild fast and an expiring ledger's possible; a repair that finds it behind the state rewrites it. -- **Only the tool runner's ledger credential publishes on a ledger's history** (§9): one per module, held by the - tool runner and never by the module's own code, whose account may read its ledger and not publish on it. The grants - part issues it with the module's membership on that machine, scoped to the subjects of that module's ledger +- **Only the tool runner's ledger credential publishes on a ledger's history** (§9): one per module assignment, that + is per node the module runs on, the account `ledger..` in the tool runner's own secret `ledgers` on + that node (ADR 0597 §9, as its progressive insight of 2026-11-03 has it, waiting for the operator's Approve), held by + the tool runner and never by the module's own code, whose account may read its ledger and not publish on it. A + person's `rotate` of that secret is refused by the vault, as a bus account's is. The grants part issues it with the + module's membership on that machine, scoped to the subjects of that module's ledger histories and snapshots, and removes it when the module is unassigned from the machine. Only the owner's account reads the history, the snapshot and the state. - **An index** is a second key-value bucket kept by a watcher in the owning module's process, rebuildable from the @@ -846,10 +901,15 @@ the grants part's and the indexer's) stays on the old sections for good and is n wait for a process that needs them first. **Readers move after their owners** (ADR 0596 §3). Today 195 `reads` entries in 37 modules read another module's -bus state, the indexer's 36 times and the assigner's 30. An owner declares the events and gains the tools its -readers need before any reader moves: the judge refuses a moved module's subscription to an event its owner does -not declare, and the review of a reader's move reads that the owner's tool is on main and delivered. These moves -are tracked by issue 1966. +bus state (counted 2026-11-03 02:00). **A correction of fact**: the indexer's and the assigner's state, counted again +from every manifest on main later that day, is read by 23 modules, 9 buckets of the indexer and 6 of the assigner, +not by the 19 the first plan counted; and 151 state entries were counted, not 158 (the template table of § 12.1 keeps +the first count, which chose the templates). The owners first gain their events and first-view tools: the indexer +one per bucket its readers read, the assigner the same. Then all 23 readers move in the milestone of ADR 0612, the +four foundation readers included, by the first view of § 12.2 (ADR 0615 §1); their order is the module graph's, not +a review reading an owner delivered (ADR 0615 §2). The judge still refuses a moved module's subscription to an event +its owner does not declare. Every other reader moves when its module is next touched. These moves are tracked by +issue 1966. ### 12.7 How it is checked @@ -857,7 +917,10 @@ are tracked by issue 1966. |---|---| | A manifest's bus state is valid, once | the judge's unit tests, each refusal naming the field: an unknown template, an option outside its bound, past values 0, "when full" given, a fixed setting given, a name used twice across storage types, two modules' bus names alike, a `reads` in the new form, an effect outside the fixed set (ADR 0595 §8, ADR 0596 §2, ADR 0597 §2) | | The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused | -| The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at 75 percent and cleared below | +| The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at the raise mark and cleared only below the clear mark; `recreated` rewrites the binding and `dropped` removes it; at start the first view from `indexer_bus_state` creates what a verdict given while it was down asked; the older remove tools refuse a new-form entry, naming `drop`'s tool | +| A module asking bus state waits for the bus module | the module graph's table test: a manifest with a ledger entry has a requirement edge to the module providing `bus-state`, one without has none (ADR 0614 §5) | +| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked): the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition past the bound and never reads the owner's state; the four readers' manifests hold no `reads` of the indexer's or the assigner's state | +| Until the identity seat, the caller is the one the tool runner proved | a unit test of the bus module's process: a request on its tool subject naming another caller in its body acts for the proved caller; `drop` without the warrant is refused | | Drift and orphans, live | live, made only by a manifest change: a manifest that shrinks a setting raises `drifted`; the conditions clear as above. Nothing is filled on the live bus, and no orphan is made there | | Overdue and stopped are loud | live, carried by the change of the first module that moves in step 5, never by a merge made for the test alone: with the bus module's process stopped, an accepted manifest asking for an entry raises the overdue condition within five minutes, from the watchman, and a later `created` clears it; the bus module's health turns unhealthy when its pass is more than three minutes old | | A person's tools reach the process, and only by role | live: a role without the dangerous grant is refused `drop`'s tool; the tool runner's account holds no grant of `create`, the seat's `apply`, `recreate` or `drop` | @@ -895,7 +958,9 @@ ruled on every finding. What was found, and what answers it: | upgrade | the build order lacked the grants part's binding and two undo steps | design 25 § 12.6 | | tools | the ledger's repairs had no tool for the next owner; the bus module's events had no read tool | design 25 § 12.5 (served for every ledger owner), § 12.3 | -**Left to the operator**, because each changes an accepted record: a margin between raising and clearing the -condition `full` (ADR 0595 §6 raises and clears it at 75 percent, so an entry sitting there flaps), and an event of -its own for `drop` and `recreate`. **A correction of fact** for ADR 0595 §4: the bus module deletes through `recreate` -as well as `drop`. Each is noted on issue 1966. +**Answered since**: the margin of `full` and the events of `drop` and `recreate` were the operator's answers of +2026-11-03 and are now ADR 0595 §6's progressive insight, with the correction of fact for ADR 0595 §4 (the bus +module deletes through `recreate` as well as `drop`); both wait for the operator's Approve and are written in § 12.2 +and § 12.3. The milestone's amendment (ADR 0613 to ADR 0615: the first view, the bus-state edge, the deletion paths, +the caller until the identity seat, the reader count) awaits its own review by architectural domain, which rewrites +this section. diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 58cb32b4..2e435973 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -15,9 +15,12 @@ code: - mesh-catalog modules/gitea/module.json - mesh-controller internal/inventory/migrations/0063-a-machines-names-are-one-seats.sql - mesh-controller internal/inventory/migrations/0064-the-resolver-file-is-the-uplinks.sql -updated: 2026-11-03 +updated: 2026-11-04 decisions: - "ADR 0608" + - "ADR 0613" + - "ADR 0612" + - "ADR 0614" - "ADR 0605" - "ADR 0287" - "ADR 0223" @@ -141,7 +144,7 @@ convention, which later seats departed from. |---|---|---|---| | `mesh-controller` | — | mesh | — | the controller | | `mesh-store` | — | mesh | — | the store the mesh's own records live in | -| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus | +| `mesh-broker` | — | mesh | `mesh-bus`, and `bus-state` (ADR 0595 §1, ADR 0614 §5) | the broker carrying the mesh's own bus; its holder, the bus module, creates the bus state modules ask for (design 25 § 12) | | `mesh-vault` | — | mesh | `secret` | the vault (in the seed since ADR 0161; the earlier *reserved* named an effect no rule produced) | | `container-image-registry` | `mesh-artifact-store` for images (ADR 0605; the old name readable until every node-engine reads the new one) | mesh | `artifact-store` | the artifact registry (`distribution`) | | `oci-artifact-registry` | `mesh-artifact-store` for bundles and archives (ADR 0605) | mesh | `artifact-store` | the artifact registry (`distribution`) | @@ -172,42 +175,81 @@ merges. ## A seat's definition (from 2026-11-03) **A seat is defined once, in one language-neutral file, and every part learns it from the bus** (ADR 0608, from -research 081). The registry seats (design 51) are the first defined this way, then `mesh-broker`'s verbs. +research 081). The registry seats (design 51) and `mesh-broker` (design 25 § 12.3) are the first defined whole this +way, in the milestone of ADR 0612 (ADR 0613 §1). - **The file** is a schema with conformance fixtures: the seat's verbs and their messages, its events and their bodies, its state and the forms of its provisions. It lives in the contracts module of its area. Its Go and - TypeScript types are generated from it and committed beside it; nobody writes them by hand. + TypeScript types are generated from it and committed beside it; nobody writes them by hand. The format is JSON, its + messages and bodies JSON Schema, as the seat set's verb inputs already are, with the format's own schema checked in; + the fixtures are data, never functions. +- **What is defined whole now** (ADR 0613). The five seats this milestone changes are defined whole and their contracts + move into `mesh-contracts`: `npm-package-registry`, `go-package-registry`, `container-image-registry`, + `oci-artifact-registry` (one common part of their shared verbs, written once, so the four cannot drift) and + `mesh-broker`. Every other seat, the foundation's included, has its definition on the bus in the same milestone, all + at once (ADR 0608 §2), holding the seat list's grain: its name, whether it is held once or on every node, its verbs + with their inputs and its provision, generated from the seat list mesh-sdk compiles today, so the move changes + nothing a reader sees (ADR 0613 §2). A verb this milestone adds to such a seat (`mesh-config`'s `renames`, + `mesh-vault`'s `retire`, design 32 § 8) is added at that grain. Its messages, events and state layouts are added to + a seat's definition when its area's contract next changes, which moves that area's seats whole into its contracts + module in that change (ADR 0613 §3); which area moves when is tracked by issue 167 (ADR 0613 §4). +- **The seat set has one source.** The judge, the node-engine, the tool runner and claude-code each compile a copy of + the seat list today; all four import the set generated in `mesh-contracts` instead. The tool runner's descriptions + and claude-code keep that compiled set, equal to the trunk's definition, in this milestone, so neither depends on + the indexer at its start; they move to `indexer_seat_definition` and its event when their next change moves them, + tracked on issue 1966. - **The generator** is each language's build module's: the Go build module's package for Go, the Node and TypeScript - build module's for TypeScript (the logic is the module's whose work it is). The check of a change regenerates and - fails, naming the file, when a committed type differs; its verdict event names the out-of-step files, and that is - how the indexer learns of them. The indexer runs no generator. + build module's for TypeScript (the logic is the module's whose work it is). The generator's version is a build input + of every contracts module, so a changed generator rebuilds each contracts module once (ADR 0603 §3). Each build + module's test step regenerates and compares, for every module carrying the contracts marker (below): a committed + file that differs, a file missing, or a file in the package tree the generator did not write fails the step, naming + each path. +- **Out of step is learned from events, and the indexer runs no generator.** For a pull request, the check's step + fails with the out-of-step paths; the checker puts them in its verdict and emits its declared event `verdict` + carrying them (its `preview.written` is declared in the same change). For a trunk, the builder's build-end event + carries a trunk build's out-of-step paths, which is how a generator's change is seen, since no check of the + contracts module runs then. The indexer consumes both and writes the trunk's finding. - **On the bus**: the indexer reads every seat definition from every trunk and open pull request, from the forge bridge's messages, and keeps them in its own state `seat-definitions`, keyed by seat and source (the trunk, or a pull request's head), as their only writer; a definition is at most 256 KiB. It emits `seat-definition-changed` - (seat, source, digest), kept seven days. **Only the trunk's entry is the seat's contract**: the readers (the contracts - modules' builds, the tool runner's descriptions, the review) ask `indexer_seat_definition`, which answers the trunk's - unless a pull request's head is named, and says which it answered. A pull request's entries are removed on the forge - bridge's event that it closed or merged. A definition that does not parse fails its pull request's check; the - trunk's stays. The merge is the only approval. The foundation's own definitions are bootstrapped before the bus - exists, as the bootstrap of a fresh mesh has it (research 075). + (seat, source, digest), kept seven days, only when a digest changed. **Only the trunk's entry is the seat's + contract**: the readers (the contracts modules' builds, the review, and later the tool runner's descriptions) ask + `indexer_seat_definition`, which answers the trunk's unless a pull request's head is named, and says which it + answered. A pull request's entries are removed on the forge bridge's event that it closed or merged. A definition + that does not parse fails its pull request's check; the trunk's stays. The merge is the only approval. The + foundation's own definitions are bootstrapped before the bus exists, as the bootstrap of a fresh mesh has it + (research 075). - **Findings**, written by the indexer, from which the condition keeper raises and clears the conditions (ADR 0564): a - trunk's seat definition that does not parse (a pull request's is its check's verdict, not a condition); generated - types on a trunk out of step with their definition. **Tools** of the indexer: `indexer_seat_definition` (read one), - `indexer_seat_definitions_again` (read a trunk's or a pull request's again now) and `indexer_out_of_step` (the - generated types out of step). + trunk's seat definition that does not parse, `seat-definition-unparsed` (a pull request's is its check's verdict, not + a condition), cleared by a definition that parses; generated types on a trunk out of step with their definition, + `generated-types-out-of-step`, cleared by a clean build end. **Tools** of the indexer: `indexer_seat_definition` + (read one), `indexer_seat_definitions_again` (read a trunk's or a pull request's again now) and `indexer_out_of_step` + (the generated types out of step). - **Contracts modules.** A seat's contract is published by a contracts module, which holds contracts only (every file - generated from a seat definition) and no seat; the seat's holder implements it. The seat contracts mesh-sdk holds - move into `mesh-contracts` in one change, delivered with the first numbered publish (design 51, *The move*, step 2); - an area's contracts move into `mesh--contracts` when the area's contract next changes, and that change creates - the module. mesh-sdk keeps its name and the shared runtime. A catalog check refuses a contracts module holding a file - its generator did not write, and a seat-specific type in mesh-sdk once `mesh-contracts` publishes. + generated from a seat definition) and no seat; the seat's holder implements it. **The contracts marker**: a contracts + module's package artifact names the directory of its definitions in the field `generated-from`, and the judge + refuses a module carrying it that claims a seat, naming the seat. `mesh-contracts` declares two packages, Go and + TypeScript, in package series `0.1`, published through the two package seats (design 51) and taken by its users by + version, or inside one delivery group by the group's head. No `mesh--contracts` is created in this milestone: + an area's contracts module is created by the change that next moves its area. mesh-sdk keeps its name and the + shared runtime, and takes no new seat-specific type (ADR 0613 §5): a unit test of mesh-sdk refuses an exported + identifier or string constant naming a seat the definitions define. The two checks every change already runs, the + build modules' compare step and that test, are where a stray file and a stray type are refused; mesh-catalog's own + checks cannot see another repository. +- **A contracts cycle holds nothing.** `mesh-contracts` is published through the forge module's package seats and + built by the Go and Node build modules, and the forge module, `distribution` and the Go build module import it. A + package edge and a build module's edge are triggers, never waits, so that cycle starts and is not refused (ADR 0614 + §6), and a package is numbered per source revision, so the triggers end (design 51 *The versions*). - **Older readers keep working**: a moved contract keeps its subjects, keys and message shapes, and a reader ignores a field it does not know. - **A vendor-specific** provision or contribution lives in its module's own extension package, published by version like any package; a later holder of the seat may publish its own the same way. A seat's definition stays vendor-neutral, which the seams review checks. -Acceptance tests: research 081's 14, 21, 25 and 26, and cases of the indexer's whole-module integration test: a trunk +Acceptance tests: research 081's 14, 21, 25 and 26; a unit test that the generated seat set equals the seat list +mesh-sdk compiled before the move, entry for entry; mesh-sdk's seat-naming test; each build module's compare step +failing on a differing, a missing and an unwritten file, naming each; the checker's `verdict` naming out-of-step +files; and cases of the indexer's whole-module integration test: a trunk definition that does not parse writes its finding and a fixed one clears it; a check verdict naming out-of-step types writes the finding and a clean one clears it; `indexer_seat_definition` answers the trunk's entry while a pull request holds another; a pull request's entries go when it closes; a definition over 256 KiB is refused; each of the three @@ -403,3 +445,7 @@ and 32 by the four reviewers by architectural domain; the findings and what answ *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). + +The milestone's amendment (ADR 0612, ADR 0613, ADR 0614: five seats defined whole, the rest at the seat list's grain, +the contracts marker, out of step learned from the checker's `verdict` and the builder's build end, the seat set's one +source) awaits its own review by architectural domain, which rewrites this section. 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 3ca7ae5d..6b2b1689 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 @@ -13,9 +13,11 @@ code: - mesh-catalog modules/mesh-catalog - mesh-tools node-tools/internal/runtime (a module's state, ADR 0201) - mesh-sdk (the typed bus-state entry in `requires` and its binding, ADR 0595) -updated: 2026-11-03 +updated: 2026-11-04 decisions: - "ADR 0609" + - "ADR 0612" + - "ADR 0613" - "ADR 0606" - "ADR 0595" - "ADR 0596" @@ -491,8 +493,15 @@ complete, because a team that believes it is complete stops reviewing for the ca - **A user outside the package's delivery group is not built before the merge**: it is built after it, when the new version is in its series. A user that breaks fails its own build, is not wanted, keeps its previous build, and a condition tells the operator. A group's members are built in the group's check, against the group's tagged version. +- **A contracts module's package** names the directory of the seat definitions it is generated from in the field + `generated-from`, the contracts marker (design 26). +- **The judge reads the new words** (ADR 0578 §1): each package series well formed and only on the four kinds that may + carry it, a package naming `go` or `typescript`, and each name in `packages` a package some manifest declares. A + running judge that does not know a word sets the manifest aside to wait, never refuses it, while a newer indexer + build is merged and not yet delivered; and a node-engine is handed a manifest using these words only once it + states the capability to read them (ADR 0614 §4). - **Cost**: the new manifest fields are read through mesh-sdk's manifest types, so their change rebuilds every module - once; it is delivered with design 51's step 2, the one rebuild that step already takes. + once; it is the one rebuild the milestone of ADR 0612 already takes for the contracts move (ADR 0612 §1, ADR 0613 §1). ### A config value renamed (from 2026-11-03) @@ -503,25 +512,46 @@ value it holds under the old name to the new name in its own state, without the - **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 whose node has a want of the module reports applied a - build of it that reads the new name: mesh-config hears the status view's `delivered` events, and asks its - `node_haves` at its start (design 47); 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, which it learns from the status view's - `delivered` events for mesh-config. `config_renames` names, per rename, the nodes still holding the old name open. +- **How long the old name is read** (the **rename window**): 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 status view's `delivered` + events and, at its start and on each, asks its `node_haves` for every node, away ones included, since `delivered` + counts only the nodes present (design 47); until then mesh-config answers both names. A node holding a window open + for seven days is mesh-config's finding naming the node, cleared when the window closes; `config_rename_close` + drops a node from a window by hand, recorded as a hand act. +- **The checker asks the seat, never the module.** The seat `mesh-config` gains the optional verb `renames` (the + open renames, and the nodes holding each window open), in its seat definition (design 26). The checker asks it for + every change carrying `renamed-from` or dropping one: a holder that does not serve it is refused by the tool runner + naming the verb, and the checker then refuses `renamed-from` naming that; while a window is open, the same answer + refuses dropping `renamed-from`, naming the nodes. A check that cannot ask fails, and says so. So no manifest uses + `renamed-from` in the milestone that brings it, whose running mesh-config serves no `renames`; the first that does + is its live case. `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). + raises the condition, cleared once the value moved or `renamed-from` left the trunk's manifest. **Tools of + mesh-config**: `config_renames` (what moved, from which name, when), `config_rename_again` (move one value by hand + when a move was refused) and `config_rename_close` (above). ### A value no longer read (from 2026-11-03) -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. +A config value or secret no manifest on any trunk reads any more is a **retiring value**. mesh-config learns it from +the indexer's tool `indexer_config_readers`, which it asks on each of the indexer's `manifest-accepted` events; the +indexer says `manifest-accepted` for every newly accepted manifest, its bus-state entries empty when it asks none (the +bus module reads an empty one as "this module asks nothing now", design 25 § 12.2). A tool the indexer does not serve +yet answers "not known", and nothing retires on it. It is marked retiring only once no node-engine reports applied a +build that reads it, as the rename 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. **mesh-config holds no secret**, so the +retiring flow and its one ask stay mesh-config's, and on the Approve it removes a value itself and retires a secret +through the seat `mesh-vault`'s new optional verb `retire` (one module's own secret, by node, module and name), carrying +the operator's warrant, which the vault checks (ADR 0259 §6) before it removes anything. An ask unanswered for seven +days is asked once more, and then written as a finding, cleared by an answer, a cancel, or the value read again; a +sleeping node holding a retirement open for seven days is a finding naming it. `config_retiring` lists what is +retiring, since when, and its ask, and asks it again or cancels it. + +**Where mesh-config keeps it.** Its new state (`renames`, `retiring`, `findings`) and its stream `config.events` are +declared in its old `state` and `streams` sections in this milestone, as the builder's and the checker's new state is +(design 51 *The move*), so that neither mesh-config nor the deciding parts that ask it wait on the bus module's +process, which is delivered in the same milestone. They move to bus state when each module next moves (ADR 0595 §11). +`config-changed` stays on the shared events stream, unchanged, so its consumers do not move. ### The mesh delivery cases this design carries @@ -740,3 +770,7 @@ what answers them are listed in design 51's *Reviewed by architectural domain*. value's trust (security), `packages` is a section of its own, not `uses`, and mesh-config hears the status view's `delivered` 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). + +The milestone's amendment (ADR 0612, ADR 0613: the seat verb `renames` the checker asks, the vault's `retire`, +`manifest-accepted` for every manifest, mesh-config's new state in its old sections) awaits its own review by +architectural domain, which rewrites this section. 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 d4bcc5c6..243393a6 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 @@ -3,12 +3,18 @@ layer: to-be status: in-progress code: - mesh-controller cmd/mesh-controller (the verbs artifacts, collect, images and mirrors), internal/builder (copied bases), internal/inventory (artifact_mirrored) - - mesh-catalog modules/artifact-store-tools (the store's tools), modules/distribution (its unbuilt code removed) - - mesh-catalog modules/gitea (package retention) + - mesh-catalog modules/artifact-store-tools (the store's tools), modules/distribution (its program, holding the two image seats) + - mesh-catalog modules/gitea (package retention; the two package seats' verbs in its Go bundle) + - mesh-catalog modules/mesh-build-go, modules/mesh-build-node, modules/mesh-build-image (publishing through the seats) + - mesh-delivery modules/mesh-delivery-builder (versions, tag asks), the forge bridge, the indexer, the checker, the assigner + - mesh-sdk (the bus helper for sending in parts) - mesh-catalog modules/docker (image pruning) -updated: 2026-11-03 +updated: 2026-11-04 decisions: - "ADR 0605" + - "ADR 0612" + - "ADR 0614" + - "ADR 0613" - "ADR 0606" - "ADR 0607" - "ADR 0610" @@ -49,22 +55,31 @@ asks work of another by an event of its own (ADR 0610 §1). 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. `distribution` closes it itself: it hears the status view's -`delivered` events (and asks the status view's `node_haves` at its start, design 47), 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. +new names (ADR 0610 §4); a sleeping node holds it open. `distribution` closes it itself. The status view's +`delivered` counts only the nodes present (an away node is normal, ADR 0603 §7), so it is only the wake-up, never the +proof (ADR 0610 §4, its progressive insight of 2026-11-03): at its start and on each `delivered` event, `distribution` +asks the status view's `node_haves` for every node, away ones included, and stops serving the old name once every node +whose declaration names an artifact has applied a build whose node-engine states the capability to read the new names +(ADR 0614 §4), judged by that stated capability and never by a fingerprint. Its tool `registry_old_name` lists the +nodes still holding it open, and drops a retired node from the window by hand, recorded as a hand act; a node holding +it open for seven days is a finding naming the node, cleared when the window closes. `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`, `publish-part` (ADR 0611), `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 +tagged version; the holder itself refuses `remove` of a version with no tag, but for the retention removal below), and +on `container-image-registry` `mirror` and `tags`; the two seats `distribution` holds add `hold`, for the artifact +sweep. The five seats this milestone changes, these four and `mesh-broker`, are defined whole, their contracts moved +into `mesh-contracts` (ADR 0613 §1; design 26). 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; 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` and `publish-part` the build modules, and a part only from the -proved caller that opened its transfer; `mirror` mesh-build-image; `remove` the builder. +proved caller that opened its transfer; `mirror` mesh-build-image; `remove` the builder, for a tagged version, and the +holder of the artifact sweep, for an untagged bundle, archive or copied base with the reason retention (ADR 0610 §3); +`hold` (on the two seats `distribution` holds: keep one held thing from removal, and let it go again) the holder of +the artifact sweep alone. Every removal, whoever asked it, emits `removed` with its reason. 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. @@ -86,9 +101,9 @@ the verb. - **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 + mesh-sdk's bus helper for sending in parts, added to mesh-sdk's runtime in the milestone's one 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 + own object store `incoming`, asked for as bus state in its manifest (design 25 § 12) 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. @@ -103,16 +118,38 @@ the verb. - **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 build module calls `publish` with the version and the held digest, which stores nothing. An upstream image is copied - by `mirror` with its pinned digest. The done key names the reference the seat answered; a key in the old - `artifact-store://` form keeps it, every reader reads both forms, and none is built again for its form. -- **No build module holds a registry's write credential**: the npm write token, the image push credential and the - artifact store's write binding go in step 4. Each holder keeps its write side as an own secret beside a read-only - tool token (the forge module: `write:package`, its tool token `read:package`). + by `mirror` with its pinned digest. +- **The done key names the reference the seat answered**, in ADR 0605 §9's form from this milestone on: + `//@sha256:` for an image, bundle, archive or upstream copy and + `@` for a package. A key in the old `artifact-store://` form keeps it, every reader reads both forms, + and none is built again for its form. A node-engine built before this milestone cannot read the new form, so a + want whose build carries it reaches a node only once that node's engine states the capability to read both forms + and both seat names; the assigner holds the node's want meanwhile and names the capability as the reason, and a + node away keeps its want and catches up on return (ADR 0612 §2, made general by ADR 0614 §4). +- **No build module holds a registry's write credential** (ADR 0605 §5). The npm write token goes. The image push + credential and the artifact store's write binding that ADR 0605 §5 revokes do not exist today: the store takes + writes from any process on the mesh network, so the rule protects nothing until its write side is closed. So + `distribution`'s own program serves the store's port in front of the registry container, which listens on its + node's loopback only: reads (the OCI distribution protocol's GET and HEAD) stay open to every node's container + runtime, and a write reaches the store only from `distribution`'s own process, after a `publish` it accepted. Each + holder keeps its write side as an own secret beside a read-only tool token (the forge module: `write:package`, its + tool token `read:package`). +- **The package seats' read sides need no credential** for the mesh's own packages: the forge serves the mesh owner's + packages to anonymous reads, so the provisions `npm-package-registry` and `go-package-registry` carry no + credential (form none), and mesh-build-node, which held a pair for both writing and installing, requires none (the + operator's answer of 2026-11-03, that the mesh's packages may be read without a credential). +- **The forge module serves both package seats' verbs from its Go bundle**, the one that already serves its package + retention, renamed for both packages; its tools bundle in TypeScript is unchanged, so no holder needs generated + TypeScript types in this milestone. - **Go packages** are fetched through the Go seat's provision first and Go's public proxy second, with no `GOPRIVATE` - and the checksum database off for the forge's paths; `go.sum` and the digest the builder records (Go's `h1:` hash, - computed by the Go build module at the publish) refuse a mesh package with other content. The builder also tags - each Go version in git (`go/v0.1.`), so a user that names a version can fetch it from the forge's git as well. - No mirror of outside modules is built; one could come later as a holder of a seat of its own. + and the checksum database off for the forge's paths (ADR 0605 §6); `go.sum` and the digest the builder records (Go's + `h1:` hash, computed by the Go build module at the publish) refuse a mesh package with other content. For this + milestone the fetch keeps a third source last, the forge's git over the build module's own account (Go's `direct`): + between the recipe change and the first numbered versions of mesh-sdk and `mesh-contracts` in the Go seat, a build + by the new recipe could otherwise fetch nothing it names. The first change after those versions are in the Go seat + removes `direct`, said in its pull request. The builder also tags each Go version in git (`go/v0.1.`), so a user + that names a version can fetch it from the forge's git as well. No mirror of outside modules is built; one could + come later as a holder of a seat of its own. ### The versions @@ -122,6 +159,12 @@ the verb. 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. +- **A package is numbered per source revision**, not per fingerprint: a second trunk build of a revision already + numbered for that package (a build module's change reaching it by a second build, ADR 0603 §3, or the contracts + move's one rebuild) keeps the package's version and publishes nothing, since a package's content is its source. + So a publish that triggers a rebuild of the package's own build module (the shared contracts, the forge module + that holds their registry and the Go build module that builds them, ADR 0614 §6) never comes round to a new + version, and the triggers end. An image, bundle or archive is numbered per fingerprint, as above. - **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 @@ -130,8 +173,15 @@ the verb. - **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, 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`. + name until all three answered (ADR 0606 §3), 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`. +- **An unknown floor does not stall the build of an image, bundle or archive.** The floor of `distribution`'s own + names is `distribution`'s `versions`, which only its new build serves, and the same holds for the forge module and + the builder: waiting would never end. So a trunk build of an image, bundle or archive whose floor is not yet known + is built unnumbered and published by its digest (as a bundle or archive is from every done key, ADR 0605 §3); once + the floor answers, the builder numbers it, and the build module calls `publish` with the version and the held + digest, which stores nothing. A package's trunk build waits for its floor, since a package is published only under + its version. - **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, @@ -146,22 +196,30 @@ the verb. 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 + branch is merged or deleted, or its pull request closed. An ask the builder has not answered within five minutes is narrated in the build log with what it waits on. - **Numbered below the next version** (`0.1.15-feat-x.2`), never the latest; npm keeps it under a dist-tag of its own and Go's `@latest` skips it. A package new in a group is numbered from its floor (`0.1.0-feat-x.1`). - **A group's check builds after `published`**: Go members by the tagged version, TypeScript members with their lock naming it and the registry's integrity; nothing is packed for a check, and a lone pull request's check publishes nothing. No `published` within the publish's bound (10 minutes) fails the check naming the package. -- **Removed with its branch**: on the forge bridge's merged or deleted event, heard on a durable consumer, the builder +- **Removed with its branch**: on the forge bridge's `merged` event, its pull `closed`, or its branch-deleted event, + heard on a durable consumer, the builder calls each seat's `remove`. It acknowledges the event only once every seat emitted `removed`; until then it retries every minute and, past 15 minutes, writes a finding naming the seat. A version with no tag is not removed by a branch's end; retention may remove an old unreferenced one (below). +- **The forge bridge says a branch's end**: beside its `merged`, `refused`, `open` and `pull` kinds on its stream + `forge`, it says a branch-deleted kind, from the forge module's `forge.delete` event, which it consumes on a durable + consumer as it consumes the pull events. Its tool `bridge_tags` answers a package's git tags, for the floor. ### Retention 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. +With the store's write side closed, the artifact sweep (seat `mesh-artifact-sweep`, held by artifact-store-tools) no +longer deletes or holds by writing to the store: it calls `remove` on the seat that holds the thing, with the reason +retention, and `hold` to keep a kept archive from removal, both granted to its holder alone by `distribution`'s +caller list; its rules of what is kept (*The artifact store* below) stand, and it reads both reference forms. 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 @@ -174,28 +232,43 @@ publish refused (the holder when it refuses, the builder when a build module's b 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. +the builder), a node holding the old seat name open for seven days (`distribution`, cleared when the window closes). A build narrates its publish: asked, waiting on a seat, stored, refused. -### The move, in four steps +### The move, one milestone, ordered by the module graph -Each step is its own delivery, and no credential goes before its replacement works (ADR 0605 §7). +The move is one milestone: one delivery group, one check, one merge (ADR 0612 §1, with design 26's seat definitions +and design 32's manifest words; ADR 0613 §1 adds the bus module's work and the knowledge base's). Its order is not +a sequence of deliveries but edges of the module graph, which the indexer keeps and each delivery part waits on by +its own rule; nothing stores the order (ADR 0612 §2, ADR 0614 §1). The edges this move relies on: -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. 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. -3. The Go fetch switches to the Go seat. Its rollback stays safe after a user moved, since the git tags exist. -4. The three credentials go, and `package-version.sum`. The old seat name retires after it, on the node-engines' - reports. +| Order | Edge | Kept by | +|---|---|---| +| A build module's build that publishes through a seat runs only after a holder build serving `publish` and `publish-part` runs healthy | served verb: each `invokes` entry of a seat verb or module tool is an edge to the module whose build serves it (ADR 0614 §2). The provision edge alone is empty here: today's holder builds already offer `npm-package-registry` and `artifact-store` | the assigner, counting the holder's own have as ADR 0603 §5 counts a provider's; the status view names the wait | +| A holder's check of `builder_versions` and the builder's `remove` | the two calls close a cycle, so both verb edges on it are left out of the graph and the calls retry (ADR 0614 §3); the builder retries `remove` each minute with its 15-minute finding | the indexer's graph, naming each edge left out | +| mesh-build-go's fetch from the Go seat only after the forge module serves its read side | provision: `go-package-registry`, offered only by the forge module's new build (ADR 0603 §5) | the assigner and the provisioner | +| No credential goes before its replacement works | the build modules' manifests drop the write credentials in the same change that makes them publish through the seats, so the vault mints none for the build that publishes (ADR 0612 §2) | the served-verb edge above | +| A build carrying the new reference form, or a manifest using this milestone's words or seat names, reaches a node only through a node-engine that reads them | a reader's capability the node-engine states, the baseline when it states none (ADR 0614 §4) | the assigner, holding that node's want and naming the capability | +| The holders' `incoming` and `.events` exist before the holders start | bus state: each entry is a requirement on the provision `bus-state` of the bus module (ADR 0614 §5) | the assigner, as for any provision | +| The shared contracts, the forge module that holds their registry and the build module that builds them | package and build module edges are triggers, never waits; the cycle they make holds nothing and is not refused (ADR 0614 §6); inside the group every member takes `mesh-contracts` and mesh-sdk by the group's head (the checker's group pin) | the indexer's graph | -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`. +**The milestone's own check** runs the changed build modules (ADR 0603 §10) against the running holders, which serve +no `publish`: a changed build module whose manifest invokes a verb no running build serves is one the check cannot +run, and the modules it builds are composed from their manifests, the verdict naming them and the verb (ADR 0614 §7). +**The foundation's deciding parts** (the builder, the checker, the indexer, the forge bridge) keep their new state +(`versions`, `tag-asks`, the stream `checker.asks` and the like) in their old `state` and `streams` sections in this +milestone, so none of them waits on the bus module's process, which needs them first; the holders ask theirs as bus +state. + +**Before the merge, the operator is told** two things that come with it: mesh-build-node no longer requires +`npm-package-registry`, so its provision key goes retiring and the provisioner asks the operator once per consumer +(ADR 0570 §5); and closing the store's write side changes nothing the operator sees. `package-version.sum` and its +guard go in the same change (ADR 0606 §6, read with ADR 0612 §1). + +**Rolled back by one revert** (ADR 0612 §4): reverting the milestone's merge brings back the build modules' +manifests that require the write credentials, so the vault mints them again as own secrets on the revert's delivery, +and the builds that publish through the seats go with it. The old seat name still retires on the node-engines' +reports (ADR 0610 §4). **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`. ### Acceptance tests @@ -225,11 +298,20 @@ test). 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; the bootstrap's one-off credential is gone - once the holders serve `publish`. -- **Live.** Step 2 waits while one node has not got step 1's build, as `node_haves` shows (the lead's gate before - step 2's merge); `registry_old_name` names the nodes - holding the old name open. +- **Holder, added for the milestone.** A write to the store's port from any account but `distribution`'s process is + refused, and a read from a node's container runtime is answered; the artifact sweep's `remove` with the reason + retention removes an untagged bundle and emits `removed`, and the same call from another caller is refused; `hold` + keeps an archive from removal; the forge bridge says a branch-deleted kind for the forge module's `forge.delete`. +- **Builder, added for the milestone.** With the seat's holder down at a first claim, a bundle's trunk build is built + unnumbered and published by its digest, and is numbered once the floor answers; a package's trunk build waits; + a second trunk build of a numbered package's revision keeps its version and publishes nothing. +- **Checker.** A changed build module invoking a verb no running holder serves is not run, and the verdict names the + modules composed from their manifests instead, and the verb. +- **Vault.** Reverting the milestone's merge mints the build modules' credentials again; the bootstrap's one-off + credential is gone once the holders serve `publish`. +- **Live.** The status view shows each build module's new build waiting on its holder's build serving `publish` + until that runs healthy (ADR 0612 *How it is checked*); no build module's provision key holds a write credential; + `registry_old_name` names the nodes holding the old name open, an away node among them. ## Reviewed by architectural domain @@ -250,6 +332,10 @@ and ADR 0611 (the file travels in parts through the seat). What was found, and w | 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 milestone's amendment (ADR 0612 to ADR 0614: one milestone ordered by the module graph, the new reference form +from it on, the store's write side closed, anonymous package reads, the floor and source-revision rules, the sweep +through the seats) awaits its own review by architectural domain, which rewrites this section. + ## The parts ``` diff --git a/03-DESIGN/01-to-be/55-the-knowledge-base.md b/03-DESIGN/01-to-be/55-the-knowledge-base.md index 396ee8e2..41fa48b2 100644 --- a/03-DESIGN/01-to-be/55-the-knowledge-base.md +++ b/03-DESIGN/01-to-be/55-the-knowledge-base.md @@ -2,7 +2,7 @@ layer: to-be status: in-progress code: [mesh-catalog, hq] -updated: 2026-11-03 +updated: 2026-11-04 decisions: - "ADR 0290" - "ADR 0321" @@ -13,6 +13,8 @@ decisions: - "ADR 0595" - "ADR 0596" - "ADR 0597" + - "ADR 0616" + - "ADR 0613" --- # 55 — The knowledge base @@ -36,6 +38,10 @@ commit rule fall away (ADR 0597 §12, changing ADR 0592 §1 to §12, §17, §25, `requires` by template (ADR 0595), and no other module reads it: another module asks the knowledge base's tools or hears its events (ADR 0596). The ledger itself, the same for every module, is designed in [design 25](25-the-bus-on-nats.md) § 12; how a module asks for it, in [design 32](32-what-a-module-declares.md) § 4. + +**Amended, 2026-11-04** (ADR 0616; the operator, 2026-11-03, on issue 1968: "I'm starting to lose the overview"). The +knowledge base serves a read-only overview page for the operator, built from its own ledger and three other modules' +verbs and events (§ 11), in the milestone that moves it onto the ledger (ADR 0613 §1). § 1, § 1.1, § 2.1, § 4, § 8.1, § 9 and § 10 say what changes here. **The mesh keeps its decision records and research efforts whole in a module of its own, `mesh-knowledge`, one @@ -93,7 +99,9 @@ and the knowledge base works out each design's state (ADR 0423, ADR 0424). │ imported, drafted, confirmed, returned ├ consumes the router's warrants: the operator's Approve for a record that needs it (§ 5) ├ consumes claude-code's judged: the judge's answer to a part (§ 5) - └ consumes the delivery module's delivery-done announcement: a design's state (§ 9) + ├ consumes the delivery module's delivery-done announcement: a design's state (§ 9) + ├ consumes the issue tracker's, mesh-delivery's and the router's events for the overview page (§ 11) + └ serves the overview page, read-only, on a port it declares, through the route proxy (§ 11) mesh-issues (control-node) the counters for decision and research numbers (§ 6); the issues' design links, read for a design's state (§ 9) @@ -519,7 +527,8 @@ The records leave the store once, and the database is dropped only after a check 1. **Order.** The ledger is built first, in the order of ADR 0595 §10: mesh-sdk's typed `requires`, binding and package of the new calls; the indexer's judge's rules; the bus module's process, verbs and events; the tool - runner's calls, `apply` among them, with its ledger credential per module; then mesh-knowledge moves, asking for + runner's calls, `apply` among them, with its ledger credential per module assignment (ADR 0597 §9, as its + progressive insight of 2026-11-03 has it, waiting for the operator's Approve); then mesh-knowledge moves, asking for its ledger, states and stream in `requires`. An indexer whose judge predates the new form refuses a manifest that uses it, and the placement holds the module off a machine whose tool runner does not read the typed binding, so mesh-knowledge cannot be delivered before them. The release sdk1 (issue 1585, ADR 0593 §10), which @@ -768,10 +777,82 @@ done: no state `names`, and the bus holds none for it once the move's check is clean; its tools and health name the ledger's repairs and lag, and no digest store; the consumers of `reviews` still hear it. +## 11. The overview page + +**The knowledge base serves one read-only web page for the operator** (ADR 0616 §1), so the state of the work is one +address away rather than an answer read in chat. It shows three blocks: every design in progress with its milestones +and their issues (the tree the `progress` verb answers); the decisions and research efforts accepted in the last seven +days and those waiting (proposed, or waiting for the operator's Approve); and what waits on the operator: records +waiting for an Approve, the operator channel's open asks and the delivery groups waiting for an Approve. The seven days +are counted from the ledger's dates when the page or its tool answers, never kept as a window in the model. What +reached which nodes is not on it: a merged group no longer waits on the operator. + +``` + mesh-knowledge's process + ├ its own ledger records, their status and dates (no first view needed) + ├ issue tracker first view: milestones, list then its events + ├ seat mesh-delivery first view: groups then group, transition, merged + ├ seat operator-channel first view: asks then the router's offered, closed + │ (a restricted stream granted to the knowledge base alone) + ▼ + the page's model, in memory ──▶ the page (GET only) ──▶ route proxy ──▶ the operator + └▶ its tool (the model as data) +``` + +- **Built from mesh paths alone** (ADR 0616 §2). Besides its own ledger, the model comes from three other modules, each through + a verb for the first view and events after it: the issue tracker's `milestones` and `list` and its events; the seat + `mesh-delivery`'s `groups` and its `group`, `transition` and `merged` events, a group leaving the block on `merged`; + and the seat `operator-channel`'s `asks` and the router's events `offered` and `closed`, an ask entering the block on + `offered` and leaving it on `closed`. It never hears an asker's `decided` family. It reads no other module's bus + state (ADR 0596 §2), and its manifest holds no `reads` entry, which the judge refuses (ADR 0595 §8). +- **The router says when an ask starts and ends** (ADR 0616 §7). The router holding `operator-channel` emits `offered` when it + offers an ask on a channel, and `closed` once for every end of an ask: answered, expired, cancelled by its asker, + replaced by a newer ask about the same subject, or ended by the router itself on an `unlink` or `allow-link`. Each + carries the ask's id, asker, headline and the channels it was offered on, and `closed` how it ended; never an option, + a token or a warrant's proofs, so neither says more than `asks` already answers. Both go on a restricted stream of + the router (ADR 0363 §4), heard only by a module the router's trusted config value `readers` grants, which names the + knowledge base alone; `decided` stays read by its asker only. +- **Current on events, never polled** (ADR 0616 §3). Each source's block is built once at start from its verb, its first view, + and then kept in memory from that source's events. A verb is asked again only for a source marked unanswered. +- **A source that does not answer says so, never shows as empty** (ADR 0616 §6). A source is marked unanswered for one of two + causes, named in the mark: its verb did not answer within its bound, or its subscription missed the bus's idle + heartbeat past its bound, so a lost subscription or a silent source is found, not taken for a quiet one. Its block, + on the page and in the tool's answer, then says so with the time of its last answer (the verb's last answer or the + subscription's last event or heartbeat, whichever is later). A mark from the verb: the verb is asked again on a + bounded backoff (from 10 seconds, doubling, at most every 5 minutes), and its first answer rebuilds the block and + clears the mark. A mark from the subscription: nothing is asked while it is silent; its next event or heartbeat + makes the module ask the verb once, to rebuild the block with what it missed, and that answer clears the mark (an + unanswered verb then turns the mark into one from the verb). These are the only asks the page makes after start. +- **It writes nothing** (ADR 0616 §4). The page has no form and no verb behind it and answers GET only. It is served by the + module's own process on a port the module declares, reached through the route proxy by a route the module + requires, which only the operator may open. +- **Every number on it can be read by a tool** (ADR 0616 §5): a tool of the knowledge base answers the page's model as data, + marked reads, so the page shows nothing a tool cannot read (tools-are-how-a-module-is-spoken-to). +- **Health.** The module's health carries the page's last rebuild and each source's last answer, and is degraded while + any source is unanswered. +- **Built after the ledger store**: the page reads the records from the ledger (§ 2.1), so it is built after the + knowledge base's ledger store, in the same milestone (ADR 0613 §1). + +How it is checked (ADR 0616 *How it is checked*): a unit test builds the model from fixture issues, milestones, +records, groups and asks and sees the three blocks; a fixture `group` or `transition` event changes the delivery block +and `merged` takes the group out; `offered` adds an ask and `closed` takes it out; no verb is asked again in either +case; a record accepted eight days before the answer's time is not in the seven-day block, with no event between. The +manifest has no `reads` entry, names `groups` and `asks` among what it invokes, and consumes no `decided` family. The +handler refuses any method but GET, and the route's grant names only the operator. The tool's answer equals the model +the page renders. For each source, a verb failing at start marks it unanswered from the verb on the page, in the tool +and in the health, never as an empty list, asked again on the backoff alone, its first answer clearing every mark; +and a subscription stopping its heartbeat marks it unanswered from the subscription, asks nothing while silent, and +asks the verb once on its next event or heartbeat. In the router, an ask offered emits `offered` once and each of its +five ends emits `closed` once with how it ended, with no option, token or proof in either body; the knowledge base +hears the stream and another module, an asker among them, is refused. Live: after its delivery the operator opens the +page and sees design 47's milestones, rendered headless and looked at before it is handed over. + ## What it leaves out -Semantic or vector search; a browser view (its reading of the buckets is announced, ADR 0592); line comments on a proposal (the judgement's findings name the section and -clause instead); and the rules of how-we-build, which are not decision records. Each needs a decision of its own. +Semantic or vector search; a browser view other than the overview page of § 11 (its reading of the buckets is +announced, ADR 0592); line comments on a proposal (the judgement's findings name the section and clause instead), +and any form or writing from the overview page; and the rules of how-we-build, which are not decision records. Each +needs a decision of its own. ## Reviewed by architectural domain diff --git a/AGENTS.md b/AGENTS.md index 0bf61105..227191d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,9 @@ playbook 01's *Review by architectural domain*, two architectural domains each: `review-health-and-events`; the lead starts all four side by side and rules on their findings. A fifth, `research-reader`, carries one reading task of playbook 01's step 5: code on main, counts from the manifests, a try in process. Four more carry playbook 07: `mesh-preparer` (the prepare step), `mesh-developer` (develop), -`mesh-code-reviewer` (the review) and `mesh-watcher` (every wait). Every agent starts from the knowledge base and maps +`mesh-code-reviewer` (the review) and `mesh-watcher` (every wait). A tenth, `hq-design-writer`, writes one set of design amendments +(playbook 02, *Amending an existing design*) from accepted decisions and a milestone's findings, in a worktree of its +own, ready for the review by architectural domain. Every agent starts from the knowledge base and maps each interaction onto a mesh path; the lead writes only the handover, never an agent's way of working. A subagent runs in the session that started it, from hq: its way of working comes from its definition and hq's playbooks, its knowledge from the knowledge base; one given a coding task works in a worktree under hq's `.work/` and also reads the conventions of the repository it changes, and where those contradict hq, hq wins and the agent says so. ## The development cycle -- 2.54.0 From 2d48b85c0e45d8f3178a55bc6f0d9a28b750d8d7 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:09:56 +0100 Subject: [PATCH 02/20] Design 51: answer the first review of the milestone amendment (builds counted from the records, the write side's window and outage, the revert's open points, holds and the old name's events, live proof) Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../51-the-registries-keep-what-is-named.md | 84 ++++++++++++++++--- 1 file changed, 71 insertions(+), 13 deletions(-) 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 243393a6..d60c9c78 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 @@ -62,7 +62,12 @@ asks the status view's `node_haves` for every node, away ones included, and stop whose declaration names an artifact has applied a build whose node-engine states the capability to read the new names (ADR 0614 §4), judged by that stated capability and never by a fingerprint. Its tool `registry_old_name` lists the nodes still holding it open, and drops a retired node from the window by hand, recorded as a hand act; a node holding -it open for seven days is a finding naming the node, cleared when the window closes. `cargo-package-registry` stays +it open for seven days is a finding naming the node, cleared when the window closes. Closing the old name emits +`old-name-closed` on both image seats' `.events`. A node away when the last `delivered` was said must close the +window when it returns and applies the new build, with no other delivery: the event that says such a node's return, +and the verb that answers each node's stated capabilities, are not yet named by any record or design (the stated +capability comes with the release that adds it, design 47); until they are, the window waits on the next `delivered`, +tracked on issue 1966. `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`, `publish-part` (ADR 0611), `versions`, `remove` (a @@ -131,13 +136,23 @@ the verb. writes from any process on the mesh network, so the rule protects nothing until its write side is closed. So `distribution`'s own program serves the store's port in front of the registry container, which listens on its node's loopback only: reads (the OCI distribution protocol's GET and HEAD) stay open to every node's container - runtime, and a write reaches the store only from `distribution`'s own process, after a `publish` it accepted. Each + runtime, and a write reaches the store only from `distribution`'s own process, after a `publish` it accepted. + **Until every build module publishes through the seats**, the build modules' builds still running push straight to + the store, as they do today: so `distribution`'s program also accepts a write from a build module's own account + until every node's have of every build module, read through `node_haves` at its start and on each `delivered`, runs + a build that publishes through the seats; then it refuses them, naming `publish`. **While the program is down**, + the store answers no read and no write: a node pulling an image fails and retries, and what already runs is not + touched; the holder's health check, which probes the read side every minute, names it, and the program serves + again at once when it restarts, holding nothing but its transfers in `incoming`. Each holder keeps its write side as an own secret beside a read-only tool token (the forge module: `write:package`, its tool token `read:package`). - **The package seats' read sides need no credential** for the mesh's own packages: the forge serves the mesh owner's packages to anonymous reads, so the provisions `npm-package-registry` and `go-package-registry` carry no credential (form none), and mesh-build-node, which held a pair for both writing and installing, requires none (the - operator's answer of 2026-11-03, that the mesh's packages may be read without a credential). + operator's answer of 2026-11-03, that the mesh's packages may be read without a credential). The scope is the + mesh network only: the store's GET and HEAD and the forge's package reads answer anonymously there, and through no + public route. Whether the forge has a public route that would also serve its package reads anonymously is not + known to this design, to confirm with the operator before the merge. - **The forge module serves both package seats' verbs from its Go bundle**, the one that already serves its package retention, renamed for both packages; its tools bundle in TypeScript is unchanged, so no holder needs generated TypeScript types in this milestone. @@ -161,7 +176,9 @@ the verb. naming the merge it waits on. A fingerprint already numbered keeps its number. - **A package is numbered per source revision**, not per fingerprint: a second trunk build of a revision already numbered for that package (a build module's change reaching it by a second build, ADR 0603 §3, or the contracts - move's one rebuild) keeps the package's version and publishes nothing, since a package's content is its source. + move's one rebuild) keeps the package's version and publishes nothing, since a package's content is its source, + but only when `versions` holds that revision `published`; a revision numbered but never stored is published by its + next build under the same version. The build log says the version was kept, and why. So a publish that triggers a rebuild of the package's own build module (the shared contracts, the forge module that holds their registry and the Go build module that builds them, ADR 0614 §6) never comes round to a new version, and the triggers end. An image, bundle or archive is numbered per fingerprint, as above. @@ -178,9 +195,11 @@ the verb. - **An unknown floor does not stall the build of an image, bundle or archive.** The floor of `distribution`'s own names is `distribution`'s `versions`, which only its new build serves, and the same holds for the forge module and the builder: waiting would never end. So a trunk build of an image, bundle or archive whose floor is not yet known - is built unnumbered and published by its digest (as a bundle or archive is from every done key, ADR 0605 §3); once - the floor answers, the builder numbers it, and the build module calls `publish` with the version and the held - digest, which stores nothing. A package's trunk build waits for its floor, since a package is published only under + is built unnumbered and published by its digest (as a bundle or archive is from every done key, ADR 0605 §3). That + publish is what wakes the floor: the builder hears the holder's `published` on its durable consumer of + `.events` and asks the floor again then, never on a timer; once all three answer it numbers the build and emits + `numbered`, which the build module consumes to call `publish` with the version and the held digest, which stores + nothing. A package's trunk build waits for its floor, since a package is published only under its version. - **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**: @@ -210,7 +229,10 @@ the verb. branch's end; retention may remove an old unreferenced one (below). - **The forge bridge says a branch's end**: beside its `merged`, `refused`, `open` and `pull` kinds on its stream `forge`, it says a branch-deleted kind, from the forge module's `forge.delete` event, which it consumes on a durable - consumer as it consumes the pull events. Its tool `bridge_tags` answers a package's git tags, for the floor. + consumer as it consumes the pull events. Only the forge module's account may publish `forge.delete`, the subject + being in its own namespace (design 32 § 2), so no other module can end a branch's tagged versions. Every existing + consumer of `forge` ignores a kind it does not know. Its tool `bridge_tags` answers a package's git tags, for the + floor. ### Retention @@ -220,6 +242,10 @@ With the store's write side closed, the artifact sweep (seat `mesh-artifact-swee longer deletes or holds by writing to the store: it calls `remove` on the seat that holds the thing, with the reason retention, and `hold` to keep a kept archive from removal, both granted to its holder alone by `distribution`'s caller list; its rules of what is kept (*The artifact store* below) stand, and it reads both reference forms. +`distribution` keeps what is held in its own state `holds` (bus state, template state), emits `held` and `released` +on the seat's `.events`, and its tool `registry_holds` lists what is held, since when and for whom, and +releases one by hand. A `remove` or `hold` the holder refuses to the sweep writes the sweep's finding, naming the +thing and the reason, cleared by its next run with no refusal. 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 @@ -251,6 +277,22 @@ its own rule; nothing stores the order (ADR 0612 §2, ADR 0614 §1). The edges t | The holders' `incoming` and `.events` exist before the holders start | bus state: each entry is a requirement on the provision `bus-state` of the bus module (ADR 0614 §5) | the assigner, as for any provision | | The shared contracts, the forge module that holds their registry and the build module that builds them | package and build module edges are triggers, never waits; the cycle they make holds nothing and is not refused (ADR 0614 §6); inside the group every member takes `mesh-contracts` and mesh-sdk by the group's head (the checker's group pin) | the indexer's graph | +**How many builds** (ADR 0603 §3). The three build modules change in this milestone, so every module they build is +built twice: at once with the recipe that runs, and again once a majority of build nodes runs the new build module. +The contracts move rides that first build and adds none (ADR 0613 §1). Whether the rebuild that a first numbered +publish of mesh-sdk or `mesh-contracts` triggers in their users (ADR 0614 §6) joins the second build or adds a third +is not decided by ADR 0603 or ADR 0614, so this design gives no count beyond two per module; it is put to the +operator. + +**The node-engine's own build keeps the old reference form** until every node-engine states the capability to read +the new one: a node-engine away across the milestone must be able to read the build it wakes to, or it could never +update itself (ADR 0603 §7, §9). The builder writes the node-engine's done key in the old form until the stated +capabilities, which the status view reads, show it on every node. + +**When the group is pushed.** Only once the release that adds the node-engine's stated capability (design 47) runs on +every node, read from the status view's haves before the push, and once the progressive insights of ADR 0595 and +ADR 0597 this design is written to are accepted. + **The milestone's own check** runs the changed build modules (ADR 0603 §10) against the running holders, which serve no `publish`: a changed build module whose manifest invokes a verb no running build serves is one the check cannot run, and the modules it builds are composed from their manifests, the verdict naming them and the verb (ADR 0614 §7). @@ -267,7 +309,10 @@ guard go in the same change (ADR 0606 §6, read with ADR 0612 §1). **Rolled back by one revert** (ADR 0612 §4): reverting the milestone's merge brings back the build modules' manifests that require the write credentials, so the vault mints them again as own secrets on the revert's delivery, and the builds that publish through the seats go with it. The old seat name still retires on the node-engines' -reports (ADR 0610 §4). **On a fresh mesh** the foundation's bootstrap (research 075) pushes the holders' own images +reports (ADR 0610 §4). Two things of the revert are not decided by any record and are tracked on issue 1966: which +edge delivers `distribution`'s reverted build, which takes direct writes again, before the build modules' reverted +builds that push straight to the store; and whether a done key written in the new form is built again after the +revert or must stay readable by the reverted readers, which the old mesh-sdk refuses. **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`. ### Acceptance tests @@ -301,17 +346,30 @@ answers (a holder down whole is its module's own health check's); `registry_inco - **Holder, added for the milestone.** A write to the store's port from any account but `distribution`'s process is refused, and a read from a node's container runtime is answered; the artifact sweep's `remove` with the reason retention removes an untagged bundle and emits `removed`, and the same call from another caller is refused; `hold` - keeps an archive from removal; the forge bridge says a branch-deleted kind for the forge module's `forge.delete`. + keeps an archive from removal, emits `held` and is listed by `registry_holds`, which releases it with `released`; a + refused sweep `remove` writes the sweep's finding and its next clean run clears it; the forge bridge says a + branch-deleted kind for the forge module's `forge.delete`, a `forge.delete` from another account is refused by the + bus, and an existing consumer of `forge` ignores the new kind; with the program stopped a read fails and the + holder's finding is written, and after its restart the read is answered and the finding cleared; a build module's + old build pushing straight to the store is accepted until every node runs a build module publishing through the + seats, and refused after. - **Builder, added for the milestone.** With the seat's holder down at a first claim, a bundle's trunk build is built - unnumbered and published by its digest, and is numbered once the floor answers; a package's trunk build waits; - a second trunk build of a numbered package's revision keeps its version and publishes nothing. + unnumbered and published by its digest, the holder's `published` makes the builder ask the floor again and number + it, and the build module, hearing `numbered`, publishes the version with the held digest; a package's trunk build + waits; a second trunk build of a package's revision held `published` keeps its version and publishes nothing, + while one numbered but refused is published by its second build under the same version; with `bridge_tags` + silenced (a floor source other than the publish target) the floor finding is written and cleared once it answers. - **Checker.** A changed build module invoking a verb no running holder serves is not run, and the verdict names the modules composed from their manifests instead, and the verb. - **Vault.** Reverting the milestone's merge mints the build modules' credentials again; the bootstrap's one-off credential is gone once the holders serve `publish`. - **Live.** The status view shows each build module's new build waiting on its holder's build serving `publish` until that runs healthy (ADR 0612 *How it is checked*); no build module's provision key holds a write credential; - `registry_old_name` names the nodes holding the old name open, an away node among them. + `registry_old_name` names the nodes holding the old name open, an away node among them. After the delivery: + mesh-sdk's first numbered version is `published` on the Go seat and an image is read back by its digest, both + shown by `builder_versions`; a node runs a module from a reference in the new form; a node-engine away across the + milestone wakes and applies the new node-engine. Go's `direct`: its removal is named on the milestone's issue, and + the change that removes it adds to the Go build module's recipe test a case that refuses `direct` in the fetch. ## Reviewed by architectural domain -- 2.54.0 From 34384b6c9cfd59644165e6cb6864adbd6754c451 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:10:19 +0100 Subject: [PATCH 03/20] =?UTF-8?q?Design=2025=20=C2=A7=2012:=20answer=20the?= =?UTF-8?q?=20first=20review=20of=20the=20milestone=20amendment=20(the=20b?= =?UTF-8?q?us=20module's=20first=20view=20against=20replays=20and=20an=20u?= =?UTF-8?q?nanswered=20tool,=20the=20foundation=20readers'=20bounds,=20fam?= =?UTF-8?q?ilies=20and=20tools,=20the=20tests=20they=20join,=20the=20open?= =?UTF-8?q?=20grant)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 42 ++++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 2e330753..8d0c4a4b 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -697,13 +697,20 @@ modules' names meet. - **The trigger is the judge's verdict** (ADR 0595 §4), heard as the indexer's event `manifest-accepted`, which carries the module's entries whole, so the bus module never reads the indexer's state and never polls. The indexer says it for every newly accepted manifest, with its entries empty when it asks for none, and the bus module reads an - empty one as "this module asks nothing now", which is what its orphans need. It hears the verdicts on a durable - consumer, so one given while it was down is heard when it is back. **Its first view** at start is the indexer's + empty one as "this module asks nothing now", which is what its orphans need. It goes, as every event of the + indexer, on the module events stream (ADR 0363) under the indexer's own subjects. A publish of it that fails is + published again, and one still failing writes the indexer's finding naming the module, cleared once said (today + the indexer only logs it). It hears the verdicts on a durable consumer, so one given while it was down is heard + when it is back. **Its first view** at start is the indexer's tool `indexer_bus_state` (every accepted manifest's bus-state entries, by module and digest), asked once; the - events keep it current after that, and a stream that keeps a verdict only for its age is never its source. It keeps + events keep it current after that, and a stream that keeps a verdict only for its age is never its source. A + verdict replayed from its consumer is applied only if newer than what the first view answered for that module (by + the manifest's digest and the indexer's revision), so a replay never takes an entry back. An `indexer_bus_state` + that does not answer makes the bus module's health unhealthy, naming the verb, and is asked again on a bounded + backoff (from 10 seconds, doubling, at most every 5 minutes); nothing is created or orphaned meanwhile. It keeps what it last heard per module in its own state `asked` (an old `state` section, its own kept data staying there, - ADR 0595 §10), from which `list`, `check` and `orphans` answer; a key goes when the module's accepted manifest asks - for nothing, or the first view no longer names the module. Then it runs `check` over what it holds, creating what + ADR 0595 §10), from which `list`, `check` and `orphans` answer; a key goes only on an empty `manifest-accepted` for + the module, never because a manifest the judge set aside to wait is missing from the first view. Then it runs `check` over what it holds, creating what is missing. The judge checks every entry once, at the merge check and at take-in (ADR 0578; ADR 0595 §8), and also each ledger type's operations: a set-entry or a remove names `into` and `key`, `fields` and an index's `on` name fields of the schema, and `digest-of` names a text field. @@ -740,13 +747,26 @@ vault as an own secret: JetStream create and update under modules' names only (s through the tool runner, so the tool runner never asks through itself: the assigner serves `placement` to the grants part, the tool runner and the indexer, `seat-holders` to the tool runner and `wants` to the grants part; the indexer serves `manifests` and `modules` to the grants part, the tool runner and the assigner (ADR 0615 §3). Each - reader declares these as verbs it invokes. The owner answers at once from what it holds, as soon as its process is + reader declares these as verbs it invokes. Serving a verb on a process's own subject, outside the tool runner, has + no manifest form today (the assigner serves its seat verbs as tools); the form is new in mesh-sdk's manifest types + and rides the milestone's one rebuild. Which part issues these subjects' grants first is not yet decided: the + grants part composes every grant from the manifests, and is itself one of these readers, so its own first view + cannot wait on a grant it has not composed (an open point, tracked on issue 1966). The owner answers at once from what it holds, as soon as its process is connected and before it has its own first view of any other part, with the revision of its state it answered at, so the indexer and the assigner, each asking the other at start, never wait on each other (§4). The reader then follows the owner's declared events, each carrying the revision of the state it follows, and never asks the verb - again on a timer; a gap in revisions makes it ask once more, said in its log (§5). A reader that starts while its - owner is down raises a condition naming the owner and the verb past its bound and keeps asking until it answers, - never reading the owner's state instead; once it had its first view, it keeps working on what it heard (§6). The + again on a timer; a gap in revisions makes it ask once more, said in its log (§5). The owners' events are new + families of their own, apart from the indexer's `manifest-refused` and `manifest-accepted` and the assigner's + `put`: the indexer's `indexer.views` and the assigner's `assigner.views`, one event per changed key with its + bucket, key and revision, kept seven days, declared in each owner's old `streams` section (its own kept data), + so a waiter on an existing event never wakes on them. A reader that starts while its owner is down raises a + condition naming the owner and the verb past its bound and keeps asking until it answers, never reading the + owner's state instead; once it had its first view, it keeps working on what it heard (§6). The bound is longer + than an owner's start (two minutes), so a normal start, readers and owners coming up together, notifies nobody; + the owner's answer clears the condition. An answer that is empty at revision 0 (an owner that holds nothing yet) + is a first view of nothing, never a reason for the grants part to drop a login it holds: it keeps the list in + force until an answer at a revision above 0. Each reader's tools say the owner, verb and revision its first view + holds (a read tool), and ask the first view again by hand (a re-read tool, recorded as a hand act). The order of owner before reader is kept by the module graph's served-verb edge for the 19 readers (ADR 0614 §2), and, for the four, whose verb edges lie on cycles the graph leaves out (ADR 0614 §3), by the owner's answer at once and the reader's wait (ADR 0615 §2). @@ -919,7 +939,9 @@ issue 1966. | The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused | | The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at the raise mark and cleared only below the clear mark; `recreated` rewrites the binding and `dropped` removes it; at start the first view from `indexer_bus_state` creates what a verdict given while it was down asked; the older remove tools refuse a new-form entry, naming `drop`'s tool | | A module asking bus state waits for the bus module | the module graph's table test: a manifest with a ledger entry has a requirement edge to the module providing `bus-state`, one without has none (ADR 0614 §5) | -| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked): the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition past the bound and never reads the owner's state; the four readers' manifests hold no `reads` of the indexer's or the assigner's state | +| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`cmd/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`cmd/mesh-delivery-assigner`) and the grants part's (`cmd/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an existing waiter on `manifest-accepted` does not wake on the new families; the read tool shows the revision each reader holds. Live, after the delivery: the indexer's count of `reads` entries on the indexer's and the assigner's state reads 0 | +| The bus module's first view | cases in the nats module's one integration test: a replayed verdict older than the first view is not applied; an `indexer_bus_state` that does not answer turns the health unhealthy naming it and is asked again on the backoff; a set-aside manifest missing from the first view keeps its `asked` key, an empty `manifest-accepted` removes it | +| `manifest-accepted` is never lost silently | a unit test of the indexer: a failed publish is published again, and one still failing writes the finding | | Until the identity seat, the caller is the one the tool runner proved | a unit test of the bus module's process: a request on its tool subject naming another caller in its body acts for the proved caller; `drop` without the warrant is refused | | Drift and orphans, live | live, made only by a manifest change: a manifest that shrinks a setting raises `drifted`; the conditions clear as above. Nothing is filled on the live bus, and no orphan is made there | | Overdue and stopped are loud | live, carried by the change of the first module that moves in step 5, never by a merge made for the test alone: with the bus module's process stopped, an accepted manifest asking for an entry raises the overdue condition within five minutes, from the watchman, and a later `created` clears it; the bus module's health turns unhealthy when its pass is more than three minutes old | -- 2.54.0 From 4710fd18c6fb393ccf37a8302dc7cea441ec3fc9 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:10:40 +0100 Subject: [PATCH 04/20] Design 26: answer the first review (the seat list's grain as ADR 0613 has it; out of step cleared only by the same contracts module) Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/26-the-seats.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 2e435973..10e165fa 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -189,8 +189,7 @@ way, in the milestone of ADR 0612 (ADR 0613 §1). `mesh-broker`. Every other seat, the foundation's included, has its definition on the bus in the same milestone, all at once (ADR 0608 §2), holding the seat list's grain: its name, whether it is held once or on every node, its verbs with their inputs and its provision, generated from the seat list mesh-sdk compiles today, so the move changes - nothing a reader sees (ADR 0613 §2). A verb this milestone adds to such a seat (`mesh-config`'s `renames`, - `mesh-vault`'s `retire`, design 32 § 8) is added at that grain. Its messages, events and state layouts are added to + nothing a reader sees (ADR 0613 §2). Its messages, events and state layouts are added to a seat's definition when its area's contract next changes, which moves that area's seats whole into its contracts module in that change (ADR 0613 §3); which area moves when is tracked by issue 167 (ADR 0613 §4). - **The seat set has one source.** The judge, the node-engine, the tool runner and claude-code each compile a copy of @@ -222,7 +221,7 @@ way, in the milestone of ADR 0612 (ADR 0613 §1). - **Findings**, written by the indexer, from which the condition keeper raises and clears the conditions (ADR 0564): a trunk's seat definition that does not parse, `seat-definition-unparsed` (a pull request's is its check's verdict, not a condition), cleared by a definition that parses; generated types on a trunk out of step with their definition, - `generated-types-out-of-step`, cleared by a clean build end. **Tools** of the indexer: `indexer_seat_definition` + `generated-types-out-of-step`, cleared only by a clean build end of the same contracts module. **Tools** of the indexer: `indexer_seat_definition` (read one), `indexer_seat_definitions_again` (read a trunk's or a pull request's again now) and `indexer_out_of_step` (the generated types out of step). - **Contracts modules.** A seat's contract is published by a contracts module, which holds contracts only (every file @@ -250,8 +249,9 @@ Acceptance tests: research 081's 14, 21, 25 and 26; a unit test that the generat mesh-sdk compiled before the move, entry for entry; mesh-sdk's seat-naming test; each build module's compare step failing on a differing, a missing and an unwritten file, naming each; the checker's `verdict` naming out-of-step files; and cases of the indexer's whole-module integration test: a trunk -definition that does not parse writes its finding and a fixed one clears it; a check verdict naming out-of-step types -writes the finding and a clean one clears it; `indexer_seat_definition` answers the trunk's entry while a pull request +definition that does not parse writes its finding and a fixed one clears it; a trunk build end naming out-of-step types +writes the finding, a clean build end of another contracts module leaves it, and a clean one of the same module clears +it; `indexer_seat_definition` answers the trunk's entry while a pull request holds another; a pull request's entries go when it closes; a definition over 256 KiB is refused; each of the three tools answers. -- 2.54.0 From 3b1278b3bcf743ee25cf3914416b9764de02a409 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:10:57 +0100 Subject: [PATCH 05/20] Design 32: answer the first review (the checker invokes renames and waits for it, retire's caller list and warrant, retired events, tests, the away node's window) Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../01-to-be/32-what-a-module-declares.md | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) 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 6b2b1689..aca771d2 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 @@ -515,7 +515,10 @@ value it holds under the old name to the new name in its own state, without the - **How long the old name is read** (the **rename window**): 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 status view's `delivered` events and, at its start and on each, asks its `node_haves` for every node, away ones included, since `delivered` - counts only the nodes present (design 47); until then mesh-config answers both names. A node holding a window open + counts only the nodes present (design 47); until then mesh-config answers both names. A node away when the last + `delivered` was said should close its window when it returns, with no other delivery; the event that says its + return is not yet named, as for the old seat name (design 51), and until it is the window waits on the next + `delivered`, tracked on issue 1966. A node holding a window open for seven days is mesh-config's finding naming the node, cleared when the window closes; `config_rename_close` drops a node from a window by hand, recorded as a hand act. - **The checker asks the seat, never the module.** The seat `mesh-config` gains the optional verb `renames` (the @@ -524,7 +527,11 @@ value it holds under the old name to the new name in its own state, without the naming the verb, and the checker then refuses `renamed-from` naming that; while a window is open, the same answer refuses dropping `renamed-from`, naming the nodes. A check that cannot ask fails, and says so. So no manifest uses `renamed-from` in the milestone that brings it, whose running mesh-config serves no `renames`; the first that does - is its live case. `config_renames` names, per rename, the nodes still holding the old name open. + is its live case. The checker declares `seat:mesh-config.renames` in its `invokes`, so by the served-verb edge + (ADR 0614 §2) its new build waits until a build of mesh-config that serves `renames` runs healthy; no cycle closes, + since mesh-config waits only on the indexer's `indexer_config_readers`. The "not served" answer is then met only by + the running checker in the milestone's own check, and after a revert. `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, cleared once the value moved or `renamed-from` left the trunk's manifest. **Tools of mesh-config**: `config_renames` (what moved, from which name, when), `config_rename_again` (move one value by hand @@ -542,7 +549,11 @@ kept seven days) and asks the operator once, through the operator channel, wheth still read it until lately; it is removed only on the operator's Approve. **mesh-config holds no secret**, so the retiring flow and its one ask stay mesh-config's, and on the Approve it removes a value itself and retires a secret through the seat `mesh-vault`'s new optional verb `retire` (one module's own secret, by node, module and name), carrying -the operator's warrant, which the vault checks (ADR 0259 §6) before it removes anything. An ask unanswered for seven +the operator's warrant, which the vault checks (ADR 0259 §6) before it removes anything. The vault's caller list for +`retire`, a trusted config value in ADR 0363's form, names the holder of the seat `mesh-config` alone, checked against +the caller the tool runner proved; the warrant is bound to the node, module and name it was asked for, so it retires +nothing else. mesh-config then emits `retired` on `config.events` (module, name, node, the ask), and the vault its own +event that it removed the secret. An ask unanswered for seven days is asked once more, and then written as a finding, cleared by an answer, a cancel, or the value read again; a sleeping node holding a retirement open for seven days is a finding naming it. `config_retiring` lists what is retiring, since when, and its ask, and asks it again or cancels it. @@ -553,6 +564,14 @@ declared in its old `state` and `streams` sections in this milestone, as the bui process, which is delivered in the same milestone. They move to bus state when each module next moves (ADR 0595 §11). `config-changed` stays on the shared events stream, unchanged, so its consumers do not move. +**How the rename and the retiring are checked**, as cases of mesh-config's first whole-module integration test (C20, +C24 below), of the checker's and of the vault's existing whole-module tests: the checker's ask of `renames` meeting a +holder that does not serve it refuses `renamed-from` naming the verb, one meeting an open window refuses dropping +`renamed-from` naming the nodes, and a check that cannot ask fails saying so; `retire` with the warrant removes the +secret and emits both events, without it, with a warrant for another name, or from a caller not on the list it is +refused; `config_rename_close` drops a node from a window and records the hand act; a window held seven days and a +retirement held seven days each write their finding naming the node, cleared when it closes. + ### The mesh delivery cases this design carries Research 081 listed the mesh delivery cases; these are the ones it handed to a design, each with its acceptance test, -- 2.54.0 From da823becbe5c180ea2bb85725b74cd7c8fb282ed Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:11:15 +0100 Subject: [PATCH 06/20] =?UTF-8?q?Design=2055=20=C2=A7=2011:=20answer=20the?= =?UTF-8?q?=20first=20review=20(the=20router's=20stream=20kept=20and=20cap?= =?UTF-8?q?ped,=20the=20page's=20port=20reached=20only=20by=20the=20route?= =?UTF-8?q?=20proxy,=20a=20re-read=20tool=20per=20source)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/55-the-knowledge-base.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/03-DESIGN/01-to-be/55-the-knowledge-base.md b/03-DESIGN/01-to-be/55-the-knowledge-base.md index 41fa48b2..d990358a 100644 --- a/03-DESIGN/01-to-be/55-the-knowledge-base.md +++ b/03-DESIGN/01-to-be/55-the-knowledge-base.md @@ -811,7 +811,8 @@ reached which nodes is not on it: a merged group no longer waits on the operator carries the ask's id, asker, headline and the channels it was offered on, and `closed` how it ended; never an option, a token or a warrant's proofs, so neither says more than `asks` already answers. Both go on a restricted stream of the router (ADR 0363 §4), heard only by a module the router's trusted config value `readers` grants, which names the - knowledge base alone; `decided` stays read by its asker only. + knowledge base alone; `decided` stays read by its asker only. The stream is of the template events: kept seven + days, capped at 64 MiB, dropping its oldest when full, so a page down longer than a week rebuilds from `asks`. - **Current on events, never polled** (ADR 0616 §3). Each source's block is built once at start from its verb, its first view, and then kept in memory from that source's events. A verb is asked again only for a source marked unanswered. - **A source that does not answer says so, never shows as empty** (ADR 0616 §6). A source is marked unanswered for one of two @@ -825,9 +826,12 @@ reached which nodes is not on it: a merged group no longer waits on the operator unanswered verb then turns the mark into one from the verb). These are the only asks the page makes after start. - **It writes nothing** (ADR 0616 §4). The page has no form and no verb behind it and answers GET only. It is served by the module's own process on a port the module declares, reached through the route proxy by a route the module - requires, which only the operator may open. + requires, which only the operator may open. The port answers the route proxy alone, never the mesh network: it + listens `from: machine` where the route proxy runs on the same node; a listening scope that grants one module's + process (the route proxy's) on another node has no manifest form today, an open point tracked on issue 1968. - **Every number on it can be read by a tool** (ADR 0616 §5): a tool of the knowledge base answers the page's model as data, - marked reads, so the page shows nothing a tool cannot read (tools-are-how-a-module-is-spoken-to). + marked reads, so the page shows nothing a tool cannot read (tools-are-how-a-module-is-spoken-to); a second tool, + marked acts, asks one source's verb again by hand and rebuilds its block, recorded as a hand act. - **Health.** The module's health carries the page's last rebuild and each source's last answer, and is degraded while any source is unanswered. - **Built after the ledger store**: the page reads the records from the ledger (§ 2.1), so it is built after the @@ -844,7 +848,8 @@ and in the health, never as an empty list, asked again on the backoff alone, its and a subscription stopping its heartbeat marks it unanswered from the subscription, asks nothing while silent, and asks the verb once on its next event or heartbeat. In the router, an ask offered emits `offered` once and each of its five ends emits `closed` once with how it ended, with no option, token or proof in either body; the knowledge base -hears the stream and another module, an asker among them, is refused. Live: after its delivery the operator opens the +hears the stream and another module, an asker among them, is refused. The page's port refuses a connection from any +module but the route proxy. The re-read tool rebuilds one source's block and asks no other. Live: after its delivery the operator opens the page and sees design 47's milestones, rendered headless and looked at before it is handed over. ## What it leaves out -- 2.54.0 From 0ae7fc2e57f4b0b0114e2bd284c864070a4e9735 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:11:23 +0100 Subject: [PATCH 07/20] hq-design-writer: it calls no verb that writes on the mesh; what a record must carry goes to the caller Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .claude/agents/hq-design-writer.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.claude/agents/hq-design-writer.md b/.claude/agents/hq-design-writer.md index ede0564e..669a85ef 100644 --- a/.claude/agents/hq-design-writer.md +++ b/.claude/agents/hq-design-writer.md @@ -15,6 +15,9 @@ there and never push. record you did not expect may already decide a point. Read each design whole before changing it, hq's glossary (`00-META/glossary.md`) and playbook 02's steps for amending a to-be design. A clause the records do not decide is not written into a design as if they did: say it to the caller as an open point. +**It reads the mesh and writes nothing there.** It calls only verbs that read (`knowledge-base.get`, `ask`, `links`, +`holds`; `issue-tracker.list`, `get`); never `knowledge-base.reserve` or `propose`, nor any `issue-tracker` verb that +writes (`open`, `move`, `note`, `link`). What a record or an issue must carry goes to the caller in its report. **Then write, by the rules of the record.** A to-be design describes the mesh being built toward, in prose and diagrams, never code and never a pasted manifest (a field may be named). Each statement a decision carries -- 2.54.0 From 0168a545c5d3b52d2d2996725998538caa359300 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:12:24 +0100 Subject: [PATCH 08/20] Design 51: cite ADR 0617 for the unnumbered-first image and hold; narrow hold to what the sweep keeps, never an image Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../51-the-registries-keep-what-is-named.md | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) 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 d60c9c78..902fc58f 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 @@ -15,6 +15,7 @@ decisions: - "ADR 0612" - "ADR 0614" - "ADR 0613" + - "ADR 0617" - "ADR 0606" - "ADR 0607" - "ADR 0610" @@ -72,8 +73,8 @@ named, without a holder, until a build module publishes cargo packages. **One contract, a seat definition** (design 26, *A seat's definition*). Verbs: `publish`, `publish-part` (ADR 0611), `versions`, `remove` (a tagged version; the holder itself refuses `remove` of a version with no tag, but for the retention removal below), and -on `container-image-registry` `mirror` and `tags`; the two seats `distribution` holds add `hold`, for the artifact -sweep. The five seats this milestone changes, these four and `mesh-broker`, are defined whole, their contracts moved +on `container-image-registry` `mirror` and `tags`; the two seats `distribution` holds add `hold` and its release, +with the events `held` and `released`, for the artifact sweep (ADR 0617 §2). The five seats this milestone changes, these four and `mesh-broker`, are defined whole, their contracts moved into `mesh-contracts` (ADR 0613 §1; design 26). 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; each seat's events are its own family, `.events`, kept seven days. A holder stores @@ -83,8 +84,9 @@ under the version it is given and counts nothing. form ADR 0363 gives a restricted family: `publish` and `publish-part` the build modules, and a part only from the proved caller that opened its transfer; `mirror` mesh-build-image; `remove` the builder, for a tagged version, and the holder of the artifact sweep, for an untagged bundle, archive or copied base with the reason retention (ADR 0610 §3); -`hold` (on the two seats `distribution` holds: keep one held thing from removal, and let it go again) the holder of -the artifact sweep alone. Every removal, whoever asked it, emits `removed` with its reason. +`hold` (on the two seats `distribution` holds: keep one untagged bundle, archive or copied base that the artifact +sweep's rules keep from removal by retention, and let it go again; never an image of its own) the module that runs +the artifact sweep alone (ADR 0617 §2). Every removal, whoever asked it, emits `removed` with its reason. 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. @@ -192,10 +194,12 @@ the verb. pseudo-version a consumer requires (the indexer's tool `indexer_requirements`). The builder numbers nothing for that name until all three answered (ADR 0606 §3), 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`. -- **An unknown floor does not stall the build of an image, bundle or archive.** The floor of `distribution`'s own +- **An unknown floor does not stall the build of an image, bundle or archive** (ADR 0617 §1 for an image). The floor of `distribution`'s own names is `distribution`'s `versions`, which only its new build serves, and the same holds for the forge module and the builder: waiting would never end. So a trunk build of an image, bundle or archive whose floor is not yet known - is built unnumbered and published by its digest (as a bundle or archive is from every done key, ADR 0605 §3). That + is claimed with no number, so none travels in its build request, built unnumbered and published by its digest (as a + bundle or archive is from every done key, ADR 0605 §3; an image so by ADR 0617 §1). Such fingerprints are numbered + in merge order once the floor answers (ADR 0606 §1). That publish is what wakes the floor: the builder hears the holder's `published` on its durable consumer of `.events` and asks the floor again then, never on a timer; once all three answer it numbers the build and emits `numbered`, which the build module consumes to call `publish` with the version and the held digest, which stores @@ -240,9 +244,9 @@ The retention of *The package registry* below, and the artifact sweep, keep work removal emits `removed` and the builder marks its entry, so `versions` never says `published` for bytes that are gone. With the store's write side closed, the artifact sweep (seat `mesh-artifact-sweep`, held by artifact-store-tools) no longer deletes or holds by writing to the store: it calls `remove` on the seat that holds the thing, with the reason -retention, and `hold` to keep a kept archive from removal, both granted to its holder alone by `distribution`'s -caller list; its rules of what is kept (*The artifact store* below) stand, and it reads both reference forms. -`distribution` keeps what is held in its own state `holds` (bus state, template state), emits `held` and `released` +retention, and `hold` to keep an untagged bundle, archive or copied base its rules keep from removal by retention, +which then skips it (ADR 0617 §2), both granted to its module alone by `distribution`'s caller list; its rules of what is kept (*The artifact store* below) stand, and it reads both reference forms. +`distribution` keeps the holds in its own state `holds` (bus state, template state), emits `held` and `released` on the seat's `.events`, and its tool `registry_holds` lists what is held, since when and for whom, and releases one by hand. A `remove` or `hold` the holder refuses to the sweep writes the sweep's finding, naming the thing and the reason, cleared by its next run with no refusal. @@ -346,7 +350,7 @@ answers (a holder down whole is its module's own health check's); `registry_inco - **Holder, added for the milestone.** A write to the store's port from any account but `distribution`'s process is refused, and a read from a node's container runtime is answered; the artifact sweep's `remove` with the reason retention removes an untagged bundle and emits `removed`, and the same call from another caller is refused; `hold` - keeps an archive from removal, emits `held` and is listed by `registry_holds`, which releases it with `released`; a + keeps an archive from retention's removal, a `hold` of an image or from another caller is refused, emits `held` and is listed by `registry_holds`, which releases it with `released`; a refused sweep `remove` writes the sweep's finding and its next clean run clears it; the forge bridge says a branch-deleted kind for the forge module's `forge.delete`, a `forge.delete` from another account is refused by the bus, and an existing consumer of `forge` ignores the new kind; with the program stopped a read fails and the @@ -358,7 +362,9 @@ answers (a holder down whole is its module's own health check's); `registry_inco it, and the build module, hearing `numbered`, publishes the version with the held digest; a package's trunk build waits; a second trunk build of a package's revision held `published` keeps its version and publishes nothing, while one numbered but refused is published by its second build under the same version; with `bridge_tags` - silenced (a floor source other than the publish target) the floor finding is written and cleared once it answers. + silenced (a floor source other than the publish target) the floor finding is written and cleared once it answers; + with the image's own seat silent at the claim of `distribution`'s own image, the image is still built, published by + its digest, and numbered once its new build answers (ADR 0617 *How it is checked*). - **Checker.** A changed build module invoking a verb no running holder serves is not run, and the verdict names the modules composed from their manifests instead, and the verb. - **Vault.** Reverting the milestone's merge mints the build modules' credentials again; the bootstrap's one-off -- 2.54.0 From e51e6961e3db305d446ad4232fcf0a38dc511932 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:28:07 +0100 Subject: [PATCH 09/20] =?UTF-8?q?Design=2055=20=C2=A7=2011:=20say=20what?= =?UTF-8?q?=20from:=20machine=20gives=20the=20page's=20port,=20that=20loca?= =?UTF-8?q?l=20processes=20reach=20it=20until=20issue=201968,=20and=20that?= =?UTF-8?q?=20the=20route=20proxy=20runs=20on=20its=20node?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answers the second review's K1 by architectural domain. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/55-the-knowledge-base.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/03-DESIGN/01-to-be/55-the-knowledge-base.md b/03-DESIGN/01-to-be/55-the-knowledge-base.md index d990358a..b9d5cc69 100644 --- a/03-DESIGN/01-to-be/55-the-knowledge-base.md +++ b/03-DESIGN/01-to-be/55-the-knowledge-base.md @@ -826,9 +826,14 @@ reached which nodes is not on it: a merged group no longer waits on the operator unanswered verb then turns the mark into one from the verb). These are the only asks the page makes after start. - **It writes nothing** (ADR 0616 §4). The page has no form and no verb behind it and answers GET only. It is served by the module's own process on a port the module declares, reached through the route proxy by a route the module - requires, which only the operator may open. The port answers the route proxy alone, never the mesh network: it - listens `from: machine` where the route proxy runs on the same node; a listening scope that grants one module's - process (the route proxy's) on another node has no manifest form today, an open point tracked on issue 1968. + requires, which only the operator may open. The port listens `from: machine`, the narrowest listening scope the + manifest has today, so a connection from another node is refused. That scope does not name a module: every local + process of the knowledge base's node reaches the port, not only the route proxy, until issue 1968 gives a scope + that grants one module's process. That scope is a change to mesh-sdk's manifest types, so it rebuilds every module + that imports them, and it rides a later milestone's one rebuild rather than adding one here. A route proxy runs on + the knowledge base's node (read from placement, 2026-11-04), so the page is reachable through it; a route proxy on + another node cannot reach a port listening `from: machine`, so a page placed where none runs is unreachable until + issue 1968. - **Every number on it can be read by a tool** (ADR 0616 §5): a tool of the knowledge base answers the page's model as data, marked reads, so the page shows nothing a tool cannot read (tools-are-how-a-module-is-spoken-to); a second tool, marked acts, asks one source's verb again by hand and rebuilds its block, recorded as a hand act. @@ -848,8 +853,8 @@ and in the health, never as an empty list, asked again on the backoff alone, its and a subscription stopping its heartbeat marks it unanswered from the subscription, asks nothing while silent, and asks the verb once on its next event or heartbeat. In the router, an ask offered emits `offered` once and each of its five ends emits `closed` once with how it ended, with no option, token or proof in either body; the knowledge base -hears the stream and another module, an asker among them, is refused. The page's port refuses a connection from any -module but the route proxy. The re-read tool rebuilds one source's block and asks no other. Live: after its delivery the operator opens the +hears the stream and another module, an asker among them, is refused. A connection to the page's port from another node +is refused (what `from: machine` gives; a local process other than the route proxy is not refused before issue 1968). The re-read tool rebuilds one source's block and asks no other. Live: after its delivery the operator opens the page and sees design 47's milestones, rendered headless and looked at before it is handed over. ## What it leaves out -- 2.54.0 From 939d00032782182301826604900b9c5cab705f84 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:28:07 +0100 Subject: [PATCH 10/20] Design 32: name the vault's event for retire and its family Answers the second review's R1 by architectural domain: own-retired on the module events stream under the vault's subjects. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/32-what-a-module-declares.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 aca771d2..f3baafb4 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 @@ -552,8 +552,9 @@ through the seat `mesh-vault`'s new optional verb `retire` (one module's own sec the operator's warrant, which the vault checks (ADR 0259 §6) before it removes anything. The vault's caller list for `retire`, a trusted config value in ADR 0363's form, names the holder of the seat `mesh-config` alone, checked against the caller the tool runner proved; the warrant is bound to the node, module and name it was asked for, so it retires -nothing else. mesh-config then emits `retired` on `config.events` (module, name, node, the ask), and the vault its own -event that it removed the secret. An ask unanswered for seven +nothing else. mesh-config then emits `retired` on `config.events` (module, name, node, the ask), and the vault emits +`own-retired` (node, module, name, the ask's id, the caller it proved) beside its `rotated` and `own-replaced`, on the +module events stream (ADR 0363) under the vault's own subjects, kept as that stream keeps every module's events. An ask unanswered for seven days is asked once more, and then written as a finding, cleared by an answer, a cancel, or the value read again; a sleeping node holding a retirement open for seven days is a finding naming it. `config_retiring` lists what is retiring, since when, and its ask, and asks it again or cancels it. @@ -568,7 +569,7 @@ process, which is delivered in the same milestone. They move to bus state when e C24 below), of the checker's and of the vault's existing whole-module tests: the checker's ask of `renames` meeting a holder that does not serve it refuses `renamed-from` naming the verb, one meeting an open window refuses dropping `renamed-from` naming the nodes, and a check that cannot ask fails saying so; `retire` with the warrant removes the -secret and emits both events, without it, with a warrant for another name, or from a caller not on the list it is +secret and emits `retired` and `own-retired`, without it, with a warrant for another name, or from a caller not on the list it is refused; `config_rename_close` drops a node from a window and records the hand act; a window held seven days and a retirement held seven days each write their finding naming the node, cleared when it closes. -- 2.54.0 From cdfddff72efca00ca49866d1ce175a0f66656595 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:29:30 +0100 Subject: [PATCH 11/20] =?UTF-8?q?Design=2025=20=C2=A7=2012:=20owners'=20ev?= =?UTF-8?q?ents=20carry=20the=20value=20whole,=20a=20deleted=20module's=20?= =?UTF-8?q?entries=20become=20orphans,=20the=20first=20views=20get=20named?= =?UTF-8?q?=20tools=20and=20a=20live=20bound?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Answers the second review's V1-V9 by architectural domain: the assigner's views in the new form, newer by revision alone, what a reader does when a recreate takes revisions back, a hand re-say of manifest-accepted, and the judge question under the open point on issue 1966. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 49 ++++++++++++++++++------ 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 8d0c4a4b..688824ee 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -697,17 +697,23 @@ modules' names meet. - **The trigger is the judge's verdict** (ADR 0595 §4), heard as the indexer's event `manifest-accepted`, which carries the module's entries whole, so the bus module never reads the indexer's state and never polls. The indexer says it for every newly accepted manifest, with its entries empty when it asks for none, and the bus module reads an - empty one as "this module asks nothing now", which is what its orphans need. It goes, as every event of the + empty one as "this module asks nothing now", which is what its orphans need. A module whose manifest leaves the + trunk (its directory deleted) gets an empty `manifest-accepted` too, so its entries reach `orphans` and raise + `orphaned`; a manifest the judge set aside to wait gets none, so its entries stay asked. The indexer's tool + `indexer_say_accepted` says `manifest-accepted` again for one module by hand, from the verdict it holds, recorded as a + hand act, for a verdict the bus module is thought to have missed. It goes, as every event of the indexer, on the module events stream (ADR 0363) under the indexer's own subjects. A publish of it that fails is published again, and one still failing writes the indexer's finding naming the module, cleared once said (today the indexer only logs it). It hears the verdicts on a durable consumer, so one given while it was down is heard when it is back. **Its first view** at start is the indexer's tool `indexer_bus_state` (every accepted manifest's bus-state entries, by module and digest), asked once; the events keep it current after that, and a stream that keeps a verdict only for its age is never its source. A - verdict replayed from its consumer is applied only if newer than what the first view answered for that module (by - the manifest's digest and the indexer's revision), so a replay never takes an entry back. An `indexer_bus_state` + verdict replayed from its consumer is applied only if newer than what the first view answered for that module, by + the indexer's revision alone (a digest has no order), so a replay never takes an entry back. An `indexer_bus_state` that does not answer makes the bus module's health unhealthy, naming the verb, and is asked again on a bounded - backoff (from 10 seconds, doubling, at most every 5 minutes); nothing is created or orphaned meanwhile. It keeps + backoff (from 10 seconds, doubling, at most every 5 minutes); nothing is created or orphaned meanwhile. Beside the + backoff, the bus module's tool `nats_first_view_again` asks `indexer_bus_state` at once by hand, recorded as a hand + act. It keeps what it last heard per module in its own state `asked` (an old `state` section, its own kept data staying there, ADR 0595 §10), from which `list`, `check` and `orphans` answer; a key goes only on an empty `manifest-accepted` for the module, never because a manifest the judge set aside to wait is missing from the first view. Then it runs `check` over what it holds, creating what @@ -751,22 +757,42 @@ vault as an own secret: JetStream create and update under modules' names only (s no manifest form today (the assigner serves its seat verbs as tools); the form is new in mesh-sdk's manifest types and rides the milestone's one rebuild. Which part issues these subjects' grants first is not yet decided: the grants part composes every grant from the manifests, and is itself one of these readers, so its own first view - cannot wait on a grant it has not composed (an open point, tracked on issue 1966). The owner answers at once from what it holds, as soon as its process is + cannot wait on a grant it has not composed (an open point, tracked on issue 1966). Under the same point: which + judge takes in the indexer's and the assigner's own new manifests, which use that form. Every manifest of the + merge is judged by the indexer that runs, the old build, and mesh-sdk's strict decoding refuses a field it does + not know (research 082, its Findings for question 5), so the running judge would refuse the new form; whether the + form rides an earlier release the running judge already decodes, or the new indexer judges its own manifest, is + not decided by a record. The owner answers at once from what it holds, as soon as its process is connected and before it has its own first view of any other part, with the revision of its state it answered at, so the indexer and the assigner, each asking the other at start, never wait on each other (§4). The reader then follows the owner's declared events, each carrying the revision of the state it follows, and never asks the verb again on a timer; a gap in revisions makes it ask once more, said in its log (§5). The owners' events are new families of their own, apart from the indexer's `manifest-refused` and `manifest-accepted` and the assigner's `put`: the indexer's `indexer.views` and the assigner's `assigner.views`, one event per changed key with its - bucket, key and revision, kept seven days, declared in each owner's old `streams` section (its own kept data), - so a waiter on an existing event never wakes on them. A reader that starts while its owner is down raises a + bucket, key and revision and the key's value whole, or a delete mark for a key removed, so a reader keeps its + picture from the events alone and asks the verb only on a gap (ADR 0615 §5). Each family is kept seven days and + capped at 256 MiB, dropping its oldest when full (the template events); a reader that misses a dropped event sees + the gap in revisions and asks once more. The indexer declares `indexer.views` in its old `streams` section, its + own kept data staying there (ADR 0595 §10 names the indexer among the foundation). The assigner is not named + there and is touched in this milestone, so it asks for `assigner.views` in the new form, as bus state of the + template events in its `requires` (ADR 0595 §1, §11). A waiter on an existing event, `manifest-accepted` or the + assigner's `put`, never wakes on them. **When an owner's revisions go back**: should an owner's state be rebuilt + by the bus module's `recreate`, its revisions start again; a reader that hears a revision lower than the one it + holds takes it as a gap and asks the first view again, and keeps what that answer says, whatever revision it held + before. A `recreate` that could not keep the data answers empty at revision 0, so the grants part keeps its list + in force (below): a login revoked meanwhile stays until the owner's first change, and the grants part's + first-view condition stays raised meanwhile, naming the owner and the empty answer, cleared by an answer above + revision 0. A reader that starts while its owner is down raises a condition naming the owner and the verb past its bound and keeps asking until it answers, never reading the owner's state instead; once it had its first view, it keeps working on what it heard (§6). The bound is longer than an owner's start (two minutes), so a normal start, readers and owners coming up together, notifies nobody; the owner's answer clears the condition. An answer that is empty at revision 0 (an owner that holds nothing yet) is a first view of nothing, never a reason for the grants part to drop a login it holds: it keeps the list in force until an answer at a revision above 0. Each reader's tools say the owner, verb and revision its first view - holds (a read tool), and ask the first view again by hand (a re-read tool, recorded as a hand act). The + holds, when it asked and when it was answered (a read tool), and ask the first view again by hand (a re-read tool, + recorded as a hand act): the grants part's `grants_first_views` and `grants_first_view_again`, the tool runner's + `runtime_first_views` and `runtime_first_view_again`, the indexer's `indexer_first_views` and + `indexer_first_view_again`, and the assigner's `assigner_first_views` and `assigner_first_view_again`. The order of owner before reader is kept by the module graph's served-verb edge for the 19 readers (ADR 0614 §2), and, for the four, whose verb edges lie on cycles the graph leaves out (ADR 0614 §3), by the owner's answer at once and the reader's wait (ADR 0615 §2). @@ -939,9 +965,10 @@ issue 1966. | The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused | | The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at the raise mark and cleared only below the clear mark; `recreated` rewrites the binding and `dropped` removes it; at start the first view from `indexer_bus_state` creates what a verdict given while it was down asked; the older remove tools refuse a new-form entry, naming `drop`'s tool | | A module asking bus state waits for the bus module | the module graph's table test: a manifest with a ledger entry has a requirement edge to the module providing `bus-state`, one without has none (ADR 0614 §5) | -| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`cmd/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`cmd/mesh-delivery-assigner`) and the grants part's (`cmd/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an existing waiter on `manifest-accepted` does not wake on the new families; the read tool shows the revision each reader holds. Live, after the delivery: the indexer's count of `reads` entries on the indexer's and the assigner's state reads 0 | -| The bus module's first view | cases in the nats module's one integration test: a replayed verdict older than the first view is not applied; an `indexer_bus_state` that does not answer turns the health unhealthy naming it and is asked again on the backoff; a set-aside manifest missing from the first view keeps its `asked` key, an empty `manifest-accepted` removes it | -| `manifest-accepted` is never lost silently | a unit test of the indexer: a failed publish is published again, and one still failing writes the finding | +| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`cmd/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`cmd/mesh-delivery-assigner`) and the grants part's (`cmd/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an owner's event carries the changed value whole, and a reader applies a put and a delete from the events alone and never asks the verb; a revision lower than the one held makes the reader ask its first view again; an existing waiter on `manifest-accepted` or on the assigner's `put` does not wake on the new families; the read tool shows the revision each reader holds, and the re-read tool asks once and records the hand act. Live, after the delivery: the indexer's count of `reads` entries on the indexer's and the assigner's state reads 0 | +| A foundation part's first view comes within its bound | live, after the delivery: each reader's read tool (`grants_first_views`, `runtime_first_views`, `indexer_first_views`, `assigner_first_views`) shows, for the indexer's and the assigner's verbs, the time from its ask to the owner's answer under the two-minute bound, and no first-view condition raised by the delivery | +| The bus module's first view | cases in the nats module's one integration test: a replayed verdict older than the first view is not applied; an `indexer_bus_state` that does not answer turns the health unhealthy naming it and is asked again on the backoff; a set-aside manifest missing from the first view keeps its `asked` key, an empty `manifest-accepted` removes it; a deleted module's entry, after its empty `manifest-accepted`, is listed by `orphans` and raises `orphaned`, and a set-aside module's entry is neither; `nats_first_view_again` asks at once and records the hand act | +| `manifest-accepted` is never lost silently | unit tests of the indexer: a failed publish is published again, and one still failing writes the finding; `indexer_say_accepted` says it again for one module from the verdict held, and refuses a module with no accepted verdict | | Until the identity seat, the caller is the one the tool runner proved | a unit test of the bus module's process: a request on its tool subject naming another caller in its body acts for the proved caller; `drop` without the warrant is refused | | Drift and orphans, live | live, made only by a manifest change: a manifest that shrinks a setting raises `drifted`; the conditions clear as above. Nothing is filled on the live bus, and no orphan is made there | | Overdue and stopped are loud | live, carried by the change of the first module that moves in step 5, never by a merge made for the test alone: with the bus module's process stopped, an accepted manifest asking for an entry raises the overdue condition within five minutes, from the watchman, and a later `created` clears it; the bus module's health turns unhealthy when its pass is more than three minutes old | -- 2.54.0 From fa2d199b16c72d9ff68dbb76704b73a8cb8861c5 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:31:30 +0100 Subject: [PATCH 12/20] Design 51: answer the second review (event wakes for every floor source, versions within the state cap, the direct-write and old-form windows shaped as the old name's, release on the caller list, distribution's own family) Also writes the operator's answer of 2026-11-04 on anonymous reads (mesh network only, the public route refusing the forge's package paths), the expected count of three builds per module, and the revert's order by the graph. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../51-the-registries-keep-what-is-named.md | 139 ++++++++++++------ 1 file changed, 93 insertions(+), 46 deletions(-) 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 902fc58f..bc1d097d 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 @@ -20,6 +20,10 @@ decisions: - "ADR 0607" - "ADR 0610" - "ADR 0611" + - "ADR 0595" + - "ADR 0596" + - "ADR 0564" + - "ADR 0240" - "ADR 0311" - "ADR 0251" - "ADR 0189" @@ -64,17 +68,20 @@ whose declaration names an artifact has applied a build whose node-engine states (ADR 0614 §4), judged by that stated capability and never by a fingerprint. Its tool `registry_old_name` lists the nodes still holding it open, and drops a retired node from the window by hand, recorded as a hand act; a node holding it open for seven days is a finding naming the node, cleared when the window closes. Closing the old name emits -`old-name-closed` on both image seats' `.events`. A node away when the last `delivered` was said must close the -window when it returns and applies the new build, with no other delivery: the event that says such a node's return, -and the verb that answers each node's stated capabilities, are not yet named by any record or design (the stated -capability comes with the release that adds it, design 47); until they are, the window waits on the next `delivered`, -tracked on issue 1966. `cargo-package-registry` stays +`old-name-closed` (the old seat name, the time, the nodes that held it last, and the nodes dropped by hand) on +`distribution`'s own family `distribution.events`, kept seven days, never on a seat's `.events`: it is one +module's window, not the seats' contract. A node away when the last `delivered` was said closes the window when it +returns and applies the new build, with no other delivery: what wakes `distribution` then is the node-engine seat's +event for a change of a node's stated manifest-reads, and the delivery view's read of them, both decided in ADR 0618 +(proposed after lead-47's final release; the event's family is filled in once it is), never a watch of the +node-engine's have state (ADR 0596). `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`, `publish-part` (ADR 0611), `versions`, `remove` (a tagged version; the holder itself refuses `remove` of a version with no tag, but for the retention removal below), and -on `container-image-registry` `mirror` and `tags`; the two seats `distribution` holds add `hold` and its release, -with the events `held` and `released`, for the artifact sweep (ADR 0617 §2). The five seats this milestone changes, these four and `mesh-broker`, are defined whole, their contracts moved +on `container-image-registry` `mirror` and `tags`; the two seats `distribution` holds add `hold` and `release`, +with the events `held` and `released`, for the artifact sweep (ADR 0617 §2), both verbs in the seat definition and +in `distribution`'s caller list. The five seats this milestone changes, these four and `mesh-broker`, are defined whole, their contracts moved into `mesh-contracts` (ADR 0613 §1; design 26). 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; each seat's events are its own family, `.events`, kept seven days. A holder stores @@ -85,8 +92,9 @@ form ADR 0363 gives a restricted family: `publish` and `publish-part` the build proved caller that opened its transfer; `mirror` mesh-build-image; `remove` the builder, for a tagged version, and the holder of the artifact sweep, for an untagged bundle, archive or copied base with the reason retention (ADR 0610 §3); `hold` (on the two seats `distribution` holds: keep one untagged bundle, archive or copied base that the artifact -sweep's rules keep from removal by retention, and let it go again; never an image of its own) the module that runs -the artifact sweep alone (ADR 0617 §2). Every removal, whoever asked it, emits `removed` with its reason. +sweep's rules keep from removal by retention; never an image of its own) the module that runs the artifact sweep +alone (ADR 0617 §2); `release` (let a hold go) that module, and the operator's session only through +`registry_holds`, each release by hand recorded as a hand act. Every removal, whoever asked it, emits `removed` with its reason. 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. @@ -139,22 +147,34 @@ the verb. `distribution`'s own program serves the store's port in front of the registry container, which listens on its node's loopback only: reads (the OCI distribution protocol's GET and HEAD) stay open to every node's container runtime, and a write reaches the store only from `distribution`'s own process, after a `publish` it accepted. - **Until every build module publishes through the seats**, the build modules' builds still running push straight to - the store, as they do today: so `distribution`'s program also accepts a write from a build module's own account - until every node's have of every build module, read through `node_haves` at its start and on each `delivered`, runs - a build that publishes through the seats; then it refuses them, naming `publish`. **While the program is down**, - the store answers no read and no write: a node pulling an image fails and retries, and what already runs is not - touched; the holder's health check, which probes the read side every minute, names it, and the program serves - again at once when it restarts, holding nothing but its transfers in `incoming`. Each + **Until every build module publishes through the seats**, a window, the build modules' + builds still running push straight to the store, as they do today, with no credential: today's store takes an + unauthenticated OCI write from the mesh network, and no build module holds an image push credential. So + `distribution`'s program keeps accepting such a write from the mesh network, no wider than today, until every + node's have of every build module, read through `node_haves` at its start and on each `delivered`, runs a build + that publishes through the seats; then it refuses every write but its own process's, naming `publish`, says so in + its log and emits `direct-writes-closed` (the time, the build modules' builds last seen, the nodes dropped by hand) + on `distribution.events`. Since the window has no credential, closing it removes nothing from the store or the + vault: it is the program's refusal alone. Its tool `registry_direct_writes` lists whether the window is open and + the nodes whose have still runs an old build module, and drops a retired node from it by hand, recorded as a hand + act; a node holding it open for seven days is `distribution`'s finding naming the node, cleared when the window + closes. **While the program is down**, the store answers no read and no write: a node pulling an image fails and + retries, a build module's write fails and its build says so, and what already runs is not touched. The holder's + health check is declared in its manifest and run outside the program's process, as every module's (ADR 0240), so + it still answers while the program is down: it probes the read side every minute and names the program. The + program serves again at once when it restarts, holding nothing but its transfers in `incoming`. **On + `distribution`'s own delivery** its new images are pulled from the store before its program stops, since a stopped + program serves no pull, its own images included. Each holder keeps its write side as an own secret beside a read-only tool token (the forge module: `write:package`, its tool token `read:package`). - **The package seats' read sides need no credential** for the mesh's own packages: the forge serves the mesh owner's packages to anonymous reads, so the provisions `npm-package-registry` and `go-package-registry` carry no credential (form none), and mesh-build-node, which held a pair for both writing and installing, requires none (the operator's answer of 2026-11-03, that the mesh's packages may be read without a credential). The scope is the - mesh network only: the store's GET and HEAD and the forge's package reads answer anonymously there, and through no - public route. Whether the forge has a public route that would also serve its package reads anonymously is not - known to this design, to confirm with the operator before the merge. + mesh network only (the operator, 2026-11-04): the forge's package reads and the store's GET and HEAD answer + anonymously on the mesh network only, and the public route refuses the forge's package paths, as it already + refuses the forge's internal API. That refusal is a route-proxy route change for the forge module, delivered in this + milestone. - **The forge module serves both package seats' verbs from its Go bundle**, the one that already serves its package retention, renamed for both packages; its tools bundle in TypeScript is unchanged, so no holder needs generated TypeScript types in this milestone. @@ -186,8 +206,10 @@ the verb. version, and the triggers end. An image, bundle or archive is numbered per fingerprint, as above. - **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, cleared below it. Only the builder reaches it; others call `builder_versions` or hear its events. The build log + branch's end, retention, the sweep) and the event that removed it. History one, cap 64 MiB, the most the template + state allows (ADR 0595 §3). An estimate, not a count: about 30,000 keys a year (some 18,000 trunk versions, about + 50 numbered builds a day, and some 10,000 tagged ones) of about 0.5 KiB each, some 15 MiB a year, so the cap holds + about four years at that rate; past 80 % the builder 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 @@ -199,9 +221,13 @@ the verb. the builder: waiting would never end. So a trunk build of an image, bundle or archive whose floor is not yet known is claimed with no number, so none travels in its build request, built unnumbered and published by its digest (as a bundle or archive is from every done key, ADR 0605 §3; an image so by ADR 0617 §1). Such fingerprints are numbered - in merge order once the floor answers (ADR 0606 §1). That - publish is what wakes the floor: the builder hears the holder's `published` on its durable consumer of - `.events` and asks the floor again then, never on a timer; once all three answer it numbers the build and emits + in merge order once the floor answers (ADR 0606 §1). What wakes the floor is an event of the source that was + silent, never a timer: for the seat's `versions`, the holder's `published` on the builder's durable consumer of + `.events`, which that by-digest publish itself says; for `bridge_tags`, the forge bridge's next message on + its stream `forge`, which the builder already consumes; for `indexer_requirements`, the indexer's next + `manifest-accepted`; and for each, the condition keeper's event clearing the silent module's health condition, on + the seat `mesh-conditions`' family (ADR 0564 §3), so a source that recovers with nothing to say still wakes it. On + any of these the builder asks the silent sources again; once all three answer it numbers the build and emits `numbered`, which the build module consumes to call `publish` with the version and the held digest, which stores nothing. A package's trunk build waits for its floor, since a package is published only under its version. @@ -246,9 +272,10 @@ With the store's write side closed, the artifact sweep (seat `mesh-artifact-swee longer deletes or holds by writing to the store: it calls `remove` on the seat that holds the thing, with the reason retention, and `hold` to keep an untagged bundle, archive or copied base its rules keep from removal by retention, which then skips it (ADR 0617 §2), both granted to its module alone by `distribution`'s caller list; its rules of what is kept (*The artifact store* below) stand, and it reads both reference forms. -`distribution` keeps the holds in its own state `holds` (bus state, template state), emits `held` and `released` -on the seat's `.events`, and its tool `registry_holds` lists what is held, since when and for whom, and -releases one by hand. A `remove` or `hold` the holder refuses to the sweep writes the sweep's finding, naming the +`distribution` keeps the holds in its own state `holds` (bus state, template state), emits `held` (the thing's +name, kind and digest, the caller that asked, the time) and `released` (the same, and who let it go: the sweep, or +the operator's session by hand) on the seat's `.events`, and its tool `registry_holds` lists what is held, +since when and for whom, and releases one by hand through the seat's `release`, recorded as a hand act. A `remove` or `hold` the holder refuses to the sweep writes the sweep's finding, naming the thing and the reason, cleared by its next run with no refusal. 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 @@ -262,7 +289,9 @@ publish refused (the holder when it refuses, the builder when a build module's b 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 node holding the old seat name open for seven days (`distribution`, cleared when the window closes). A build narrates its publish: asked, waiting on a seat, stored, refused. +the builder), a node holding the old seat name open for seven days (`distribution`, cleared when the window closes), a node +holding open the window of direct writes for seven days (`distribution`, the same), a node holding open the window of +the node-engine's old-form done key for seven days (the builder, the same). A build narrates its publish: asked, waiting on a seat, stored, refused. ### The move, one milestone, ordered by the module graph @@ -278,24 +307,31 @@ its own rule; nothing stores the order (ADR 0612 §2, ADR 0614 §1). The edges t | mesh-build-go's fetch from the Go seat only after the forge module serves its read side | provision: `go-package-registry`, offered only by the forge module's new build (ADR 0603 §5) | the assigner and the provisioner | | No credential goes before its replacement works | the build modules' manifests drop the write credentials in the same change that makes them publish through the seats, so the vault mints none for the build that publishes (ADR 0612 §2) | the served-verb edge above | | A build carrying the new reference form, or a manifest using this milestone's words or seat names, reaches a node only through a node-engine that reads them | a reader's capability the node-engine states, the baseline when it states none (ADR 0614 §4) | the assigner, holding that node's want and naming the capability | -| The holders' `incoming` and `.events` exist before the holders start | bus state: each entry is a requirement on the provision `bus-state` of the bus module (ADR 0614 §5) | the assigner, as for any provision | +| The holders' `incoming`, `.events`, `distribution`'s `holds` and `distribution.events` exist before the holders start | bus state: each entry is a requirement on the provision `bus-state` of the bus module (ADR 0614 §5) | the assigner, as for any provision | | The shared contracts, the forge module that holds their registry and the build module that builds them | package and build module edges are triggers, never waits; the cycle they make holds nothing and is not refused (ADR 0614 §6); inside the group every member takes `mesh-contracts` and mesh-sdk by the group's head (the checker's group pin) | the indexer's graph | -**How many builds** (ADR 0603 §3). The three build modules change in this milestone, so every module they build is -built twice: at once with the recipe that runs, and again once a majority of build nodes runs the new build module. -The contracts move rides that first build and adds none (ADR 0613 §1). Whether the rebuild that a first numbered -publish of mesh-sdk or `mesh-contracts` triggers in their users (ADR 0614 §6) joins the second build or adds a third -is not decided by ADR 0603 or ADR 0614, so this design gives no count beyond two per module; it is put to the -operator. +**How many builds** (ADR 0603 §3). The three build modules change in this milestone, so a module they build can +be built up to three times in one window, and this milestone expects that count: once with the recipe that runs, +once more when the agreement of the build nodes moves to the new build module, and once more when a first numbered +publish of mesh-sdk or `mesh-contracts` triggers their users (ADR 0614 §6). The contracts move rides the first +build and adds none (ADR 0613 §1). ADR 0614 bounds nothing here; a bound, such as a settle before building a +fingerprint whose inputs still move, is a question for a record, put to the operator, and is not designed here. **The node-engine's own build keeps the old reference form** until every node-engine states the capability to read the new one: a node-engine away across the milestone must be able to read the build it wakes to, or it could never update itself (ADR 0603 §7, §9). The builder writes the node-engine's done key in the old form until the stated -capabilities, which the status view reads, show it on every node. +capabilities show it on every node; this is a window like the old seat name's. What wakes the builder is the node-engine seat's event +for a change of a node's stated manifest-reads, and the delivery view's read of them, both decided in ADR 0618 +(proposed after lead-47's final release; the event's family is filled in once it is), never a watch of the +node-engine's have state (ADR 0596). Its tool `builder_old_form` lists whether the window is open and the nodes whose +node-engine does not yet state the capability, and drops a retired node from it by hand, recorded as a hand act; a +node holding it open for seven days is the builder's finding naming the node, cleared when the window closes. **When the group is pushed.** Only once the release that adds the node-engine's stated capability (design 47) runs on every node, read from the status view's haves before the push, and once the progressive insights of ADR 0595 and -ADR 0597 this design is written to are accepted. +ADR 0597 this design is written to are accepted. A node away for weeks does not hold the push for ever: it is left +out of that read only by a recorded hand act naming the node and why, as `registry_old_name` drops one, said in the +group's pull request; the node then takes the new node-engine as any away node does when it returns. **The milestone's own check** runs the changed build modules (ADR 0603 §10) against the running holders, which serve no `publish`: a changed build module whose manifest invokes a verb no running build serves is one the check cannot @@ -313,10 +349,11 @@ guard go in the same change (ADR 0606 §6, read with ADR 0612 §1). **Rolled back by one revert** (ADR 0612 §4): reverting the milestone's merge brings back the build modules' manifests that require the write credentials, so the vault mints them again as own secrets on the revert's delivery, and the builds that publish through the seats go with it. The old seat name still retires on the node-engines' -reports (ADR 0610 §4). Two things of the revert are not decided by any record and are tracked on issue 1966: which -edge delivers `distribution`'s reverted build, which takes direct writes again, before the build modules' reverted -builds that push straight to the store; and whether a done key written in the new form is built again after the -revert or must stay readable by the reverted readers, which the old mesh-sdk refuses. **On a fresh mesh** the foundation's bootstrap (research 075) pushes the holders' own images +reports (ADR 0610 §4). The revert's order is the module graph's, as the move's is: `distribution`'s reverted build +is delivered before a build module's reverted build only where that build module's reverted manifest requires or +invokes something only `distribution`'s reverted build offers; the milestone's prepare step checks the build +modules' manifests for such an edge. The done keys written in the new form stay in the builder's state after a +revert and are not built again for their form; whether an older reader can read them is ADR 0618's. **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`. ### Acceptance tests @@ -354,15 +391,23 @@ answers (a holder down whole is its module's own health check's); `registry_inco refused sweep `remove` writes the sweep's finding and its next clean run clears it; the forge bridge says a branch-deleted kind for the forge module's `forge.delete`, a `forge.delete` from another account is refused by the bus, and an existing consumer of `forge` ignores the new kind; with the program stopped a read fails and the - holder's finding is written, and after its restart the read is answered and the finding cleared; a build module's - old build pushing straight to the store is accepted until every node runs a build module publishing through the - seats, and refused after. + holder's finding is written, a write fails too, and after its restart the read is answered and the finding + cleared; a build module's old build pushing straight to the store is accepted until every node runs a build + module publishing through the seats, and refused after, naming `publish`, with `direct-writes-closed` emitted and + its log line written; `registry_direct_writes` names the node still running an old build module, and drops it by + hand with a hand act; `release` from a caller not on the caller list is refused, and `registry_holds`' release + records its hand act; a waiter on a build's `published` does not wake on `held`, `released`, `old-name-closed` + or `direct-writes-closed`. - **Builder, added for the milestone.** With the seat's holder down at a first claim, a bundle's trunk build is built unnumbered and published by its digest, the holder's `published` makes the builder ask the floor again and number it, and the build module, hearing `numbered`, publishes the version with the held digest; a package's trunk build waits; a second trunk build of a package's revision held `published` keeps its version and publishes nothing, while one numbered but refused is published by its second build under the same version; with `bridge_tags` - silenced (a floor source other than the publish target) the floor finding is written and cleared once it answers; + silenced (a floor source other than the publish target) the floor finding is written and cleared once it answers, + and with `bridge_tags` still silent after the by-digest publish, the build stays unnumbered until the forge + bridge's health clears (no timer runs), and is then numbered and published by version; the node-engine's done key + stays in the old form while one node's node-engine lacks the capability and is written in the new form once every + node states it, `builder_old_form` naming that node meanwhile and dropping it by hand with a hand act; with the image's own seat silent at the claim of `distribution`'s own image, the image is still built, published by its digest, and numbered once its new build answers (ADR 0617 *How it is checked*). - **Checker.** A changed build module invoking a verb no running holder serves is not run, and the verdict names the @@ -374,7 +419,9 @@ answers (a holder down whole is its module's own health check's); `registry_inco `registry_old_name` names the nodes holding the old name open, an away node among them. After the delivery: mesh-sdk's first numbered version is `published` on the Go seat and an image is read back by its digest, both shown by `builder_versions`; a node runs a module from a reference in the new form; a node-engine away across the - milestone wakes and applies the new node-engine. Go's `direct`: its removal is named on the milestone's issue, and + milestone wakes and applies the new node-engine; `distribution`'s own delivery pulls its new images before its + program stops; a package read through the public route is refused, and the same read inside the mesh answers. + Go's `direct`: its removal is named on the milestone's issue, and the change that removes it adds to the Go build module's recipe test a case that refuses `direct` in the fetch. ## Reviewed by architectural domain -- 2.54.0 From 021fca5e6d66681502ed73340d8e87f7137987c7 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:32:24 +0100 Subject: [PATCH 13/20] Design 32: the rename window wakes on the node-engine seat's event of ADR 0618, as design 51's old seat name does Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/32-what-a-module-declares.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 f3baafb4..dcf03d58 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 @@ -516,9 +516,10 @@ value it holds under the old name to the new name in its own state, without the module reports applied a build of it that reads the new name. mesh-config hears the status view's `delivered` events and, at its start and on each, asks its `node_haves` for every node, away ones included, since `delivered` counts only the nodes present (design 47); until then mesh-config answers both names. A node away when the last - `delivered` was said should close its window when it returns, with no other delivery; the event that says its - return is not yet named, as for the old seat name (design 51), and until it is the window waits on the next - `delivered`, tracked on issue 1966. A node holding a window open + `delivered` was said closes its window when it returns, with no other delivery: what wakes mesh-config then is the + node-engine seat's event for a node's stated manifest-reads, said also on its return, decided in ADR 0618 as for + the old seat name (design 51; the event's family is filled in once it is proposed), never a watch of the + node-engine's have state (ADR 0596). A node holding a window open for seven days is mesh-config's finding naming the node, cleared when the window closes; `config_rename_close` drops a node from a window by hand, recorded as a hand act. - **The checker asks the seat, never the module.** The seat `mesh-config` gains the optional verb `renames` (the -- 2.54.0 From a9745b63cb85508143f7a2cbb01e3f43370931dd Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:42:35 +0100 Subject: [PATCH 14/20] Design 51: answer the third review (ADR 0618 only reserved, a module's images pulled before its containers are replaced, public package reads kept, levels on every new case) The windows close on the next delivered until ADR 0618 is accepted; distribution's first delivery needs the node-engine to pull every image first, since its registry serves every pull; the operator kept public reads, so ADR 0346 stands and the route refusal goes. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../51-the-registries-keep-what-is-named.md | 167 ++++++++++++------ 1 file changed, 110 insertions(+), 57 deletions(-) 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 bc1d097d..aa3ea32d 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 @@ -9,6 +9,7 @@ code: - mesh-delivery modules/mesh-delivery-builder (versions, tag asks), the forge bridge, the indexer, the checker, the assigner - mesh-sdk (the bus helper for sending in parts) - mesh-catalog modules/docker (image pruning) + - mesh-host internal/apply (every new image of a module pulled before any of its containers is replaced) updated: 2026-11-04 decisions: - "ADR 0605" @@ -22,6 +23,9 @@ decisions: - "ADR 0611" - "ADR 0595" - "ADR 0596" + - "ADR 0615" + - "ADR 0346" + - "ADR 0386" - "ADR 0564" - "ADR 0240" - "ADR 0311" @@ -69,12 +73,14 @@ whose declaration names an artifact has applied a build whose node-engine states nodes still holding it open, and drops a retired node from the window by hand, recorded as a hand act; a node holding it open for seven days is a finding naming the node, cleared when the window closes. Closing the old name emits `old-name-closed` (the old seat name, the time, the nodes that held it last, and the nodes dropped by hand) on -`distribution`'s own family `distribution.events`, kept seven days, never on a seat's `.events`: it is one -module's window, not the seats' contract. A node away when the last `delivered` was said closes the window when it -returns and applies the new build, with no other delivery: what wakes `distribution` then is the node-engine seat's -event for a change of a node's stated manifest-reads, and the delivery view's read of them, both decided in ADR 0618 -(proposed after lead-47's final release; the event's family is filled in once it is), never a watch of the -node-engine's have state (ADR 0596). `cargo-package-registry` stays +`distribution`'s own family `distribution.events`, kept seven days and capped at 16 MiB, dropping its oldest when +full (it carries a handful of events per window), never on a seat's `.events`: it is one module's window, not +the seats' contract. A node away when the last `delivered` was said should close the window when it returns and +applies the new build, with no other delivery: what wakes `distribution` then is to be decided in ADR 0618 +(reserved), the node-engine seat's event for a change of a node's stated manifest-reads and the delivery view's read +of them, never a watch of the node-engine's have state (ADR 0596). **Until ADR 0618 is accepted**, such a node closes +the window on the next `delivered`, whichever delivery says it: `distribution` asks `node_haves` then, finds the +returned node's new build, and closes. `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`, `publish-part` (ADR 0611), `versions`, `remove` (a @@ -94,7 +100,11 @@ holder of the artifact sweep, for an untagged bundle, archive or copied base wit `hold` (on the two seats `distribution` holds: keep one untagged bundle, archive or copied base that the artifact sweep's rules keep from removal by retention; never an image of its own) the module that runs the artifact sweep alone (ADR 0617 §2); `release` (let a hold go) that module, and the operator's session only through -`registry_holds`, each release by hand recorded as a hand act. Every removal, whoever asked it, emits `removed` with its reason. +`registry_holds`, each release by hand recorded as a hand act. When `registry_holds`, `distribution`'s own tool, +calls its own seat's `release`, the caller the holder proves is the one the tool runner proved and stamped on the +`registry_holds` call, the operator's session (ADR 0386 §7), carried on to `release` by the tool runner's stamp and +never by the tool's own words; `distribution`'s own account is not on `release`'s list, so the tool cannot release in +its own name. Every removal, whoever asked it, emits `removed` with its reason. 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. @@ -154,7 +164,9 @@ the verb. node's have of every build module, read through `node_haves` at its start and on each `delivered`, runs a build that publishes through the seats; then it refuses every write but its own process's, naming `publish`, says so in its log and emits `direct-writes-closed` (the time, the build modules' builds last seen, the nodes dropped by hand) - on `distribution.events`. Since the window has no credential, closing it removes nothing from the store or the + on `distribution.events`. While the window is open, a direct write to a tag the seat holds `published` is refused, + naming the tag and `publish`, so an old build module cannot overwrite a version published through the seat. Since the + window has no credential, closing it removes nothing from the store or the vault: it is the program's refusal alone. Its tool `registry_direct_writes` lists whether the window is open and the nodes whose have still runs an old build module, and drops a retired node from it by hand, recorded as a hand act; a node holding it open for seven days is `distribution`'s finding naming the node, cleared when the window @@ -162,19 +174,34 @@ the verb. retries, a build module's write fails and its build says so, and what already runs is not touched. The holder's health check is declared in its manifest and run outside the program's process, as every module's (ADR 0240), so it still answers while the program is down: it probes the read side every minute and names the program. The - program serves again at once when it restarts, holding nothing but its transfers in `incoming`. **On - `distribution`'s own delivery** its new images are pulled from the store before its program stops, since a stopped - program serves no pull, its own images included. Each - holder keeps its write side as an own secret beside a read-only tool token (the forge module: `write:package`, its + program serves again at once when it restarts, holding nothing but its transfers in `incoming`. Each holder keeps its write side as an own secret beside a read-only tool token (the forge module: `write:package`, its tool token `read:package`). +- **`distribution`'s first delivery** moves the registry container to its node's loopback and lets the program take + the store's port, and it is the one delivery whose own containers serve every pull. Read on the node-engine's main + (its apply of a container): the node-engine pulls a container's new image just before it replaces that container, + then sets the running one aside, stopped, starts the new one, and starts the old one again if the new one does not + run (issues 1742 and 1945). It does so one container at a time, so a module with two containers pulls its second + image only after its first container was replaced; for `distribution` that second pull would meet the stopped + registry. **So the node-engine pulls every new image of a module before it replaces any of its containers**, and a + pull that fails replaces nothing, the module keeping what it ran: a change in the node-engine, a subtask of this + milestone's node-engine work. The quick fix, a second port for this one delivery, is refused, since every module + with more than one container meets the same order. `distribution`'s declaration lists the registry container before + the program, so the port is free when the program starts. **What a pull meets in the swap**: from the registry + container's stop until the program listens on the port, the store answers nothing. On main, a failed pull fails that + module's apply, and the node agent tries a failed apply again only on the next change of the module's want or + composition, on the node-engine's verb `compose` naming the module (by hand), or when its liveness judge finds a + declared resource missing or down; only a failure of the bus itself is tried again on a clock (a bounded backoff, + at most four times before the have says failed). So another module's apply whose pull falls in that window stays + failed, its running containers kept, until one of those: its have says failed naming the image, and `compose` + naming the module is its recovery. - **The package seats' read sides need no credential** for the mesh's own packages: the forge serves the mesh owner's packages to anonymous reads, so the provisions `npm-package-registry` and `go-package-registry` carry no credential (form none), and mesh-build-node, which held a pair for both writing and installing, requires none (the - operator's answer of 2026-11-03, that the mesh's packages may be read without a credential). The scope is the - mesh network only (the operator, 2026-11-04): the forge's package reads and the store's GET and HEAD answer - anonymously on the mesh network only, and the public route refuses the forge's package paths, as it already - refuses the forge's internal API. That refusal is a route-proxy route change for the forge module, delivered in this - milestone. + operator's answer of 2026-11-03, that the mesh's packages may be read without a credential). Public reads stay (the + operator, 2026-11-04): the forge's package reads and the store's GET and HEAD answer anonymously on the mesh network + and, where a public route serves them today, through it, the forge's public route included. The forge keeps serving + the registry's public root (ADR 0346 §6, unchanged), so a lock file naming that root installs as today, and the lab + trusts it as before (ADR 0346 §7). - **The forge module serves both package seats' verbs from its Go bundle**, the one that already serves its package retention, renamed for both packages; its tools bundle in TypeScript is unchanged, so no holder needs generated TypeScript types in this milestone. @@ -227,7 +254,8 @@ the verb. its stream `forge`, which the builder already consumes; for `indexer_requirements`, the indexer's next `manifest-accepted`; and for each, the condition keeper's event clearing the silent module's health condition, on the seat `mesh-conditions`' family (ADR 0564 §3), so a source that recovers with nothing to say still wakes it. On - any of these the builder asks the silent sources again; once all three answer it numbers the build and emits + any of these the builder asks the silent sources again; beside them, its tool `builder_floor_again` asks the three + sources of one name again at once by hand, recorded as a hand act. Once all three answer it numbers the build and emits `numbered`, which the build module consumes to call `publish` with the version and the held digest, which stores nothing. A package's trunk build waits for its floor, since a package is published only under its version. @@ -237,7 +265,8 @@ the verb. 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). + their state), `builder_untag` (remove a branch's tagged versions), `builder_floor_again` (ask a name's floor + again by hand, a hand act) and `builder_old_form` (below). ### Tagged versions @@ -320,16 +349,23 @@ fingerprint whose inputs still move, is a question for a record, put to the oper **The node-engine's own build keeps the old reference form** until every node-engine states the capability to read the new one: a node-engine away across the milestone must be able to read the build it wakes to, or it could never update itself (ADR 0603 §7, §9). The builder writes the node-engine's done key in the old form until the stated -capabilities show it on every node; this is a window like the old seat name's. What wakes the builder is the node-engine seat's event -for a change of a node's stated manifest-reads, and the delivery view's read of them, both decided in ADR 0618 -(proposed after lead-47's final release; the event's family is filled in once it is), never a watch of the -node-engine's have state (ADR 0596). Its tool `builder_old_form` lists whether the window is open and the nodes whose -node-engine does not yet state the capability, and drops a retired node from it by hand, recorded as a hand act; a -node holding it open for seven days is the builder's finding naming the node, cleared when the window closes. +capabilities show it on every node; this is a window like the old seat name's. What should wake the builder for a +node that returns is to be decided in ADR 0618 (reserved): the node-engine seat's event for a change of a node's stated +manifest-reads and the delivery view's read of them, never a watch of the node-engine's have state (ADR 0596). **Until +ADR 0618 is accepted**, the builder hears the status view's `delivered` events, asks `node_haves` at its start and on +each, and a node that was away closes the window on the next `delivered`. Its tool `builder_old_form` lists whether the +window is open and the nodes whose node-engine does not yet state the capability, and drops a retired node from it by +hand, recorded as a hand act; a node holding it open for seven days is the builder's finding naming the node, cleared +when the window closes. Closing it says so in the build log and emits `old-form-closed` (the time, the nodes that held +it last, the nodes dropped by hand) on the builder's own family beside `numbered`; a waiter on a build's end does not +wake on it. **When the group is pushed.** Only once the release that adds the node-engine's stated capability (design 47) runs on every node, read from the status view's haves before the push, and once the progressive insights of ADR 0595 and -ADR 0597 this design is written to are accepted. A node away for weeks does not hold the push for ever: it is left +ADR 0597 this design is written to are accepted, and once the running judge accepts the process-subject form, the +manifest form by which the indexer and the assigner serve their verbs on their own process subjects (design 25 +§ 12.2): the judge that runs accepts that form before the indexer and the assigner use it, decided in a record of +this milestone extending ADR 0615 §3 (to be proposed). A node away for weeks does not hold the push for ever: it is left out of that read only by a recorded hand act naming the node and why, as `registry_old_name` drops one, said in the group's pull request; the node then takes the new node-engine as any away node does when it returns. @@ -353,7 +389,8 @@ reports (ADR 0610 §4). The revert's order is the module graph's, as the move's is delivered before a build module's reverted build only where that build module's reverted manifest requires or invokes something only `distribution`'s reverted build offers; the milestone's prepare step checks the build modules' manifests for such an edge. The done keys written in the new form stay in the builder's state after a -revert and are not built again for their form; whether an older reader can read them is ADR 0618's. **On a fresh mesh** the foundation's bootstrap (research 075) pushes the holders' own images +revert and are not built again for their form; whether an older reader can read them is to be decided in ADR 0618 +(reserved). **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`. ### Acceptance tests @@ -372,9 +409,10 @@ 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`. -- **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. +- **Builder, added in round 2** (integration, cases of the builder's whole-module test). 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 part sent by a module other than its transfer's opener is refused; a transfer cut in the middle resumes from the parts held, @@ -382,34 +420,48 @@ test). 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 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` + answers (a holder down whole is its module's own health check's); `registry_incoming` lists and drops. -- **Holder, added for the milestone.** A write to the store's port from any account but `distribution`'s process is - refused, and a read from a node's container runtime is answered; the artifact sweep's `remove` with the reason - retention removes an untagged bundle and emits `removed`, and the same call from another caller is refused; `hold` - keeps an archive from retention's removal, a `hold` of an image or from another caller is refused, emits `held` and is listed by `registry_holds`, which releases it with `released`; a - refused sweep `remove` writes the sweep's finding and its next clean run clears it; the forge bridge says a - branch-deleted kind for the forge module's `forge.delete`, a `forge.delete` from another account is refused by the - bus, and an existing consumer of `forge` ignores the new kind; with the program stopped a read fails and the +- **Holder, added for the milestone, unit** (table cases of `distribution`'s caller list and store front): a write to + the store's port from any account but `distribution`'s process is refused, and a read is answered; the artifact + sweep's `remove` with the reason retention is accepted and the same call from another caller is refused; a `hold` + of an image or from another caller is refused; `release` from a caller not on the caller list is refused, a + `release` carrying the operator's session as the proved caller is accepted and `distribution`'s own account as + the caller is refused; while the direct-write window is open, a direct write to a tag the seat holds `published` is + refused naming the tag and `publish`. +- **Holder, added for the milestone, integration** (cases of `distribution`'s and the forge bridge's whole-module + tests): the sweep's `remove` removes an untagged bundle and emits `removed`; `hold` keeps an archive from + retention's removal, emits `held` and is listed by `registry_holds`, which releases it with `released` and records + its hand act; a refused sweep `remove` writes the sweep's finding and its next clean run clears it; the forge bridge + says a branch-deleted kind for the forge module's `forge.delete`, a `forge.delete` from another account is refused + by the bus, and an existing consumer of `forge` ignores the new kind; with the program stopped a read fails and the holder's finding is written, a write fails too, and after its restart the read is answered and the finding cleared; a build module's old build pushing straight to the store is accepted until every node runs a build module publishing through the seats, and refused after, naming `publish`, with `direct-writes-closed` emitted and its log line written; `registry_direct_writes` names the node still running an old build module, and drops it by - hand with a hand act; `release` from a caller not on the caller list is refused, and `registry_holds`' release - records its hand act; a waiter on a build's `published` does not wake on `held`, `released`, `old-name-closed` - or `direct-writes-closed`. -- **Builder, added for the milestone.** With the seat's holder down at a first claim, a bundle's trunk build is built - unnumbered and published by its digest, the holder's `published` makes the builder ask the floor again and number - it, and the build module, hearing `numbered`, publishes the version with the held digest; a package's trunk build - waits; a second trunk build of a package's revision held `published` keeps its version and publishes nothing, - while one numbered but refused is published by its second build under the same version; with `bridge_tags` - silenced (a floor source other than the publish target) the floor finding is written and cleared once it answers, - and with `bridge_tags` still silent after the by-digest publish, the build stays unnumbered until the forge - bridge's health clears (no timer runs), and is then numbered and published by version; the node-engine's done key - stays in the old form while one node's node-engine lacks the capability and is written in the new form once every - node states it, `builder_old_form` naming that node meanwhile and dropping it by hand with a hand act; - with the image's own seat silent at the claim of `distribution`'s own image, the image is still built, published by - its digest, and numbered once its new build answers (ADR 0617 *How it is checked*). + hand with a hand act; a node that was away, returning with the new build, closes the old seat name's window on + the next `delivered` (until ADR 0618 is accepted); a waiter on a build's `published` does not wake on `held`, + `released`, `old-name-closed` or `direct-writes-closed`. +- **Builder, added for the milestone, integration** (cases of the builder's whole-module test): with the seat's + holder down at a first claim, a bundle's trunk build is built unnumbered and published by its digest, the holder's + `published` makes the builder ask the floor again and number it, and the build module, hearing `numbered`, + publishes the version with the held digest; a package's trunk build waits; with `bridge_tags` silenced (a floor + source other than the publish target) the floor finding is written and cleared once it answers, and with + `bridge_tags` still silent after the by-digest publish, the build stays unnumbered until the forge bridge's health + clears (no timer runs), and is then numbered and published by version; `builder_floor_again` asks the three + sources at once and records the hand act; the node-engine's done key stays in the old form while one node's + node-engine lacks the capability and is written in the new form once every node states it, `builder_old_form` + naming that node meanwhile and dropping it by hand with a hand act; a node that was away, returning with the + capability, closes that window on the next `delivered` (until ADR 0618 is accepted), with `old-form-closed` emitted + and its build-log line written, and a waiter on a build's end does not wake on it; with the image's own seat silent + at the claim of `distribution`'s own image, the image is still built, published by its digest, and numbered once + its new build answers (ADR 0617 *How it is checked*). +- **Builder, added for the milestone, unit** (cases of the builder's numbering): a second trunk build of a package's + revision held `published` keeps its version and publishes nothing, while one numbered but refused is published by + its second build under the same version. +- **Node-engine, unit** (a case of its apply test, with the runner it already fakes): a module of two containers + whose second new image cannot be pulled replaces neither container, says the image, and keeps both running; with + both images pullable, both are pulled before the first container is set aside. - **Checker.** A changed build module invoking a verb no running holder serves is not run, and the verdict names the modules composed from their manifests instead, and the verb. - **Vault.** Reverting the milestone's merge mints the build modules' credentials again; the bootstrap's one-off @@ -419,10 +471,11 @@ answers (a holder down whole is its module's own health check's); `registry_inco `registry_old_name` names the nodes holding the old name open, an away node among them. After the delivery: mesh-sdk's first numbered version is `published` on the Go seat and an image is read back by its digest, both shown by `builder_versions`; a node runs a module from a reference in the new form; a node-engine away across the - milestone wakes and applies the new node-engine; `distribution`'s own delivery pulls its new images before its - program stops; a package read through the public route is refused, and the same read inside the mesh answers. - Go's `direct`: its removal is named on the milestone's issue, and - the change that removes it adds to the Go build module's recipe test a case that refuses `direct` in the fetch. + milestone wakes and applies the new node-engine; the node-engine's apply journal for `distribution` on each node + shows every new image pulled before its first container was replaced, and no node's pull of any module failed + during the delivery (each node's haves, none failed naming an image); mesh-build-node installs from a lock file + naming the registry's public root with no credential. Go's `direct`: its removal is named on the milestone's issue, + and the change that removes it adds to the Go build module's recipe test a case that refuses `direct` in the fetch. ## Reviewed by architectural domain -- 2.54.0 From 546a18a20ef92024986d07ca6699ca4e5f2c345f Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:46:25 +0100 Subject: [PATCH 15/20] =?UTF-8?q?Design=2025:=20answer=20the=20third=20rev?= =?UTF-8?q?iew=20(views=20families=20on=20the=20old=20streams=20sections?= =?UTF-8?q?=20under=20a=20record=20extending=20ADR=200595=20=C2=A710,=20th?= =?UTF-8?q?e=20tool=20runner's=20first-view=20tools=20on=20the=20mesh=20MC?= =?UTF-8?q?P=20server,=20the=20running=20judge=20takes=20the=20process-sub?= =?UTF-8?q?ject=20form=20in)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The assigner must start on a fresh mesh before the bus module can answer, so its new stream stays on its old section; a new indexer can never judge its own manifest, so the running judge must accept the form first. The glossary stops counting the mesh MCP server's tools. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 00-META/glossary.md | 2 +- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 62 ++++++++++++++++-------- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/00-META/glossary.md b/00-META/glossary.md index 4d33e14a..73d59d19 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -858,7 +858,7 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03 **Uses:** operator, person, node, module, seat, tool (Module), proof and identity (Identity and access), release (Delivery). - **mesh MCP server** — the endpoint on a node's loopback through which every agent and person on that - node reaches the mesh: the MCP server named `mesh`, with its five tools `mesh_overview`, + node reaches the mesh: the MCP server named `mesh`, with its tools, among them `mesh_overview`, `mesh_machine`, `mesh_search`, `mesh_describe` and `mesh_call`. It is the tool runner's loopback mode, not a module of its own. "Console" suggested a terminal or a shell, and the module `mesh-console` it once named no longer exists; "the tool bridge" and "the brain" named the predecessor's program. diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 688824ee..c9a7a3a7 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -757,12 +757,12 @@ vault as an own secret: JetStream create and update under modules' names only (s no manifest form today (the assigner serves its seat verbs as tools); the form is new in mesh-sdk's manifest types and rides the milestone's one rebuild. Which part issues these subjects' grants first is not yet decided: the grants part composes every grant from the manifests, and is itself one of these readers, so its own first view - cannot wait on a grant it has not composed (an open point, tracked on issue 1966). Under the same point: which - judge takes in the indexer's and the assigner's own new manifests, which use that form. Every manifest of the - merge is judged by the indexer that runs, the old build, and mesh-sdk's strict decoding refuses a field it does - not know (research 082, its Findings for question 5), so the running judge would refuse the new form; whether the - form rides an earlier release the running judge already decodes, or the new indexer judges its own manifest, is - not decided by a record. The owner answers at once from what it holds, as soon as its process is + cannot wait on a grant it has not composed (an open point, tracked on issue 1966). **The judge that runs takes the + form in.** Every manifest of the merge is judged by the indexer that runs, the old build, before any new build + runs, so a new indexer can never judge its own manifest; and mesh-sdk's strict decoding refuses a field it does not + know (research 082, its Findings for question 5). So the running judge accepts the process-subject form before the + indexer and the assigner use it, decided in a record of this milestone extending ADR 0615 §3 (to be proposed), and + design 51's *When the group is pushed* holds the push until the judge that runs accepts it. The owner answers at once from what it holds, as soon as its process is connected and before it has its own first view of any other part, with the revision of its state it answered at, so the indexer and the assigner, each asking the other at start, never wait on each other (§4). The reader then follows the owner's declared events, each carrying the revision of the state it follows, and never asks the verb @@ -770,19 +770,32 @@ vault as an own secret: JetStream create and update under modules' names only (s families of their own, apart from the indexer's `manifest-refused` and `manifest-accepted` and the assigner's `put`: the indexer's `indexer.views` and the assigner's `assigner.views`, one event per changed key with its bucket, key and revision and the key's value whole, or a delete mark for a key removed, so a reader keeps its - picture from the events alone and asks the verb only on a gap (ADR 0615 §5). Each family is kept seven days and - capped at 256 MiB, dropping its oldest when full (the template events); a reader that misses a dropped event sees - the gap in revisions and asks once more. The indexer declares `indexer.views` in its old `streams` section, its - own kept data staying there (ADR 0595 §10 names the indexer among the foundation). The assigner is not named - there and is touched in this milestone, so it asks for `assigner.views` in the new form, as bus state of the - template events in its `requires` (ADR 0595 §1, §11). A waiter on an existing event, `manifest-accepted` or the + picture from the events alone and asks the verb only on a gap (ADR 0615 §5). **Both families are declared in + their owners' old `streams` sections**, each an entry with `max-age-days` 7 and `max-mib` 256: the old section keeps + a stream by limits, which drops its oldest when full (only a work queue refuses the new instead), so a reader that + misses a dropped event sees the gap in revisions and asks once more. The indexer's own kept data stays on the old + sections (ADR 0595 §10 names it among the foundation); the assigner's goes beside its existing `streams` entry + `placement`, under a record of this milestone extending ADR 0595 §10's foundation (to be proposed) to the parts + the bus module's own composition, placement, build or delivery needs before it can answer: the assigner, the tool + runner, the builder, the checker, mesh-config and the vault. **The size, an estimate**: read on the bus on + 2026-11-04, the buckets the readers read hold about 3 MiB of current values at the indexer (its manifests about + 5 KiB a value) and about 1.5 MiB at the assigner (its wants about 0.25 KiB a value). The milestone's one mesh-sdk + rebuild rewrites every module's keys, up to three times in its window (design 51 *How many builds*), so a week of + it is some 20 MiB on `indexer.views` and some 10 MiB on `assigner.views` with ordinary merges added, each well + under its 256 MiB. Each event carries one value whole, the largest today a manifest of tens of KiB, under the bus's + largest message, 1 MiB (the bus server's `max_payload`); an owner whose value with its event would pass it emits + the key's revision alone, as a gap, so the reader asks the first view once, and the owner writes its finding + naming the key. + A waiter on an existing event, `manifest-accepted` or the assigner's `put`, never wakes on them. **When an owner's revisions go back**: should an owner's state be rebuilt by the bus module's `recreate`, its revisions start again; a reader that hears a revision lower than the one it holds takes it as a gap and asks the first view again, and keeps what that answer says, whatever revision it held before. A `recreate` that could not keep the data answers empty at revision 0, so the grants part keeps its list in force (below): a login revoked meanwhile stays until the owner's first change, and the grants part's - first-view condition stays raised meanwhile, naming the owner and the empty answer, cleared by an answer above - revision 0. A reader that starts while its owner is down raises a + first-view condition stays raised meanwhile, naming the owner, the empty answer and the operator's step: let the + owner write its state again from its sources (the indexer by taking each module in again, `indexer_take_in`; the + assigner by its next composition of the wants), then ask the grants part's first view again, + `grants_first_view_again`. It is cleared by an answer above revision 0. A reader that starts while its owner is down raises a condition naming the owner and the verb past its bound and keeps asking until it answers, never reading the owner's state instead; once it had its first view, it keeps working on what it heard (§6). The bound is longer than an owner's start (two minutes), so a normal start, readers and owners coming up together, notifies nobody; @@ -791,9 +804,13 @@ vault as an own secret: JetStream create and update under modules' names only (s force until an answer at a revision above 0. Each reader's tools say the owner, verb and revision its first view holds, when it asked and when it was answered (a read tool), and ask the first view again by hand (a re-read tool, recorded as a hand act): the grants part's `grants_first_views` and `grants_first_view_again`, the tool runner's - `runtime_first_views` and `runtime_first_view_again`, the indexer's `indexer_first_views` and - `indexer_first_view_again`, and the assigner's `assigner_first_views` and `assigner_first_view_again`. The - order of owner before reader is kept by the module graph's served-verb edge for the 19 readers (ADR 0614 §2), and, + `mesh_first_views` and `mesh_first_view_again`, the indexer's `indexer_first_views` and + `indexer_first_view_again`, and the assigner's `assigner_first_views` and `assigner_first_view_again`. The tool + runner's two are tools of the mesh MCP server, beside `mesh_runtimes`, since the tool runner serves its own state + there: the mesh MCP server gains them, and the operator sees them among its tools. **On a fresh mesh** the + assigner starts before any new-form bus state exists and needs no binding of the bus module first: its own state + and `assigner.views` are on its old sections, which the grants part creates, so it answers the indexer, the grants + part and the tool runner their first views while the bus module still waits on it. The order of owner before reader is kept by the module graph's served-verb edge for the 19 readers (ADR 0614 §2), and, for the four, whose verb edges lie on cycles the graph leaves out (ADR 0614 §3), by the owner's answer at once and the reader's wait (ADR 0615 §2). - **Only the owner reaches it** (ADR 0596 §2). The membership grants the owner's account its own entries and no @@ -944,7 +961,10 @@ and the new form are never both used for one local name. During the move the gra sections' entries, so each entry has one creator. The indexer counts the modules on each old section, a tool reads the count, and the judge refuses a section once its count is 0. The foundation's own kept data (the bus module's, the grants part's and the indexer's) stays on the old sections for good and is not counted: those modules cannot -wait for a process that needs them first. +wait for a process that needs them first. A record of this milestone extending ADR 0595 §10's foundation (to be +proposed) adds the parts the bus module's own composition, placement, build or delivery needs before it can answer: +the assigner, the tool runner, the builder, the checker, mesh-config and the vault; their new state and streams of +this milestone go on their old sections too (§ 12.2; design 51 *The move*; design 32). **Readers move after their owners** (ADR 0596 §3). Today 195 `reads` entries in 37 modules read another module's bus state (counted 2026-11-03 02:00). **A correction of fact**: the indexer's and the assigner's state, counted again @@ -965,8 +985,10 @@ issue 1966. | The bus module creates, and the binding follows | live, by tool call: a module asking for a state, a log, objects and a ledger in `requires` receives each binding in its membership, written after `created`, never before; `show` gives each entry's template, settings and fill; a module calling `create` is refused | | The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at the raise mark and cleared only below the clear mark; `recreated` rewrites the binding and `dropped` removes it; at start the first view from `indexer_bus_state` creates what a verdict given while it was down asked; the older remove tools refuse a new-form entry, naming `drop`'s tool | | A module asking bus state waits for the bus module | the module graph's table test: a manifest with a ledger entry has a requirement edge to the module providing `bus-state`, one without has none (ADR 0614 §5) | -| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`cmd/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`cmd/mesh-delivery-assigner`) and the grants part's (`cmd/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an owner's event carries the changed value whole, and a reader applies a put and a delete from the events alone and never asks the verb; a revision lower than the one held makes the reader ask its first view again; an existing waiter on `manifest-accepted` or on the assigner's `put` does not wake on the new families; the read tool shows the revision each reader holds, and the re-read tool asks once and records the hand act. Live, after the delivery: the indexer's count of `reads` entries on the indexer's and the assigner's state reads 0 | -| A foundation part's first view comes within its bound | live, after the delivery: each reader's read tool (`grants_first_views`, `runtime_first_views`, `indexer_first_views`, `assigner_first_views`) shows, for the indexer's and the assigner's verbs, the time from its ask to the owner's answer under the two-minute bound, and no first-view condition raised by the delivery | +| A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`cmd/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`cmd/mesh-delivery-assigner`) and the grants part's (`cmd/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an owner's event carries the changed value whole, and a reader applies a put and a delete from the events alone and never asks the verb; a revision lower than the one held makes the reader ask its first view again; an existing waiter on `manifest-accepted` or on the assigner's `put` does not wake on the new families; the read tool shows the revision each reader holds, and the re-read tool asks once and records the hand act; in the +assigner's whole-module test, the assigner starts before any new-form bus state exists, with no binding of the bus +module, and the indexer, the grants part and the tool runner each get their first view of it. Live, after the delivery: the indexer's count of `reads` entries on the indexer's and the assigner's state reads 0 | +| A foundation part's first view comes within its bound | live, after the delivery: each reader's read tool (`grants_first_views`, the mesh MCP server's `mesh_first_views`, `indexer_first_views`, `assigner_first_views`) shows, for the indexer's and the assigner's verbs, the time from its ask to the owner's answer under the two-minute bound, and no first-view condition raised by the delivery | | The bus module's first view | cases in the nats module's one integration test: a replayed verdict older than the first view is not applied; an `indexer_bus_state` that does not answer turns the health unhealthy naming it and is asked again on the backoff; a set-aside manifest missing from the first view keeps its `asked` key, an empty `manifest-accepted` removes it; a deleted module's entry, after its empty `manifest-accepted`, is listed by `orphans` and raises `orphaned`, and a set-aside module's entry is neither; `nats_first_view_again` asks at once and records the hand act | | `manifest-accepted` is never lost silently | unit tests of the indexer: a failed publish is published again, and one still failing writes the finding; `indexer_say_accepted` says it again for one module from the verdict held, and refuses a module with no accepted verdict | | Until the identity seat, the caller is the one the tool runner proved | a unit test of the bus module's process: a request on its tool subject naming another caller in its body acts for the proved caller; `drop` without the warrant is refused | -- 2.54.0 From a81334b1afaff7d69c59f25ea6f4b7322deeb76b Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:46:40 +0100 Subject: [PATCH 16/20] Design 32: the rename window closes on the next delivered while ADR 0618 is only reserved, and no waiter on the vault's rotation wakes on own-retired Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/32-what-a-module-declares.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 dcf03d58..c55a817e 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 @@ -516,10 +516,10 @@ value it holds under the old name to the new name in its own state, without the module reports applied a build of it that reads the new name. mesh-config hears the status view's `delivered` events and, at its start and on each, asks its `node_haves` for every node, away ones included, since `delivered` counts only the nodes present (design 47); until then mesh-config answers both names. A node away when the last - `delivered` was said closes its window when it returns, with no other delivery: what wakes mesh-config then is the - node-engine seat's event for a node's stated manifest-reads, said also on its return, decided in ADR 0618 as for - the old seat name (design 51; the event's family is filled in once it is proposed), never a watch of the - node-engine's have state (ADR 0596). A node holding a window open + `delivered` was said should close its window when it returns, with no other delivery: what wakes mesh-config then + is to be decided in ADR 0618 (reserved), the node-engine seat's event for a node's stated manifest-reads, said also + on its return, as for the old seat name (design 51), never a watch of the node-engine's have state (ADR 0596). + **Until ADR 0618 is accepted**, such a node closes the window on the next `delivered`, whichever delivery says it. A node holding a window open for seven days is mesh-config's finding naming the node, cleared when the window closes; `config_rename_close` drops a node from a window by hand, recorded as a hand act. - **The checker asks the seat, never the module.** The seat `mesh-config` gains the optional verb `renames` (the @@ -571,7 +571,9 @@ C24 below), of the checker's and of the vault's existing whole-module tests: the holder that does not serve it refuses `renamed-from` naming the verb, one meeting an open window refuses dropping `renamed-from` naming the nodes, and a check that cannot ask fails saying so; `retire` with the warrant removes the secret and emits `retired` and `own-retired`, without it, with a warrant for another name, or from a caller not on the list it is -refused; `config_rename_close` drops a node from a window and records the hand act; a window held seven days and a +refused; a waiter on the vault's `rotated` or `own-replaced` does not wake on `own-retired`; a node that was away, +returning with a build that reads the new name, closes the rename window on the next `delivered` (until ADR 0618 is +accepted); `config_rename_close` drops a node from a window and records the hand act; a window held seven days and a retirement held seven days each write their finding naming the node, cleared when it closes. ### The mesh delivery cases this design carries -- 2.54.0 From c8e6eb5edf1567849afe7d8078250cf0beda621b Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:47:45 +0100 Subject: [PATCH 17/20] Designs 26, 51, 25: define whole the four seats the milestone adds verbs or events to The operator chose to move them now (2026-11-04), so a seat's new verb never lands at the seat list's grain. Each is listed with what its holder's manifest on the trunk serves today; the builder and the indexer hold no seat, so their events stay module events. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 2 +- 03-DESIGN/01-to-be/26-the-seats.md | 34 +++++++++++++++---- .../51-the-registries-keep-what-is-named.md | 5 +-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index c9a7a3a7..b6820d31 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -820,7 +820,7 @@ vault as an own secret: JetStream create and update under modules' names only (s ### 12.3 The seat's verbs, events and fill -The seat `mesh-broker`, until now a seat with no verbs, gains these (ADR 0595 §5, §6). It is one of the five seats +The seat `mesh-broker`, until now a seat with no verbs, gains these (ADR 0595 §5, §6). It is one of the nine seats defined whole in the milestone of ADR 0612, its contract in `mesh-contracts` (ADR 0613 §1; design 26 *A seat's definition*), which settles where its definition lives (ADR 0595 §12). An entry's arguments are the module, its local name, its storage type, its template, its options and its configuration; what is held answers the settings held and diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 10e165fa..ee3671d8 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -175,18 +175,40 @@ merges. ## A seat's definition (from 2026-11-03) **A seat is defined once, in one language-neutral file, and every part learns it from the bus** (ADR 0608, from -research 081). The registry seats (design 51) and `mesh-broker` (design 25 § 12.3) are the first defined whole this -way, in the milestone of ADR 0612 (ADR 0613 §1). +research 081). The registry seats (design 51), `mesh-broker` (design 25 § 12.3) and the four seats the milestone +adds verbs or events to are the first defined whole this way, in the milestone of ADR 0612 (ADR 0613 §1, and a +record of this milestone changing ADR 0613 §1-§3, to be proposed). - **The file** is a schema with conformance fixtures: the seat's verbs and their messages, its events and their bodies, its state and the forms of its provisions. It lives in the contracts module of its area. Its Go and TypeScript types are generated from it and committed beside it; nobody writes them by hand. The format is JSON, its messages and bodies JSON Schema, as the seat set's verb inputs already are, with the format's own schema checked in; the fixtures are data, never functions. -- **What is defined whole now** (ADR 0613). The five seats this milestone changes are defined whole and their contracts - move into `mesh-contracts`: `npm-package-registry`, `go-package-registry`, `container-image-registry`, +- **What is defined whole now** (ADR 0613; a record of this milestone changing ADR 0613 §1-§3, to be proposed, on the + operator's answer of 2026-11-04 to move them all). Every seat this milestone changes is defined whole and its + contract moves into `mesh-contracts`: `npm-package-registry`, `go-package-registry`, `container-image-registry`, `oci-artifact-registry` (one common part of their shared verbs, written once, so the four cannot drift) and - `mesh-broker`. Every other seat, the foundation's included, has its definition on the bus in the same milestone, all + `mesh-broker`; and the four seats it adds a verb or an event to, each with every verb and event its holder's + manifest on the trunk declares today, plus the new ones: + - `mesh-delivery-checker` (held by the checker): the verbs `try`, `try-read` and `try-end`; the events `verdict` + and `tag-wanted` (design 51 *Tagged versions*, its family `checker.asks`), both new. Its manifest declares no + event today; `preview.written` on its own stream is declared in the same change (above). + - `mesh-config` (held by mesh-config): the verbs `config` and, new, `renames`; the events `config-changed` and, + new, `renamed`, `retiring` and `retired` (design 32). + - `mesh-vault` (held by the vault): the verbs `get`, `secret-ask`, `issued`, `rotate`, `move-in`, `node-key`, + `own`, `issue-account`, `sign`, `enrolment-token`, `node-account`, `rotate-node`, `node-account-confirm`, + `abandon` and, new, `retire`; the events `provisioned`, `rotated`, `secret-changed`, `account-hashed`, + `own-replaced`, `account-issued`, `signed`, `rotation-begun`, `node-account-ready`, `rotation-done` and, new, + `own-retired` (design 32). The seat list mesh-sdk compiles today also names `accept` and `move-asks`, which + the vault does not serve; the definition carries what the holder serves, and the two are an open point. + - `operator-channel` (held by the router, the messenger module): the verbs `open`, `history`, `notify`, `tell` + and `asks`; the asks it accepts, `ask`, `cancel` and `condition`; the events `decided` and, new, `offered` and + `closed` (design 55 § 11); the holder's own `refused` beside them. mesh-sdk's seat list holds no entry for it + today, so its definition is written from the holder's manifest. + + The builder and the indexer hold no seat: the builder's `numbered` and `old-form-closed` and the indexer's + `manifest-accepted` and `indexer.views` are events of those modules, declared in their own manifests, which + ADR 0613 does not govern, and no seat definition carries them. Every other seat, the foundation's included, has its definition on the bus in the same milestone, all at once (ADR 0608 §2), holding the seat list's grain: its name, whether it is held once or on every node, its verbs with their inputs and its provision, generated from the seat list mesh-sdk compiles today, so the move changes nothing a reader sees (ADR 0613 §2). Its messages, events and state layouts are added to @@ -446,6 +468,6 @@ and 32 by the four reviewers by architectural domain; the findings and what answ 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). -The milestone's amendment (ADR 0612, ADR 0613, ADR 0614: five seats defined whole, the rest at the seat list's grain, +The milestone's amendment (ADR 0612, ADR 0613, ADR 0614: nine seats defined whole, the rest at the seat list's grain, the contracts marker, out of step learned from the checker's `verdict` and the builder's build end, the seat set's one source) awaits its own review by architectural domain, which rewrites this section. 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 aa3ea32d..d37e1b4f 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 @@ -87,8 +87,9 @@ named, without a holder, until a build module publishes cargo packages. tagged version; the holder itself refuses `remove` of a version with no tag, but for the retention removal below), and on `container-image-registry` `mirror` and `tags`; the two seats `distribution` holds add `hold` and `release`, with the events `held` and `released`, for the artifact sweep (ADR 0617 §2), both verbs in the seat definition and -in `distribution`'s caller list. The five seats this milestone changes, these four and `mesh-broker`, are defined whole, their contracts moved -into `mesh-contracts` (ADR 0613 §1; design 26). Events: `published`, `refused` (added to ADR 0605 §2's two, additively) and `removed`, each +in `distribution`'s caller list. The seats this milestone changes, these four, `mesh-broker` and the four it adds a verb or an event to, are defined +whole, their contracts moved into `mesh-contracts` (ADR 0613 §1, and a record of this milestone changing ADR 0613 +§1-§3, to be proposed; design 26). 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; each seat's events are its own family, `.events`, kept seven days. A holder stores under the version it is given and counts nothing. -- 2.54.0 From f91f4879fcdf4f3f85ecd4947762fd78e9cb7255 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:49:42 +0100 Subject: [PATCH 18/20] Design 25: cite ADR 0619, accepted, for the foundation that keeps the old sections Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index b6820d31..6159a4aa 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -13,6 +13,7 @@ code: updated: 2026-11-04 decisions: - "ADR 0595" + - "ADR 0619" - "ADR 0615" - "ADR 0614" - "ADR 0613" @@ -775,7 +776,7 @@ vault as an own secret: JetStream create and update under modules' names only (s a stream by limits, which drops its oldest when full (only a work queue refuses the new instead), so a reader that misses a dropped event sees the gap in revisions and asks once more. The indexer's own kept data stays on the old sections (ADR 0595 §10 names it among the foundation); the assigner's goes beside its existing `streams` entry - `placement`, under a record of this milestone extending ADR 0595 §10's foundation (to be proposed) to the parts + `placement`, under ADR 0619, which extends ADR 0595 §10's foundation to the parts the bus module's own composition, placement, build or delivery needs before it can answer: the assigner, the tool runner, the builder, the checker, mesh-config and the vault. **The size, an estimate**: read on the bus on 2026-11-04, the buckets the readers read hold about 3 MiB of current values at the indexer (its manifests about @@ -961,8 +962,7 @@ and the new form are never both used for one local name. During the move the gra sections' entries, so each entry has one creator. The indexer counts the modules on each old section, a tool reads the count, and the judge refuses a section once its count is 0. The foundation's own kept data (the bus module's, the grants part's and the indexer's) stays on the old sections for good and is not counted: those modules cannot -wait for a process that needs them first. A record of this milestone extending ADR 0595 §10's foundation (to be -proposed) adds the parts the bus module's own composition, placement, build or delivery needs before it can answer: +wait for a process that needs them first. ADR 0619, extending ADR 0595 §10's foundation, adds the parts the bus module's own composition, placement, build or delivery needs before it can answer: the assigner, the tool runner, the builder, the checker, mesh-config and the vault; their new state and streams of this milestone go on their old sections too (§ 12.2; design 51 *The move*; design 32). -- 2.54.0 From 216e0e510209f82c5c8b2ac681ea0cdd17db2e80 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 00:53:30 +0100 Subject: [PATCH 19/20] Design 26: keep the seats' own events apart from their holders', since no event moves subject in this milestone The four seats defined whole list only what their seat subject carries as the seat's; the holders' events stay under their own subjects, checked by a fixture from the running holder. The vault seat drops the two verbs no holder serves. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/26-the-seats.md | 42 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index ee3671d8..5eb1a69b 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -188,26 +188,36 @@ record of this milestone changing ADR 0613 §1-§3, to be proposed). operator's answer of 2026-11-04 to move them all). Every seat this milestone changes is defined whole and its contract moves into `mesh-contracts`: `npm-package-registry`, `go-package-registry`, `container-image-registry`, `oci-artifact-registry` (one common part of their shared verbs, written once, so the four cannot drift) and - `mesh-broker`; and the four seats it adds a verb or an event to, each with every verb and event its holder's - manifest on the trunk declares today, plus the new ones: - - `mesh-delivery-checker` (held by the checker): the verbs `try`, `try-read` and `try-end`; the events `verdict` - and `tag-wanted` (design 51 *Tagged versions*, its family `checker.asks`), both new. Its manifest declares no - event today; `preview.written` on its own stream is declared in the same change (above). - - `mesh-config` (held by mesh-config): the verbs `config` and, new, `renames`; the events `config-changed` and, - new, `renamed`, `retiring` and `retired` (design 32). + `mesh-broker`; and the four seats it adds a verb or an event to, each with every verb its holder's manifest on the + trunk declares today, plus the new ones. **No event moves subject in this milestone** (a record of this milestone + changing ADR 0613 §1-§3, to be proposed): a seat's own events are only those published on its seat subject today, + and a holder's own events stay under the holder's own subjects, listed beside the seat they belong with, never moved + onto it: + - `mesh-delivery-checker` (held by the checker): the verbs `try`, `try-read` and `try-end`; no seat event. The + holder's own, beside it: its family `checker.asks`, new, with `tag-wanted` (design 51 *Tagged versions*) and + `verdict` (*Out of step is learned from events*, below); and its stream `checks`, which already declares + `preview.**` and `owed.**`, only its `emits` lacking `preview.written`, which the same change adds. + - `mesh-config` (held by mesh-config): the verbs `config` and, new, `renames`; no seat event. The holder's own, + beside it: `config-changed`, and its family `config.events`, new, with `renamed`, `retiring` and `retired` + (design 32). - `mesh-vault` (held by the vault): the verbs `get`, `secret-ask`, `issued`, `rotate`, `move-in`, `node-key`, `own`, `issue-account`, `sign`, `enrolment-token`, `node-account`, `rotate-node`, `node-account-confirm`, - `abandon` and, new, `retire`; the events `provisioned`, `rotated`, `secret-changed`, `account-hashed`, - `own-replaced`, `account-issued`, `signed`, `rotation-begun`, `node-account-ready`, `rotation-done` and, new, - `own-retired` (design 32). The seat list mesh-sdk compiles today also names `accept` and `move-asks`, which - the vault does not serve; the definition carries what the holder serves, and the two are an open point. + `abandon` and, new, `retire`; no seat event. The holder's own, beside it: `provisioned`, `rotated`, + `secret-changed`, `account-hashed`, `own-replaced`, `account-issued`, `signed`, `rotation-begun`, + `node-account-ready`, `rotation-done` and, new, `own-retired` (design 32). The seat list mesh-sdk compiles + today also names `accept` and `move-asks`, which no holder serves and nothing calls: the same mesh-sdk change + drops them from the seat, and its `vault_seat_test.go` with them. - `operator-channel` (held by the router, the messenger module): the verbs `open`, `history`, `notify`, `tell` - and `asks`; the asks it accepts, `ask`, `cancel` and `condition`; the events `decided` and, new, `offered` and - `closed` (design 55 § 11); the holder's own `refused` beside them. mesh-sdk's seat list holds no entry for it - today, so its definition is written from the holder's manifest. + and `asks`; the asks it accepts, `ask`, `cancel` and `condition`; its seat event `decided`; `by-caller` for + `ask`, `cancel`, `condition` and `decided`; and `records` `asks`, as the holder's seat entry has them. The + holder's own, beside it: `refused`, and `offered` and `closed`, new, on its restricted stream (design 55 § 11). + mesh-sdk's seat list holds no entry for it today, so its definition is written from the holder's seat entry. - The builder and the indexer hold no seat: the builder's `numbered` and `old-form-closed` and the indexer's - `manifest-accepted` and `indexer.views` are events of those modules, declared in their own manifests, which + **How it is checked**: each of these definitions' conformance fixtures names every event's subject, the seat's and + the holder's own, and a case of the holder's whole-module test takes a fixture from the running holder (an event + it publishes) and matches it, subject and body, against the definition. The builder and the indexer hold no seat: + the builder's `numbered` and `old-form-closed`, the indexer's `manifest-accepted` and its family `indexer.views` + are those modules' own, declared in their own manifests, which ADR 0613 does not govern, and no seat definition carries them. Every other seat, the foundation's included, has its definition on the bus in the same milestone, all at once (ADR 0608 §2), holding the seat list's grain: its name, whether it is held once or on every node, its verbs with their inputs and its provision, generated from the seat list mesh-sdk compiles today, so the move changes -- 2.54.0 From 457ac2eabdcd57ae7d868251992b919b9ad6107a Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 01:43:36 +0100 Subject: [PATCH 20/20] Designs 25, 26, 51: cite ADR 0620 and ADR 0621; the owners serve their first views as tools in a runtime of their own ADR 0621 keeps the manifest form as it is, so the process-subject form, its mesh-sdk change, the open point on who grants it and the push's wait on a judge release all go; the per-verb grants and the record's four checks take their place. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 34 ++++++++++--------- 03-DESIGN/01-to-be/26-the-seats.md | 10 +++--- .../51-the-registries-keep-what-is-named.md | 12 +++---- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 6159a4aa..44a695ae 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -15,6 +15,7 @@ decisions: - "ADR 0595" - "ADR 0619" - "ADR 0615" + - "ADR 0621" - "ADR 0614" - "ADR 0613" - "ADR 0612" @@ -747,24 +748,24 @@ vault as an own secret: JetStream create and update under modules' names only (s and `refused`; never by the bus module, which cannot raise it while it is down; it names the module and the entry; a later `created` clears it, and so does a later accepted verdict of that module that no longer asks for the entry. -- **A foundation part's first view of another's state is a verb its owner's process answers** (ADR 0615). The four +- **A foundation part's first view of another's state is a tool its owner answers in a runtime of its own** (ADR 0615, + ADR 0621). The four foundation parts that read each other (the grants part, the tool runner, the indexer and the assigner) stop reading the indexer's and the assigner's bus state in the milestone of ADR 0612, with the other 19 readers of that - state (ADR 0615 §1). Each owner answers on its own process subject, granted by name to these readers and never - through the tool runner, so the tool runner never asks through itself: the assigner serves `placement` to the - grants part, the tool runner and the indexer, `seat-holders` to the tool runner and `wants` to the grants part; the - indexer serves `manifests` and `modules` to the grants part, the tool runner and the assigner (ADR 0615 §3). Each - reader declares these as verbs it invokes. Serving a verb on a process's own subject, outside the tool runner, has - no manifest form today (the assigner serves its seat verbs as tools); the form is new in mesh-sdk's manifest types - and rides the milestone's one rebuild. Which part issues these subjects' grants first is not yet decided: the - grants part composes every grant from the manifests, and is itself one of these readers, so its own first view - cannot wait on a grant it has not composed (an open point, tracked on issue 1966). **The judge that runs takes the - form in.** Every manifest of the merge is judged by the indexer that runs, the old build, before any new build - runs, so a new indexer can never judge its own manifest; and mesh-sdk's strict decoding refuses a field it does not - know (research 082, its Findings for question 5). So the running judge accepts the process-subject form before the - indexer and the assigner use it, decided in a record of this milestone extending ADR 0615 §3 (to be proposed), and - design 51's *When the group is pushed* holds the push until the judge that runs accepts it. The owner answers at once from what it holds, as soon as its process is - connected and before it has its own first view of any other part, with the revision of its state it answered at, + state (ADR 0615 §1). **Each owner serves them as tools of its own module**, the indexer `manifests` and + `modules`, the assigner `placement`, `seat-holders` and `wants`, and declares `runs-as`, so its tools bundle runs in + a runtime of its own on the module's own bus account, never in the node's tool runner, and the tool runner never + asks through itself (ADR 0621 §1). No new manifest form is added: a process still answers no tool call or seat verb + (ADR 0436 §3). **Each verb is granted to exactly its readers** (ADR 0621 §2, as ADR 0615 §3 lists them): `placement` + to the grants part, the tool runner and the indexer; `seat-holders` to the tool runner; `wants` to the grants part; + `manifests` and `modules` to the grants part, the tool runner and the assigner. A reader declares each in `invokes` + as `.` only where it is granted, which is the grant the grants part composes as for any tool, and + each tool checks the proved caller against a `callers` list holding exactly its readers and refuses any other, the + owner itself included. **The judge of the milestone is its own group's** (ADR 0621 §4): since no field is added, + the running judge takes the indexer's and the assigner's manifests in as it does today, and the merge check judges + them with the judge built from the group's own head; no release of the judge goes ahead of the milestone. **The + owner's runtime answers before any first view of its own** (ADR 0621 §3): at once from its module's own state, as + soon as it is connected and before it has asked any other part anything, with the revision of its state it answered at, so the indexer and the assigner, each asking the other at start, never wait on each other (§4). The reader then follows the owner's declared events, each carrying the revision of the state it follows, and never asks the verb again on a timer; a gap in revisions makes it ask once more, said in its log (§5). The owners' events are new @@ -986,6 +987,7 @@ issue 1966. | The bus module runs whole | one integration test in the nats module's repository that starts the bus module whole against the real bus server: a verdict, then `created`, then the binding in the membership; a restart that catches up a verdict given while it was down; a bucket whose settings differ raises `drifted`, the seat's `apply` of a larger cap clears it with `drift-cleared`; a refused create raises its condition and hands no binding; `recreate` and `drop` without the operator's warrant are refused, and with it act after a stream snapshot; an orphan is listed, raises `orphaned`, and goes only by `drop`; each template at a small cap refuses, events drop the oldest, and `full` is raised at the raise mark and cleared only below the clear mark; `recreated` rewrites the binding and `dropped` removes it; at start the first view from `indexer_bus_state` creates what a verdict given while it was down asked; the older remove tools refuse a new-form entry, naming `drop`'s tool | | A module asking bus state waits for the bus module | the module graph's table test: a manifest with a ledger entry has a requirement edge to the module providing `bus-state`, one without has none (ADR 0614 §5) | | A foundation part's first view | unit tests of each owner and reader (ADR 0615's How it is checked), as table cases, and these cases of the whole-module integration tests that exist today (mesh-delivery is over its integration budget, so no new test function): the indexer's (`cmd/mesh-delivery-indexer`, its one whole-module test, which the milestone's packages test merges into), the assigner's (`cmd/mesh-delivery-assigner`) and the grants part's (`cmd/mesh-grants`), and the tool runner's in mesh-tools: the owner answers its verb at once from what it holds, with its revision, before its own first view; the reader asks once at start, then only on a gap in revisions; a reader whose owner is down raises its condition only past the bound and never reads the owner's state, and the owner's answer clears it; an empty answer at revision 0 drops no login; an owner's event carries the changed value whole, and a reader applies a put and a delete from the events alone and never asks the verb; a revision lower than the one held makes the reader ask its first view again; an existing waiter on `manifest-accepted` or on the assigner's `put` does not wake on the new families; the read tool shows the revision each reader holds, and the re-read tool asks once and records the hand act; in the +| The owners' first views are their own tools, in a runtime of their own (ADR 0621) | cases of the indexer's and the assigner's whole-module integration tests: each first-view tool, called from a reader's account while the node's tool runner is stopped, answers (§1); each answers its granted readers and refuses every other account, the owner's own included (§2); the two runtimes started at the same moment, each asking the other's first view at start, both answer within the first-view bound with their revisions (§3); and the milestone's group passes its merge check with no change to the judge's known fields for these verbs (§4) | assigner's whole-module test, the assigner starts before any new-form bus state exists, with no binding of the bus module, and the indexer, the grants part and the tool runner each get their first view of it. Live, after the delivery: the indexer's count of `reads` entries on the indexer's and the assigner's state reads 0 | | A foundation part's first view comes within its bound | live, after the delivery: each reader's read tool (`grants_first_views`, the mesh MCP server's `mesh_first_views`, `indexer_first_views`, `assigner_first_views`) shows, for the indexer's and the assigner's verbs, the time from its ask to the owner's answer under the two-minute bound, and no first-view condition raised by the delivery | diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 5eb1a69b..73c610b9 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -19,6 +19,7 @@ updated: 2026-11-04 decisions: - "ADR 0608" - "ADR 0613" + - "ADR 0620" - "ADR 0612" - "ADR 0614" - "ADR 0605" @@ -176,21 +177,20 @@ merges. **A seat is defined once, in one language-neutral file, and every part learns it from the bus** (ADR 0608, from research 081). The registry seats (design 51), `mesh-broker` (design 25 § 12.3) and the four seats the milestone -adds verbs or events to are the first defined whole this way, in the milestone of ADR 0612 (ADR 0613 §1, and a -record of this milestone changing ADR 0613 §1-§3, to be proposed). +adds verbs or events to are the first defined whole this way, in the milestone of ADR 0612 (ADR 0613 §1, +ADR 0620). - **The file** is a schema with conformance fixtures: the seat's verbs and their messages, its events and their bodies, its state and the forms of its provisions. It lives in the contracts module of its area. Its Go and TypeScript types are generated from it and committed beside it; nobody writes them by hand. The format is JSON, its messages and bodies JSON Schema, as the seat set's verb inputs already are, with the format's own schema checked in; the fixtures are data, never functions. -- **What is defined whole now** (ADR 0613; a record of this milestone changing ADR 0613 §1-§3, to be proposed, on the +- **What is defined whole now** (ADR 0613; ADR 0620, on the operator's answer of 2026-11-04 to move them all). Every seat this milestone changes is defined whole and its contract moves into `mesh-contracts`: `npm-package-registry`, `go-package-registry`, `container-image-registry`, `oci-artifact-registry` (one common part of their shared verbs, written once, so the four cannot drift) and `mesh-broker`; and the four seats it adds a verb or an event to, each with every verb its holder's manifest on the - trunk declares today, plus the new ones. **No event moves subject in this milestone** (a record of this milestone - changing ADR 0613 §1-§3, to be proposed): a seat's own events are only those published on its seat subject today, + trunk declares today, plus the new ones. **No event moves subject in this milestone** (ADR 0620): a seat's own events are only those published on its seat subject today, and a holder's own events stay under the holder's own subjects, listed beside the seat they belong with, never moved onto it: - `mesh-delivery-checker` (held by the checker): the verbs `try`, `try-read` and `try-end`; no seat event. The 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 d37e1b4f..f2688589 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 @@ -24,6 +24,8 @@ decisions: - "ADR 0595" - "ADR 0596" - "ADR 0615" + - "ADR 0620" + - "ADR 0621" - "ADR 0346" - "ADR 0386" - "ADR 0564" @@ -88,8 +90,7 @@ tagged version; the holder itself refuses `remove` of a version with no tag, but on `container-image-registry` `mirror` and `tags`; the two seats `distribution` holds add `hold` and `release`, with the events `held` and `released`, for the artifact sweep (ADR 0617 §2), both verbs in the seat definition and in `distribution`'s caller list. The seats this milestone changes, these four, `mesh-broker` and the four it adds a verb or an event to, are defined -whole, their contracts moved into `mesh-contracts` (ADR 0613 §1, and a record of this milestone changing ADR 0613 -§1-§3, to be proposed; design 26). Events: `published`, `refused` (added to ADR 0605 §2's two, additively) and `removed`, each +whole, their contracts moved into `mesh-contracts` (ADR 0613 §1, ADR 0620; design 26). 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; each seat's events are its own family, `.events`, kept seven days. A holder stores under the version it is given and counts nothing. @@ -363,10 +364,9 @@ wake on it. **When the group is pushed.** Only once the release that adds the node-engine's stated capability (design 47) runs on every node, read from the status view's haves before the push, and once the progressive insights of ADR 0595 and -ADR 0597 this design is written to are accepted, and once the running judge accepts the process-subject form, the -manifest form by which the indexer and the assigner serve their verbs on their own process subjects (design 25 -§ 12.2): the judge that runs accepts that form before the indexer and the assigner use it, decided in a record of -this milestone extending ADR 0615 §3 (to be proposed). A node away for weeks does not hold the push for ever: it is left +ADR 0597 this design is written to are accepted. No release of the judge goes ahead: the indexer and the assigner +serve their first views as tools of their own, in a form the running judge already takes in (ADR 0621 §4; design 25 +§ 12.2). A node away for weeks does not hold the push for ever: it is left out of that read only by a recorded hand act naming the node and why, as `registry_old_name` drops one, said in the group's pull request; the node then takes the new node-engine as any away node does when it returns. -- 2.54.0