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