From 024627f6581cb61cd530d8202f0d04b3c54e22c4 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 01:14:06 +0100 Subject: [PATCH 01/10] Design 34: a session waits on any event, the tool runner hearing every one (ADR 0670) Watchers reread on a timer because mesh_wait refuses every event outside a list of five, and the delivery status view's events, which playbooks have watchers wait on, are not on it. ADR 0670 grants the tool runner every event subject and drops the list; this writes that as milestone every-event, one delivery group of mesh-sdk, mesh-delivery and mesh-tools, and puts design 34 in the main-file-and-parts shape. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../01-to-be/34-milestones/every-event.md | 182 ++++++++++++++++++ 03-DESIGN/01-to-be/34-the-console.md | 62 +++++- 2 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 03-DESIGN/01-to-be/34-milestones/every-event.md diff --git a/03-DESIGN/01-to-be/34-milestones/every-event.md b/03-DESIGN/01-to-be/34-milestones/every-event.md new file mode 100644 index 00000000..95408e5e --- /dev/null +++ b/03-DESIGN/01-to-be/34-milestones/every-event.md @@ -0,0 +1,182 @@ +--- +layer: to-be +design: 34 +milestone: every-event +updated: 2026-11-11 +code: [mesh-sdk, mesh-delivery, mesh-tools] +decisions: + - "ADR 0670" + - "ADR 0367" + - "ADR 0043" + - "ADR 0259" + - "ADR 0332" + - "ADR 0571" + - "ADR 0578" + - "ADR 0614" +--- + +# Design 34, milestone every-event — the tool runner hears every event + +**What it builds.** A session's `mesh_wait` waits on any event of any seat or module, by its name, with no list to +keep: the tool runner's bus account is granted the two event subjects of the whole mesh, and the list of families in +the tool runner's manifest, the manifest validator's rule about it and the grants part's computation of subjects from +it go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); +this part holds every line the milestone builds, cited as "design 34 every-event §n". + +**Where.** Three repositories, one delivery group: mesh-sdk (the shared event subjects, and the field, type and +membership entry that go), mesh-delivery (the grants part, seat `mesh-grants`; the manifest validator in the indexer) +and mesh-tools (the tool runner, module `node-tools`). The milestone is built on each repository's main as it stands; +design 47's milestone G, which edits the grants part's account composition and the tool runner's manifest and manifest +test too, is rebased onto it. + +## The event subjects, in mesh-sdk + +1. **One place names the mesh's event subjects.** mesh-sdk's package `buscompose` gains a file `events.go` with three + exports, used by the grants part (§5) and the tool runner (§12), so neither spells the event namespace itself + (shared code belongs in mesh-sdk): + - `EveryEvent`, the two subjects the tool runner's account hears: `mesh.seat.*.event.>` and `mesh.mod.*.event.>`, + in that order (ADR 0670 §1); + - `EventSubjects(name)`, the subjects one event name lands on. A name is `.`, optionally followed + by further tokens (`operator-channel.decided.claude-code`); each token matches `[A-Za-z0-9_-]+`. It answers four + subjects: `mesh.seat..event.`, the same followed by `.>`, `mesh.mod..event.`, and + the same followed by `.>`, where `` is every token after the first. It refuses, with an error naming the + rule, a name of fewer than two tokens, an empty token, or a token outside that form (so `*` and `>` are refused); + - `EventName(subject)`, the inverse for a heard subject: for `mesh.seat..event.` or + `mesh.mod..event.` it answers the name `.` and whether the emitter is a seat; any + other subject is answered as not an event. + + Why four subjects: a name does not say whether its emitter is a seat or a module, and both namespaces are heard + (§5), so both are subscribed; and a seat's event that names its asker or kind after the event (ADR 0259 §3), or a + module's that names its module and branch, is heard by the event's name alone, the `filter` narrowing it by body. + How it is checked: one table-driven unit test of the three, in `events_test.go`. +2. **The manifest loses `waits`.** `catalogue.Manifest.Waits` and its comment go from `go/catalogue/manifest.go`; + the comment on `ToolRunner` loses "and the only module that may declare `waits`". `go/catalogue/waits_test.go` is + deleted, and one row is added to the decoder's existing table of unknown fields: a manifest + `{"module":"node-tools","version":"1","tool-runner":true,"waits":["a.b"]}` decodes with `UnknownField()` naming + `waits` (ADR 0670 §2). The decoder itself is unchanged: a stored manifest that still carries `waits` is read + without it (ADR 0578 §3). +3. **The baseline of manifest reads stays as generated.** `go/catalogue/manifest_reads_baseline.txt` keeps + `word:waits`: it is the contract of the engines built before any stated what it reads (ADR 0614 §4), which did read + `waits`. In `own_manifest_test.go` the list of baseline entries this contract no longer reads (today the two + renamed seats) gains `word:waits`, its comment naming ADR 0670 §2 as the record that retired the word. +4. **The wait types go.** `go/buscompose/waits.go` (the type `Wait`) is deleted and `Users.Waits` goes from + `go/buscompose/users.go`; `Membership.Waits` and the type `WaitIssued` go from `go/membership/membership.go` + (ADR 0670 §2). Nothing replaces them: no membership names what its account hears. + +## The grant, in the grants part (seat `mesh-grants`) + +5. **The tool runner's runtime hears every event subject.** In `internal/buscompose/nats.go`, where a runtime's + permissions are composed from the modules it carries, the call of `waitGrants` is replaced by: when one of the + carried modules says `tool-runner` (`Declared.ToolRunner`), `buscompose.EveryEvent` (§1) is appended to the + runtime account's subscribe list. Nothing else is added for it: no publish, no durable consumer, no queue, no other + subject; a runtime carrying no tool runner gets neither subject (ADR 0670 §1, §3; ADR 0043 §2, §3, §5, §8 as ADR + 0670 §3 now reads them). The subjects are written by the grants part into the bus's user list as every grant is + (ADR 0571 §9), read with the grants part's own tools as today. +6. **What the grants part computed from the list goes.** `internal/buscompose/waits.go` is deleted whole (`Wait`, + `WaitSubject`, `waitGrants`, `waitsIssuedFor`); `Declared.Waits` goes from `internal/buscompose/users.go`, and with + it the term `len(d.Waits) > 0` of `runnerServes`; `m.Waits = waitsIssuedFor(d)` goes from `membership.go`, and the + alias `WaitIssued` from `sdk_membership.go`; `waitsOf` and the field `Waits:` of `declaredFor` go from + `internal/grants/records.go` (ADR 0670 §2). `safeSubject` in `nats.go` stays: the bus-state grants use it. +7. **The tool runner's membership names no events.** The membership the grants part writes for the tool runner in + its state `memberships` (key `.node-tools`, ADR 0571 §10) carries no `waits` key, since the field is gone + (§4); every other part of it is unchanged. +8. **A composition test holds the grant.** In `internal/buscompose/buscompose_test.go`, one table-driven test composes + a runtime carrying the tool runner and one other module, and a runtime carrying the other module alone, and holds: + the first's subscribe list contains `mesh.seat.*.event.>` and `mesh.mod.*.event.>`; no subscribe subject of either + is wider than those (none of `>`, `mesh.>`, `mesh.*.>`, `mesh.seat.>`, `mesh.mod.>`, nor any `mesh.stream.` or + `$JS.` subject beyond what the carried modules' own consumes give); the first's publish list holds no subject of + another module's or seat's events; the first derives no durable consumer beyond its carried modules' `consumes`; + and the second has neither subject (ADR 0670 §1, §3). +9. **The real bus holds it too.** The grants part's existing real-bus test (`cmd/mesh-grants/composed_integration_test.go`) + gains three cases, no new test: the tool runner's account, as composed, subscribes + `mesh.mod.mesh-delivery-view.event.delivered` and receives an event published there; it is refused a publish on + that subject; and another module's account is refused a subscription on an event it does not consume (ADR 0670 §1, + §3). + +## The manifest validator, in the indexer + +10. **The rule about the list goes.** `waitProblems` and its call in `judge/rules.go` are deleted, and with them the + regular expression `waitToken` where nothing else uses it; the table rows that judged `waits` + (`judge/rules_test.go`, the `waits` cases) are deleted, and one row is added: a manifest that carries `waits` is + refused by the manifest validator as the checker runs it (`Shelf`), naming the unknown field `waits`, and set aside + to wait as the indexer runs it (`ShelfWaiting`), as any field it does not know (ADR 0578 §1); the tool runner's + manifest without it is accepted + (ADR 0670 §2). + +## The tool runner (module `node-tools`) + +11. **Its manifest loses `waits`.** `node-tools/module.json` drops the key `waits` and its five entries; nothing else + in it changes, its `version` stays `2` (ADR 0670 §2). +12. **`mesh_wait` takes any event name.** `console.Surface.waitAnswer` (`internal/console/wait.go`) no longer reads the + membership: each name of `subjects` becomes its subjects through `buscompose.EventSubjects` (§1), a refused name is + answered as a failure quoting that error, and a call naming more than 16 names is refused, saying the bound (each + name is four subscriptions for the call; 16 names is 64, far above what a watcher names, and a call is never a + subscription to the whole bus). The same subject named twice is subscribed once. The map from subject to name goes: + the heard event is named by `buscompose.EventName` of the subject it arrived on. The timeout, the filter and the + timed-out answer are unchanged (ADR 0367 §1). The refusal of names outside a list, and its words naming the + manifest, go (ADR 0670 §2). +13. **The answer names the heard event.** A heard event is answered as today, `heard` with `subject`, `body` and + `at`, where `subject` is now the heard event's whole name (`mesh-delivery-view.delivered`, + `operator-channel.decided.claude-code`), and a new field `from`, `seat` or `module`, says which namespace it arrived + on. The `read` line stays (an event only wakes you, ADR 0367 §4); for a name beginning `operator-channel.decided` + it says instead that the operator's answer is read only with `claude_code_answer`, never from the event's body + (ADR 0670 §5; ADR 0332 §5, §6). +14. **A refused subscription is said, and why.** `bus.Conn.HearFirst` is unchanged: it says a subscription the bus + refuses at once, naming the subject. `waitAnswer` answers that failure adding that the grants part grants this + machine's tool runner every event subject (ADR 0670 §1), so a refusal means its account's grant is not composed + yet, and that the state is read from its owner meanwhile. +15. **The tool's description says what it hears.** `waitTool`'s description of `subjects` says: one or more event + names, `.`, such as `mesh-delivery-view.delivered`; an event whose subject goes on after + the name (an asker, a kind, a module) is heard by the name too; the filter narrows by the body's fields. Its own + description keeps "an event only wakes you" and adds that an operator's answer is read only with + `claude_code_answer`. The words "only those the mesh granted this machine's tool runner" go. +16. **The hearing serves `mesh_wait` alone.** `HearFirst` has one caller, `waitAnswer`; a carried module's code still + reaches the bus only as its own membership names it, as the runtime keeps it today (ADR 0670 §1). How it is + checked: the code review reads `HearFirst`'s callers. +17. **What goes from the tool runner's code.** The type `bus.WaitIssued` (`internal/bus/wait.go`) and `Membership.Waits` + (`internal/bus/bus.go`) go. `console/manifest_waits_test.go` keeps its test of what the tool runner consumes and + its test of the runner's own secrets; its test of the five families becomes a test that the manifest has no key + `waits`, and its comment cites ADR 0670 §2. `console/wait_test.go`'s `waitingRuntime` issues no waits, and its + tests wait on `mesh-delivery-view.delivered` and `operator-channel.decided.claude-code` with no membership naming + either; the test that a name outside the list is refused becomes a test that a name with `*`, `>` or one token is + refused (ADR 0670 §2). No test is added: the module stays within its budget. + +## Events carry no secret + +18. **The prepare lists every event.** The milestone file's prepare (playbook 07, step 0) gains a section *Events and + their fields*: every event each module's manifest `emits` and each seat's definition declares, read from the + indexer's modules (`mesh-delivery-view.indexer_modules`) and the seat list (the seat `mesh-catalog`'s definitions), + each with the fields of its body as the emitting code builds it, by file and line (ADR 0670 §4). +19. **The code review confirms none carries a secret, before the grant is delivered.** The milestone's code review + (playbook 07, step 2) reads that list and confirms that no event's body carries a credential, a token that grants + an act, code, or a message marked secret; an intake `choice`'s one-use token is not such a token (ADR 0259 §3, §5). + An event found carrying one holds the milestone's delivery and goes to the operator as a design question + (ADR 0670 §4). +20. **From then on every change is asked the same.** Playbook 07's review step gains the question, for any change + that adds an event or a field to one: does it carry a secret? Written in this milestone's hq pull request + (ADR 0670 §4). + +## Delivery + +21. **One delivery group, no order named between its members.** The three repositories are pushed and checked as + one group. No order is named between them, and no transitional field is kept (the operator, 2026-11-03: a change + is designed as it should be, its delivery is the delivery system's): a part built with the new mesh-sdk reads a + stored manifest of the tool runner that still carries `waits` without it (ADR 0578 §3), and a node-engine is given a want + only for a manifest whose words it reads (ADR 0614 §4). Between the grants part's delivery and the tool runner's on a + node, `mesh_wait` there answers the bus's refusal (§14), or an older tool runner's "issued no event families": + both loudly, and a watcher reads its owner after every answer (ADR 0367 §4). + +## Acceptance + +- **A watcher wakes on an event no manifest names** (the operator's acceptance): on the live mesh, a watcher's + `mesh_wait` on `mesh-delivery-view.delivered` answers the status view's `delivered` within a second of its + publication, with no rereading on a timer; the tool runner (`node-tools`) is delivered to every node, and the grants part and the + indexer to theirs, read with `mesh-delivery.show` and each node's `node-engine.composition` (§5, §12). +- **The grant is exactly two subjects**: the composition test of §8 and the real-bus cases of §9 pass. +- **The list is gone**: the manifest validator, as the checker runs it, refuses a manifest carrying `waits`, the + indexer sets it aside, and the tool runner's manifest is accepted without it (§2, §10, §11). +- **No event carries a secret**: the milestone file's *Events and their fields* is complete and the code review's + confirmation is written in it before the grants part is delivered (§18, §19). +- **An answer is never taken from an event**: in the three repositories nothing reads an operator's answer from a + `decided` event's body, which the code review confirms, and `mesh_wait`'s answer for one says to read it with + `claude_code_answer` (§13). diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index 2bc703e4..61f897fa 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -1,9 +1,16 @@ --- layer: to-be -status: implemented -code: [mesh-tools, mesh-host, mesh-controller] -updated: 2026-10-17 +status: in-progress +code: [mesh-tools, mesh-host, mesh-controller, mesh-sdk, mesh-delivery] +updated: 2026-11-11 decisions: + - "ADR 0670" + - "ADR 0043" + - "ADR 0259" + - "ADR 0332" + - "ADR 0571" + - "ADR 0578" + - "ADR 0614" - "ADR 0197" - "ADR 0195" - "ADR 0175" @@ -119,6 +126,40 @@ listed once per machine does not hold on the live mesh MCP server — postgres a optional, answered by whichever instance replies. The address replaces that statement rather than repairing it. +## 3b. Waiting on the bus + +*By ADR 0367, as ADR 0670 changes it.* Beside the five discovery tools of §3a and `mesh_runtimes`, the mesh MCP +server offers a seventh, `mesh_wait`: a session names one or more events, an optional `filter` of body fields and a +`timeout` (four minutes when absent, ten at most), and is answered the first matching event or told it timed out +(ADR 0367 §1). A session calls it in a loop from a background command (ADR 0367 §3), and after every answer or time-out +reads the state once from the verb that owns it: an event only wakes it (ADR 0367 §4). + +```mermaid +flowchart LR + E[a seat's or a module's event
mesh.seat.<seat>.event.… / mesh.mod.<module>.event.…] -->|heard for the call| TR[the tool runner's account] + G[seat mesh-grants] -->|grants the two event subjects| TR + TR -->|mesh_wait answers it by name| S[a session] + S -->|then reads the state| O[the verb that owns it] +``` + +**The tool runner hears every event of the mesh, and no list says which** (ADR 0670 §1, §2). The grants part (seat +`mesh-grants`) grants the account of the runtime carrying the tool runner a plain subscription on the event subjects +of every seat and every module, and on nothing else of theirs: no accept, proof, call, work queue or bucket, and no +publish. `mesh_wait` subscribes when called and closes the subscription when it answers, so the account keeps no +durable consumer and no queue of its own (kept from ADR 0367 §2), and any event name is accepted: a new event is +heard from its first publication, with no change to a manifest. The tool runner's manifest names no events, the +manifest validator has no rule about them, and no membership lists them. + +**What that makes audible, and what still bounds it.** Every session now hears the operator channel's `decided` of +every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, no token +that grants an act, no code, no message marked secret, which the code review asks of every change that adds an event +or a field (ADR 0670 §4). And a heard event never answers anything: the operator's answer to an ask is read only +with `claude_code_answer`, from the router's record and held to the question's digest (ADR 0670 §5, ADR 0332 §5, +§6), and an asker acts only on a warrant from its own `decided` subject through its own consumer (ADR 0259 §6). + +What is built for this, line by line, is milestone every-event's part, +[`34-milestones/every-event.md`](34-milestones/every-event.md). + ## 4. Where it runs On whichever machines an operator sits at, by assignment. It is not on the control node by default and @@ -145,6 +186,12 @@ address gets a refused connection, which is the truthful answer. | a call through the mesh MCP server's endpoint reaches a module over the bus and the answer is the module's own, unshaped | ADR 0035, a surface decides nothing | | on the live mesh: the mesh MCP server assigned to a workstation answers `tools/list` on loopback and a call to the forge returns repositories | the exit of work-order step 3 | | the composed filter for a machine carrying the mesh MCP server opens no port for it | ADR 0144 | +| on a real bus, `mesh_wait` answers the first matching event within a second of its publication, and after every answer a session reads the owner's state | ADR 0367 §1, §4 | +| live: a watcher's `mesh_wait` on the status view's `delivered`, an event no manifest names, wakes on it with no rereading on a timer | ADR 0670 §2, milestone every-event | +| a composition test: the tool runner's runtime account is granted exactly `mesh.seat.*.event.>` and `mesh.mod.*.event.>` beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume | ADR 0670 §1, §3 | +| the manifest validator, as the checker runs it, refuses a manifest that carries `waits` as an unknown field, and accepts the tool runner's without it | ADR 0670 §2 | +| the milestone's prepare lists every event with its body's fields, and its code review confirms none carries a secret before the grant is delivered; every later change adding an event or a field is asked the same in its review | ADR 0670 §4 | +| nothing takes an operator's answer from a `decided` event's body; `claude_code_answer` is its one reader | ADR 0670 §5 | ## What shipped, 2026-09-30 @@ -163,6 +210,15 @@ on the live mesh; and a module registered from the catalog by hand recorded its than as a path on the git seat, because `--self` takes the forge path form — the rebuild-on-merge still matched it by URL. +## Milestones + +| Milestone | What it builds | State | +|---|---|---| +| the mesh MCP server | §1 to §5: the module, its loopback endpoint, its discovery (ADR 0152) | delivered 2026-09-30 (above) | +| found by address | §3a: five announced tools and addresses (ADR 0195, ADR 0197) | delivered; [as-is 13](../00-as-is/13-the-console.md) | +| waiting on the bus | §3b's `mesh_wait` on a list of five families in the tool runner's manifest (ADR 0367) | delivered (issue 745); [as-is 13](../00-as-is/13-the-console.md) | +| every-event | §3b as ADR 0670 changes it: the tool runner hears every event, and the list goes; one delivery group of mesh-sdk, mesh-delivery and mesh-tools | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) | + ## What this does not settle - **The mesh's own verbs on the bus.** Design 33's third family: these now answer directly, through -- 2.54.0 From c232b1fb819d929ebe06e33e82eba1bf700cd637 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 01:25:22 +0100 Subject: [PATCH 02/10] Design 34 every-event: write in the four domain reviews' findings The reviews found waits that time out for ever on a misspelt name, refusals that would loop a watcher, an acceptance no answer could prove, readers still describing the list, and no way back said. Each is now a design line; the stream-events lines are written for the recommended option and marked pending the operator's answer. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../01-to-be/34-milestones/every-event.md | 282 +++++++++++------- 03-DESIGN/01-to-be/34-the-console.md | 17 +- 2 files changed, 196 insertions(+), 103 deletions(-) diff --git a/03-DESIGN/01-to-be/34-milestones/every-event.md b/03-DESIGN/01-to-be/34-milestones/every-event.md index 95408e5e..b1544a18 100644 --- a/03-DESIGN/01-to-be/34-milestones/every-event.md +++ b/03-DESIGN/01-to-be/34-milestones/every-event.md @@ -3,13 +3,16 @@ layer: to-be design: 34 milestone: every-event updated: 2026-11-11 -code: [mesh-sdk, mesh-delivery, mesh-tools] +code: [mesh-sdk, mesh-delivery, mesh-tools, mesh-catalog modules/claude-code] decisions: - "ADR 0670" - "ADR 0367" - "ADR 0043" - "ADR 0259" - "ADR 0332" + - "ADR 0363" + - "ADR 0376" + - "ADR 0489" - "ADR 0571" - "ADR 0578" - "ADR 0614" @@ -18,37 +21,45 @@ decisions: # Design 34, milestone every-event — the tool runner hears every event **What it builds.** A session's `mesh_wait` waits on any event of any seat or module, by its name, with no list to -keep: the tool runner's bus account is granted the two event subjects of the whole mesh, and the list of families in -the tool runner's manifest, the manifest validator's rule about it and the grants part's computation of subjects from -it go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); -this part holds every line the milestone builds, cited as "design 34 every-event §n". +keep: the tool runner's bus account is granted the event subjects of the whole mesh, and the list of families in the +tool runner's manifest, the manifest validator's rule about it and the grants part's computation of subjects from it +go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); this +part holds every line the milestone builds, cited as "design 34 every-event §n". -**Where.** Three repositories, one delivery group: mesh-sdk (the shared event subjects, and the field, type and -membership entry that go), mesh-delivery (the grants part, seat `mesh-grants`; the manifest validator in the indexer) -and mesh-tools (the tool runner, module `node-tools`). The milestone is built on each repository's main as it stands; -design 47's milestone G, which edits the grants part's account composition and the tool runner's manifest and manifest -test too, is rebased onto it. +**Where.** Four repositories and hq, one delivery group: mesh-sdk (the shared event subjects; the field, type and +membership entry that go), mesh-delivery (the grants part, seat `mesh-grants`; the manifest validator in the indexer), +mesh-tools (the tool runner, module `node-tools`) and mesh-catalog (the claude-code module's Stop hook, §20), with hq +for the playbook and agent lines (§24). Only these reference the list today: no other manifest carries the key +`waits`. The milestone is built on each repository's main as it stands. Design 47's milestone G edits the grants +part's account composition and the tool runner's manifest and manifest test too; its lead rebases it onto this +milestone's heads when G's develop restarts. + +**Pending lines.** The lines marked *Pending the operator's answer (stream events)* are written for the recommended +option of a question open with the operator: whether the tool runner also hears the events kept in a module's own +stream (ADR 0363 §2). They are kept, with the mark removed, if the operator chooses it, and deleted if not; no other +line depends on them. ## The event subjects, in mesh-sdk 1. **One place names the mesh's event subjects.** mesh-sdk's package `buscompose` gains a file `events.go` with three - exports, used by the grants part (§5) and the tool runner (§12), so neither spells the event namespace itself - (shared code belongs in mesh-sdk): - - `EveryEvent`, the two subjects the tool runner's account hears: `mesh.seat.*.event.>` and `mesh.mod.*.event.>`, - in that order (ADR 0670 §1); + exports, used by the grants part (§6) and the tool runner (§13), so neither spells the event namespace itself: + - `EveryEvent`, the subjects the tool runner's account hears: `mesh.seat.*.event.>` and `mesh.mod.*.event.>`, in + that order (ADR 0670 §1); - `EventSubjects(name)`, the subjects one event name lands on. A name is `.`, optionally followed by further tokens (`operator-channel.decided.claude-code`); each token matches `[A-Za-z0-9_-]+`. It answers four subjects: `mesh.seat..event.`, the same followed by `.>`, `mesh.mod..event.`, and - the same followed by `.>`, where `` is every token after the first. It refuses, with an error naming the - rule, a name of fewer than two tokens, an empty token, or a token outside that form (so `*` and `>` are refused); + the same followed by `.>`, where `` is every token after the first; given a namespace (`seat` or `module`, + §13) it answers only that namespace's two. It refuses, with an error naming the rule, a name of fewer than two + tokens, an empty token, or a token outside that form (so `*` and `>` are refused); - `EventName(subject)`, the inverse for a heard subject: for `mesh.seat..event.` or - `mesh.mod..event.` it answers the name `.` and whether the emitter is a seat; any - other subject is answered as not an event. + `mesh.mod..event.` it answers the name `.` and its namespace; any other subject is + answered as not an event. Why four subjects: a name does not say whether its emitter is a seat or a module, and both namespaces are heard - (§5), so both are subscribed; and a seat's event that names its asker or kind after the event (ADR 0259 §3), or a - module's that names its module and branch, is heard by the event's name alone, the `filter` narrowing it by body. - How it is checked: one table-driven unit test of the three, in `events_test.go`. + (§6); and a seat's event that names its asker or kind after the event (ADR 0259 §3), or a module's that names its + module and branch, is heard by the event's name alone, the `filter` narrowing it by body. How it is checked: one + table-driven unit test of the three, in `events_test.go`, one of whose rows holds that every subject + `EventSubjects` answers for its cases is covered by a subject of `EveryEvent`. 2. **The manifest loses `waits`.** `catalogue.Manifest.Waits` and its comment go from `go/catalogue/manifest.go`; the comment on `ToolRunner` loses "and the only module that may declare `waits`". `go/catalogue/waits_test.go` is deleted, and one row is added to the decoder's existing table of unknown fields: a manifest @@ -65,33 +76,35 @@ test too, is rebased onto it. ## The grant, in the grants part (seat `mesh-grants`) -5. **The tool runner's runtime hears every event subject.** In `internal/buscompose/nats.go`, where a runtime's - permissions are composed from the modules it carries, the call of `waitGrants` is replaced by: when one of the - carried modules says `tool-runner` (`Declared.ToolRunner`), `buscompose.EveryEvent` (§1) is appended to the - runtime account's subscribe list. Nothing else is added for it: no publish, no durable consumer, no queue, no other - subject; a runtime carrying no tool runner gets neither subject (ADR 0670 §1, §3; ADR 0043 §2, §3, §5, §8 as ADR - 0670 §3 now reads them). The subjects are written by the grants part into the bus's user list as every grant is - (ADR 0571 §9), read with the grants part's own tools as today. -6. **What the grants part computed from the list goes.** `internal/buscompose/waits.go` is deleted whole (`Wait`, +5. **What the grants part computed from the list goes.** `internal/buscompose/waits.go` is deleted whole (`Wait`, `WaitSubject`, `waitGrants`, `waitsIssuedFor`); `Declared.Waits` goes from `internal/buscompose/users.go`, and with it the term `len(d.Waits) > 0` of `runnerServes`; `m.Waits = waitsIssuedFor(d)` goes from `membership.go`, and the alias `WaitIssued` from `sdk_membership.go`; `waitsOf` and the field `Waits:` of `declaredFor` go from `internal/grants/records.go` (ADR 0670 §2). `safeSubject` in `nats.go` stays: the bus-state grants use it. -7. **The tool runner's membership names no events.** The membership the grants part writes for the tool runner in - its state `memberships` (key `.node-tools`, ADR 0571 §10) carries no `waits` key, since the field is gone - (§4); every other part of it is unchanged. +6. **The tool runner's runtime hears every event subject.** In `internal/buscompose/nats.go`, where a runtime's + permissions are composed from the modules it carries, the call of `waitGrants` is replaced by: when one of the + carried modules says `tool-runner` (`Declared.ToolRunner`), `buscompose.EveryEvent` (§1) is appended to the + runtime account's subscribe list. Nothing else is added for it: no publish, no durable consumer, no queue, no other + subject; a runtime carrying no tool runner gets neither subject (ADR 0670 §1, §3; ADR 0043 §2, §3, §5, §8 as ADR + 0670 §3 now reads them). The grants part writes them into the bus's user list as every grant (ADR 0571 §9); they + are read with `mesh-grants.grants_user_list`, and composed at once with `mesh-grants.grants_compose_now`. +7. **The tool runner's membership names no events.** The grants part's first composition after its delivery rewrites + every tool runner's membership in its state `memberships` (key `.node-tools`, ADR 0571 §10) without the key + `waits`, since the field is gone (§4); every other part of it is unchanged. It is read with the grants part's + reading of memberships and rewritten at once with `mesh-grants.grants_compose_now`. 8. **A composition test holds the grant.** In `internal/buscompose/buscompose_test.go`, one table-driven test composes a runtime carrying the tool runner and one other module, and a runtime carrying the other module alone, and holds: the first's subscribe list contains `mesh.seat.*.event.>` and `mesh.mod.*.event.>`; no subscribe subject of either - is wider than those (none of `>`, `mesh.>`, `mesh.*.>`, `mesh.seat.>`, `mesh.mod.>`, nor any `mesh.stream.` or - `$JS.` subject beyond what the carried modules' own consumes give); the first's publish list holds no subject of - another module's or seat's events; the first derives no durable consumer beyond its carried modules' `consumes`; - and the second has neither subject (ADR 0670 §1, §3). + is wider than those (none of `>`, `mesh.>`, `mesh.*.>`, `mesh.seat.>`, `mesh.mod.>`, nor any `$JS.` subject beyond + what the carried modules' own consumes give); the first's publish list holds no subject of another module's or + seat's events; the first derives no durable consumer beyond its carried modules' `consumes`; and the second has + neither subject (ADR 0670 §1, §3). 9. **The real bus holds it too.** The grants part's existing real-bus test (`cmd/mesh-grants/composed_integration_test.go`) - gains three cases, no new test: the tool runner's account, as composed, subscribes + gains cases, no new test: the tool runner's account, as composed, subscribes `mesh.mod.mesh-delivery-view.event.delivered` and receives an event published there; it is refused a publish on - that subject; and another module's account is refused a subscription on an event it does not consume (ADR 0670 §1, - §3). + that subject; it is refused a subscription on `mesh.seat.channel.accept.show.` and on + `mesh.seat.intake.proof.code.` (an accept and a proof are never events, ADR 0670 §1); and another module's + account is refused a subscription on an event it does not consume (ADR 0670 §1, §3). ## The manifest validator, in the indexer @@ -100,83 +113,152 @@ test too, is rebased onto it. (`judge/rules_test.go`, the `waits` cases) are deleted, and one row is added: a manifest that carries `waits` is refused by the manifest validator as the checker runs it (`Shelf`), naming the unknown field `waits`, and set aside to wait as the indexer runs it (`ShelfWaiting`), as any field it does not know (ADR 0578 §1); the tool runner's - manifest without it is accepted - (ADR 0670 §2). + manifest without it is accepted (ADR 0670 §2). Only revisions taken in after the indexer's delivery are judged so: + a manifest the validator accepted before keeps its verdict (ADR 0578 §1). ## The tool runner (module `node-tools`) 11. **Its manifest loses `waits`.** `node-tools/module.json` drops the key `waits` and its five entries; nothing else in it changes, its `version` stays `2` (ADR 0670 §2). -12. **`mesh_wait` takes any event name.** `console.Surface.waitAnswer` (`internal/console/wait.go`) no longer reads the - membership: each name of `subjects` becomes its subjects through `buscompose.EventSubjects` (§1), a refused name is - answered as a failure quoting that error, and a call naming more than 16 names is refused, saying the bound (each - name is four subscriptions for the call; 16 names is 64, far above what a watcher names, and a call is never a - subscription to the whole bus). The same subject named twice is subscribed once. The map from subject to name goes: - the heard event is named by `buscompose.EventName` of the subject it arrived on. The timeout, the filter and the - timed-out answer are unchanged (ADR 0367 §1). The refusal of names outside a list, and its words naming the - manifest, go (ADR 0670 §2). -13. **The answer names the heard event.** A heard event is answered as today, `heard` with `subject`, `body` and +12. **The publication time is kept.** `bus.Heard` gains `Published`, read from the event's header `x-time`, which + mesh-sdk's emit sets (`go/process/emit.go`) and the status view's own emit too; `HearFirst` reads it from the + message's headers, and leaves it empty for an event that carries none. +13. **`mesh_wait` takes any event name.** `console.Surface.waitAnswer` (`internal/console/wait.go`) no longer reads the + membership. Each name of `subjects` becomes its subjects through `buscompose.EventSubjects` (§1); a refused name is + answered as a failure quoting that error. An optional argument `from`, `seat` or `module`, narrows every name to + that namespace, for an emitter whose name is both a seat's and a module's. A call naming more than 16 names is + refused, saying the bound (each name is four subscriptions for the call; 16 names is 64, far above what a watcher + names). The same subject named twice is subscribed once. The map from subject to name goes: the heard event is + named by `buscompose.EventName` of the subject it arrived on. The timeout, the filter and the timed-out answer are + unchanged (ADR 0367 §1). The refusal of names outside a list, and its words naming the manifest, go (ADR 0670 §2). +14. **A misspelt name is refused, not waited on for ever.** Before it subscribes, `waitAnswer` refuses a name whose + emitter is neither a seat of the seat list the tool runner reads through the seat `mesh-catalog` nor a module of + the indexer's manifests it holds as a first view (`mesh-delivery-indexer.views.manifests`), naming both sources. + A name whose emitter is known but whose event no seat definition and no manifest's `emits` declares is waited on, + and its timed-out answer says that no declaration names that event. While either source is not yet read (the + first views' state, `mesh_first_views`), names are not checked, and the answer says `unchecked` with why. +15. **The answer names the heard event.** A heard event is answered as today, `heard` with `subject`, `body` and `at`, where `subject` is now the heard event's whole name (`mesh-delivery-view.delivered`, - `operator-channel.decided.claude-code`), and a new field `from`, `seat` or `module`, says which namespace it arrived - on. The `read` line stays (an event only wakes you, ADR 0367 §4); for a name beginning `operator-channel.decided` - it says instead that the operator's answer is read only with `claude_code_answer`, never from the event's body - (ADR 0670 §5; ADR 0332 §5, §6). -14. **A refused subscription is said, and why.** `bus.Conn.HearFirst` is unchanged: it says a subscription the bus - refuses at once, naming the subject. `waitAnswer` answers that failure adding that the grants part grants this - machine's tool runner every event subject (ADR 0670 §1), so a refusal means its account's grant is not composed - yet, and that the state is read from its owner meanwhile. -15. **The tool's description says what it hears.** `waitTool`'s description of `subjects` says: one or more event + `operator-channel.decided.claude-code`), with two new fields: `from`, `seat` or `module`, the namespace it + arrived on, and `published`, the event's own time (§12), absent when it carries none. The `read` line stays (an + event only wakes you, ADR 0367 §4); for a name beginning `operator-channel.decided` it says instead that the + operator's answer is read only with `claude_code_answer`, never from the event's body (ADR 0670 §5; ADR 0332 §5, + §6). +16. **A refused subscription is said, and a wait is never a hot loop.** `HearFirst` subscribes every subject of the + call; a subscription the bus refuses is noted, and the others go on waiting. When every subscription of the call is + refused, it waits out the call's timeout (or the caller's going) and then answers the refusal, so a watcher that + loops on it asks once a timeout, never at once again. Its refusal words become: "the bus refused this account a + subscription on : the grants part grants this machine's tool runner every event subject (novox/hq ADR + 0670 §1), so its grant is not composed yet; read the state from its owner meanwhile"; no words name a wait grant or + ADR 0367 §2. A heard event's answer and a timed-out answer list under `refused` any subject the bus refused while + others stood. +17. **The tool's description says what it hears.** `waitTool`'s description of `subjects` says: one or more event names, `.`, such as `mesh-delivery-view.delivered`; an event whose subject goes on after - the name (an asker, a kind, a module) is heard by the name too; the filter narrows by the body's fields. Its own - description keeps "an event only wakes you" and adds that an operator's answer is read only with - `claude_code_answer`. The words "only those the mesh granted this machine's tool runner" go. -16. **The hearing serves `mesh_wait` alone.** `HearFirst` has one caller, `waitAnswer`; a carried module's code still - reaches the bus only as its own membership names it, as the runtime keeps it today (ADR 0670 §1). How it is - checked: the code review reads `HearFirst`'s callers. -17. **What goes from the tool runner's code.** The type `bus.WaitIssued` (`internal/bus/wait.go`) and `Membership.Waits` - (`internal/bus/bus.go`) go. `console/manifest_waits_test.go` keeps its test of what the tool runner consumes and - its test of the runner's own secrets; its test of the five families becomes a test that the manifest has no key - `waits`, and its comment cites ADR 0670 §2. `console/wait_test.go`'s `waitingRuntime` issues no waits, and its - tests wait on `mesh-delivery-view.delivered` and `operator-channel.decided.claude-code` with no membership naming - either; the test that a name outside the list is refused becomes a test that a name with `*`, `>` or one token is - refused (ADR 0670 §2). No test is added: the module stays within its budget. + the name (an asker, a kind, a module) is heard by the name too; the filter narrows by the body's fields; `from` + narrows to a seat's or a module's. Its own description keeps "an event only wakes you" and adds that an operator's + answer is read only with `claude_code_answer`. The words "only those the mesh granted this machine's tool runner" + go. +18. **The hearing serves `mesh_wait` alone.** `HearFirst` has one caller, `waitAnswer`. The modules the runtime carries + share its account, so the bus no longer bounds which events their code could hear; the runtime still gives a + carried module's code only what its membership names, and this is accepted under ADR 0670 §1, every session + already hearing every event. How it is checked: the code review of every change to the tool runner reads + `HearFirst`'s callers. +19. **What goes from the tool runner's code, and its tests.** The type `bus.WaitIssued` (`internal/bus/wait.go`) and + `Membership.Waits` (`internal/bus/bus.go`) go. In `console/manifest_waits_test.go` the test of the five families + becomes a test that the manifest has no key `waits`, its comment citing ADR 0670 §2; its tests of what the tool + runner consumes and of the runner's own secrets stay. In `console/wait_test.go`: `waitingRuntime` issues no waits, + and the answering test waits on `mesh-delivery-view.delivered` and on `operator-channel.decided` with no membership + naming either, its rows holding `subject`, `from`, `published` and `read` (a `decided.` event heard by + `operator-channel.decided`); `TestMeshWaitRefusesAnEventOutsideTheGrantedFamilies` becomes the table of refusals: + a name with `*`, with `>`, of one token, 17 names, an emitter no source knows (§14), and a bus-refused subscription + answered after the timeout with §16's words; `TestMeshWaitWithNoFamiliesIssuedWaitsOnNothing` is deleted. In + `bus/wait_test.go`, `TestTheBusRefusesAWaitOutsideTheGrant` stays as §16's refusal test, with a timeout of a + fraction of a second, its comment citing ADR 0670 §1. No test function is added: the module stays within its + budget. + +## The claude-code module's Stop hook + +20. **The hook no longer doubts the grant.** In mesh-catalog `modules/claude-code/cmd/claude-code/stop.go`, + `watcherAdvice` says for a wait with an event: "mesh_wait on and reread after every answer, or start + a watcher that does", with no condition on the tool runner's grant; its comment cites ADR 0670 §2 beside ADR 0489 + §3. Its row in `stop_test.go` holds the new words. ## Events carry no secret -18. **The prepare lists every event.** The milestone file's prepare (playbook 07, step 0) gains a section *Events and +21. **The prepare lists every event.** The milestone file's prepare (playbook 07, step 0) gains a section *Events and their fields*: every event each module's manifest `emits` and each seat's definition declares, read from the - indexer's modules (`mesh-delivery-view.indexer_modules`) and the seat list (the seat `mesh-catalog`'s definitions), - each with the fields of its body as the emitting code builds it, by file and line (ADR 0670 §4). -19. **The code review confirms none carries a secret, before the grant is delivered.** The milestone's code review - (playbook 07, step 2) reads that list and confirms that no event's body carries a credential, a token that grants - an act, code, or a message marked secret; an intake `choice`'s one-use token is not such a token (ADR 0259 §3, §5). - An event found carrying one holds the milestone's delivery and goes to the operator as a design question - (ADR 0670 §4). -20. **From then on every change is asked the same.** Playbook 07's review step gains the question, for any change - that adds an event or a field to one: does it carry a secret? Written in this milestone's hq pull request + indexer's modules (`mesh-delivery-view.indexer_modules`) and the seat definitions (`mesh-catalog.seat-definitions`), + each a holder's event with its subject form; and every event mesh-sdk publishes on a module's behalf (the + announcement's `states` on `mesh.mod..event.states`, found by the callers of its subject builders); each + with the fields of its body as the emitting code builds it, by file and line (ADR 0670 §4). +22. **The code review confirms none carries a secret, before the grant is delivered.** The milestone's code review + (playbook 07, step 2) reads that list again at the group's heads, and confirms that no event's body carries a + credential, a token that grants an act, code, or a message marked secret; an intake `choice`'s one-use token is not + such a token (ADR 0259 §3, §5). A subject the bus holds that the list does not name is a finding of that review; how the subjects a stream holds + are read is open with the operator (no tool answers them today). + An event found carrying a secret holds the milestone's delivery and goes to the operator as a design question (ADR 0670 §4). +23. **The stream events are read too.** *Pending the operator's answer (stream events).* §21's list also names every + event kept in a module's own stream (`mesh.stream..event.`, ADR 0363 §2), the seat holders' events + published there among them, and whether its stream is restricted (ADR 0376 §1). +24. **From then on every change is asked the same.** In this milestone's hq pull request: playbook 07's review step + gains the question, for any change that adds an event or a field to one, "does it carry a secret?"; and the watcher + lines that describe the list (`.claude/agents/mesh-watcher.md`, playbook 07's *Following and reporting*: "the + families a watcher may wait on are those the mesh granted the tool runner") say instead that `mesh_wait` hears any + event by name (ADR 0670 §2, §4). + +## Stream events + +*Pending the operator's answer (stream events): every line of this section.* + +25. **The tool runner also hears the module streams, but no restricted one.** `buscompose.EveryEvent` (§1) gains a + third subject, `mesh.stream.*.event.>`; and the grants part's composition (§6) adds to the tool runner's runtime + account a subscribe deny of every restricted stream's subjects (`ModuleStream.Restricted`, ADR 0363 §4), which the + bus's deny outranks its allow for. `Permissions` gains `SubscribeDeny`, written into the user list as the server's + subscribe deny, as `PublishDeny` is for publishing (ADR 0376 §1 unchanged: the tool runner never hears a restricted + stream). +26. **A name hears its stream form.** `EventSubjects` (§1) answers six subjects, adding + `mesh.stream..event.` and the same followed by `.>`; `from` (§13) takes `stream` too; `EventName` + answers a stream subject with namespace `stream`. A seat's holder event kept in its holder's stream is named by its + holder module (`mesh-delivery-checker.verdict`). The bound of §13 stays 16 names (96 subscriptions). +27. **The tests hold it.** §8's test also holds the third subject and the deny of a fixture's restricted stream; §9's + real-bus test also holds that the tool runner's account hears a module stream's event and is refused a + subscription on a restricted stream's subject. ## Delivery -21. **One delivery group, no order named between its members.** The three repositories are pushed and checked as - one group. No order is named between them, and no transitional field is kept (the operator, 2026-11-03: a change - is designed as it should be, its delivery is the delivery system's): a part built with the new mesh-sdk reads a - stored manifest of the tool runner that still carries `waits` without it (ADR 0578 §3), and a node-engine is given a want - only for a manifest whose words it reads (ADR 0614 §4). Between the grants part's delivery and the tool runner's on a - node, `mesh_wait` there answers the bus's refusal (§14), or an older tool runner's "issued no event families": - both loudly, and a watcher reads its owner after every answer (ADR 0367 §4). +28. **One delivery group, no order named between its members.** The members are pushed and checked as one group. No + order is named between them, and no transitional field is kept (the operator, 2026-11-03: a change is designed as + it should be, its delivery is the delivery system's): a part built with the new mesh-sdk reads a stored manifest of + the tool runner that still carries `waits` without it (ADR 0578 §3), and a node-engine is given a want only for a + manifest whose words it reads (ADR 0614 §4). +29. **What `mesh_wait` answers between the deliveries.** On a node whose tool runner is new and whose grant is not yet + composed, the subjects the old grant names are still heard and the rest are listed under `refused` (§16); on a + node whose tool runner is older and whose membership the new grants part rewrote, the old runner answers at once + that it was issued no event families. Both are loud. A watcher reads its owner after every answer (ADR 0367 §4) and + ends at a call failed twice (its definition), so neither becomes a loop; a laptop asleep through the window gets + its new tool runner when it wakes, and its watchers read their owners meanwhile in the same way. +30. **Going back is going forward.** A fault met after the delivery is fixed by a new change delivered the same way. + Reverting the group instead reverts every member, the indexer first, since the manifest validator delivered here + sets aside the old tool runner's manifest carrying `waits` (§10); until the revert reaches a node, `mesh_wait` + there answers as §29 says. ## Acceptance -- **A watcher wakes on an event no manifest names** (the operator's acceptance): on the live mesh, a watcher's - `mesh_wait` on `mesh-delivery-view.delivered` answers the status view's `delivered` within a second of its - publication, with no rereading on a timer; the tool runner (`node-tools`) is delivered to every node, and the grants part and the - indexer to theirs, read with `mesh-delivery.show` and each node's `node-engine.composition` (§5, §12). -- **The grant is exactly two subjects**: the composition test of §8 and the real-bus cases of §9 pass. -- **The list is gone**: the manifest validator, as the checker runs it, refuses a manifest carrying `waits`, the +- **A watcher wakes on an event no manifest names** (the operator's acceptance; live, read by tool call): at the first + delivery after every node holds the new tool runner, a watcher's `mesh_wait` on `mesh-delivery-view.delivered` + answers the status view's `delivered`, its `at` less than a second after its `published`, with no rereading on a + timer; the tool runner (`node-tools`) is delivered to every node, and the grants part and the indexer to theirs, + read with `mesh-delivery.show` and each node's `node-engine.composition` (§6, §13, §15). +- **The grant is exactly what the record names** (unit, and integration on a real bus): the composition test of §8 + and the real-bus cases of §9 pass. +- **The list is gone** (unit): the manifest validator as the checker runs it refuses a manifest carrying `waits`, the indexer sets it aside, and the tool runner's manifest is accepted without it (§2, §10, §11). -- **No event carries a secret**: the milestone file's *Events and their fields* is complete and the code review's - confirmation is written in it before the grants part is delivered (§18, §19). -- **An answer is never taken from an event**: in the three repositories nothing reads an operator's answer from a - `decided` event's body, which the code review confirms, and `mesh_wait`'s answer for one says to read it with - `claude_code_answer` (§13). +- **`mesh_wait` refuses loudly and never loops** (unit): §19's table of refusals passes. +- **No event carries a secret** (the code review): the milestone file's *Events and their fields* is complete and the + code review's confirmation at the group's heads is written in it before the grants part is delivered (§21, §22). +- **An answer is never taken from an event** (the code review, and unit): in the members nothing reads an operator's + answer from a `decided` event's body, and `mesh_wait`'s answer for one says to read it with `claude_code_answer` + (§15). +- **A stream event wakes a watcher** (live, read by tool call). *Pending the operator's answer (stream events).* A + watcher's `mesh_wait` on `mesh-delivery-checker.verdict` wakes on the checker's next verdict (§25, §26). diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index 61f897fa..fb60566f 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -1,7 +1,7 @@ --- layer: to-be status: in-progress -code: [mesh-tools, mesh-host, mesh-controller, mesh-sdk, mesh-delivery] +code: [mesh-tools, mesh-host, mesh-controller, mesh-sdk, mesh-delivery, mesh-catalog modules/claude-code] updated: 2026-11-11 decisions: - "ADR 0670" @@ -9,6 +9,9 @@ decisions: - "ADR 0259" - "ADR 0332" - "ADR 0571" + - "ADR 0363" + - "ADR 0376" + - "ADR 0489" - "ADR 0578" - "ADR 0614" - "ADR 0197" @@ -148,7 +151,15 @@ of every seat and every module, and on nothing else of theirs: no accept, proof, publish. `mesh_wait` subscribes when called and closes the subscription when it answers, so the account keeps no durable consumer and no queue of its own (kept from ADR 0367 §2), and any event name is accepted: a new event is heard from its first publication, with no change to a manifest. The tool runner's manifest names no events, the -manifest validator has no rule about them, and no membership lists them. +manifest validator has no rule about them, and no membership lists them. A name whose emitter no seat and no module +bears is refused, so a misspelt wait does not run for ever. + +**Who hears.** Whoever may call the mesh MCP server: any process on the machine, through its loopback endpoint (§2), +which is the session there. The tool runner's standing grant of every event is accepted under ADR 0670 §1: what a +session hears through it, it may hear. + +*Pending the operator's answer (stream events):* the tool runner also hears the events kept in a module's own stream +(ADR 0363 §2), the seat holders' events published there among them, but never a restricted stream (ADR 0376 §1). **What that makes audible, and what still bounds it.** Every session now hears the operator channel's `decided` of every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, no token @@ -217,7 +228,7 @@ matched it by URL. | the mesh MCP server | §1 to §5: the module, its loopback endpoint, its discovery (ADR 0152) | delivered 2026-09-30 (above) | | found by address | §3a: five announced tools and addresses (ADR 0195, ADR 0197) | delivered; [as-is 13](../00-as-is/13-the-console.md) | | waiting on the bus | §3b's `mesh_wait` on a list of five families in the tool runner's manifest (ADR 0367) | delivered (issue 745); [as-is 13](../00-as-is/13-the-console.md) | -| every-event | §3b as ADR 0670 changes it: the tool runner hears every event, and the list goes; one delivery group of mesh-sdk, mesh-delivery and mesh-tools | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) | +| every-event | §3b as ADR 0670 changes it: the tool runner hears every event, and the list goes; one delivery group of mesh-sdk, mesh-delivery, mesh-tools and mesh-catalog, with hq | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) | ## What this does not settle -- 2.54.0 From d758964ca9caf7e41863194b21d09feae0760cd4 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 01:26:31 +0100 Subject: [PATCH 03/10] Design 34 every-event: the review reads a stream's subjects with nats_streams No tool answered which subjects a stream holds, so the secret review could not find an event the list missed; a module's tools read everything it holds, so the nats module's tool gains that read, built first as a small fix. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- .../01-to-be/34-milestones/every-event.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/03-DESIGN/01-to-be/34-milestones/every-event.md b/03-DESIGN/01-to-be/34-milestones/every-event.md index b1544a18..544d9562 100644 --- a/03-DESIGN/01-to-be/34-milestones/every-event.md +++ b/03-DESIGN/01-to-be/34-milestones/every-event.md @@ -3,7 +3,7 @@ layer: to-be design: 34 milestone: every-event updated: 2026-11-11 -code: [mesh-sdk, mesh-delivery, mesh-tools, mesh-catalog modules/claude-code] +code: [mesh-sdk, mesh-delivery, mesh-tools, mesh-catalog modules/claude-code, mesh-catalog modules/nats] decisions: - "ADR 0670" - "ADR 0367" @@ -194,8 +194,8 @@ line depends on them. 22. **The code review confirms none carries a secret, before the grant is delivered.** The milestone's code review (playbook 07, step 2) reads that list again at the group's heads, and confirms that no event's body carries a credential, a token that grants an act, code, or a message marked secret; an intake `choice`'s one-use token is not - such a token (ADR 0259 §3, §5). A subject the bus holds that the list does not name is a finding of that review; how the subjects a stream holds - are read is open with the operator (no tool answers them today). + such a token (ADR 0259 §3, §5). A subject the bus holds that the list does not name is a finding of that review: the review reads the subjects + the EVENTS stream (and, under §23, each module stream) holds with `nats.nats_streams` as §31 extends it. An event found carrying a secret holds the milestone's delivery and goes to the operator as a design question (ADR 0670 §4). 23. **The stream events are read too.** *Pending the operator's answer (stream events).* §21's list also names every @@ -243,6 +243,18 @@ line depends on them. sets aside the old tool runner's manifest carrying `waits` (§10); until the revert reaches a node, `mesh_wait` there answers as §29 says. +## Built first, ahead of the group + +31. **`nats.nats_streams` reads the subjects a stream holds.** The nats module's tool `nats_streams` (mesh-catalog + `modules/nats`) takes, with `stream`, an optional `subjects`: a subject filter (`>` for all), read through + JetStream's stream information with that subject filter, answering each subject the stream holds with its message + count, sorted by subject; and an optional `bound` in bytes, 16,384 when absent and 262,144 at most, past which the + answer is cut and says so with `cut` and how many subjects it does not show. Without `subjects` it answers as + today. A module's tools read everything it holds (the operator's instruction that tools are how a module is spoken + to, 2026-10-25). It is built first, ahead of this group, as a small fix in mesh-catalog (playbook 08) under issue + 595, with one table row in the module's existing tool test for the filter and one for the cut; §22's review reads + the bus with it. + ## Acceptance - **A watcher wakes on an event no manifest names** (the operator's acceptance; live, read by tool call): at the first -- 2.54.0 From aec610a6f4c7077639ce125c20a0591de86f36e2 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 02:24:21 +0100 Subject: [PATCH 04/10] Design 34 every-event: hear every module stream, and gate a marked wait on the operator's warrant The operator chose every module stream, mail too (ADR 0671), so the stream lines hold and the restricted-stream deny goes. The operator also asked that a wait respect the approval mark; that extends ADR 0286 to events and has the tool runner's gate hold it, so those lines are written pending the record. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- 00-META/glossary.md | 5 +- .../01-to-be/34-milestones/every-event.md | 117 ++++++++++++++---- 03-DESIGN/01-to-be/34-the-console.md | 38 ++++-- 3 files changed, 127 insertions(+), 33 deletions(-) diff --git a/00-META/glossary.md b/00-META/glossary.md index 2b846b32..24485260 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -1117,7 +1117,10 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03 - **approval mark** — a tool's or verb's declaration in its manifest (`needs-approval`) that each call to it runs only on the operator's warrant for that one call, with its exact arguments; the tool runner that serves it, or the controller for its own verbs, asks and checks - (ADR 0286). + (ADR 0286). It covers a module's events and streams too, once the record extending it is accepted (the operator, + 2026-11-11; design 34 every-event §32): a module may mark one of its events, a restricted stream is marked whole, + and a `mesh_wait` on a marked event runs only on the operator's warrant for that one wait, with the exact names + it waits on and its bound, which the tool runner asks and checks before it subscribes. - **operator message / input** — what arrives on `intake`, once the router has checked the sender against its own list of the operator's identities: a **trusted** one is an operator message, addressed to an agent by `@name` or thread or else to the **responder** (`@mesh`, the router's own diff --git a/03-DESIGN/01-to-be/34-milestones/every-event.md b/03-DESIGN/01-to-be/34-milestones/every-event.md index 544d9562..3f4c8ae7 100644 --- a/03-DESIGN/01-to-be/34-milestones/every-event.md +++ b/03-DESIGN/01-to-be/34-milestones/every-event.md @@ -6,6 +6,8 @@ updated: 2026-11-11 code: [mesh-sdk, mesh-delivery, mesh-tools, mesh-catalog modules/claude-code, mesh-catalog modules/nats] decisions: - "ADR 0670" + - "ADR 0671" + - "ADR 0286" - "ADR 0367" - "ADR 0043" - "ADR 0259" @@ -21,9 +23,9 @@ decisions: # Design 34, milestone every-event — the tool runner hears every event **What it builds.** A session's `mesh_wait` waits on any event of any seat or module, by its name, with no list to -keep: the tool runner's bus account is granted the event subjects of the whole mesh, and the list of families in the -tool runner's manifest, the manifest validator's rule about it and the grants part's computation of subjects from it -go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); this +keep: the tool runner's bus account is granted the event subjects of the whole mesh, every module's own stream +included (ADR 0671 §1), and the list of families in the tool runner's manifest, the manifest validator's rule about it +and the grants part's computation of subjects from it go (ADR 0670). The whole shape is in the design's main file, [§3b](../34-the-console.md#3b-waiting-on-the-bus); this part holds every line the milestone builds, cited as "design 34 every-event §n". **Where.** Four repositories and hq, one delivery group: mesh-sdk (the shared event subjects; the field, type and @@ -34,10 +36,12 @@ for the playbook and agent lines (§24). Only these reference the list today: no part's account composition and the tool runner's manifest and manifest test too; its lead rebases it onto this milestone's heads when G's develop restarts. -**Pending lines.** The lines marked *Pending the operator's answer (stream events)* are written for the recommended -option of a question open with the operator: whether the tool runner also hears the events kept in a module's own -stream (ADR 0363 §2). They are kept, with the mark removed, if the operator chooses it, and deleted if not; no other -line depends on them. +**Pending lines.** The lines of *A marked event waits for the operator's warrant* (§32 to §37) write the operator's +answer of 2026-11-11 that a wait respects the approval mark, and are marked *Pending the record that extends the +approval mark*: the mark is ADR 0286's, which marks tools and verbs and has the bus enforce it, and the answer extends +it to events and streams and has the tool runner's own gate enforce it, so a record decides that before these lines +are built (playbook 02, *Answering a design question*). Only §25 leans on them: it grants the tool runner a +restricted stream's subjects, which §32 to §37 gate. ## The event subjects, in mesh-sdk @@ -198,9 +202,12 @@ line depends on them. the EVENTS stream (and, under §23, each module stream) holds with `nats.nats_streams` as §31 extends it. An event found carrying a secret holds the milestone's delivery and goes to the operator as a design question (ADR 0670 §4). -23. **The stream events are read too.** *Pending the operator's answer (stream events).* §21's list also names every - event kept in a module's own stream (`mesh.stream..event.`, ADR 0363 §2), the seat holders' events - published there among them, and whether its stream is restricted (ADR 0376 §1). +23. **The stream events are read too.** §21's list also names every event kept in a module's own stream + (`mesh.stream..event.`, ADR 0363 §2), the seat holders' events published there among them, and + whether its stream is restricted (its declaration carries a `grant`, ADR 0363 §4). The mail stream's events are + named in it as heard with their content: a mail's own text, a sign-in link or a code someone sent by mail among + it, is the operator's mail and not a secret in §22's sense, which is a credential, token or code the mesh itself + issues or keeps (ADR 0671 §3, which narrows ADR 0670 §4). 24. **From then on every change is asked the same.** In this milestone's hq pull request: playbook 07's review step gains the question, for any change that adds an event or a field to one, "does it carry a secret?"; and the watcher lines that describe the list (`.claude/agents/mesh-watcher.md`, playbook 07's *Following and reporting*: "the @@ -209,21 +216,28 @@ line depends on them. ## Stream events -*Pending the operator's answer (stream events): every line of this section.* +*By ADR 0671: the operator chose every module stream, mail too, on 2026-11-11.* -25. **The tool runner also hears the module streams, but no restricted one.** `buscompose.EveryEvent` (§1) gains a - third subject, `mesh.stream.*.event.>`; and the grants part's composition (§6) adds to the tool runner's runtime - account a subscribe deny of every restricted stream's subjects (`ModuleStream.Restricted`, ADR 0363 §4), which the - bus's deny outranks its allow for. `Permissions` gains `SubscribeDeny`, written into the user list as the server's - subscribe deny, as `PublishDeny` is for publishing (ADR 0376 §1 unchanged: the tool runner never hears a restricted - stream). +25. **The tool runner also hears every module stream, a restricted one included.** `buscompose.EveryEvent` (§1) + gains a third subject, `mesh.stream.*.event.>`, after the two of §1, and the grants part's composition (§6) appends + it with them. Nothing else is added for it: no durable consumer, no grant to acknowledge, no membership word, no + publish, and no subscribe deny of a restricted stream (ADR 0671 §1). A restricted stream's grant value (ADR 0363 + §4) decides who consumes it, not who hears it, so changing or withdrawing that value changes nothing in the tool + runner's grant (ADR 0671 §2); a module that consumes a restricted stream is composed as today (ADR 0376 §2 as ADR + 0671 §2 reads it). The bus does not hold the approval mark on a restricted stream; the tool runner's own gate does, + before it subscribes for a wait (§32 to §37; the operator, 2026-11-11). 26. **A name hears its stream form.** `EventSubjects` (§1) answers six subjects, adding `mesh.stream..event.` and the same followed by `.>`; `from` (§13) takes `stream` too; `EventName` answers a stream subject with namespace `stream`. A seat's holder event kept in its holder's stream is named by its holder module (`mesh-delivery-checker.verdict`). The bound of §13 stays 16 names (96 subscriptions). -27. **The tests hold it.** §8's test also holds the third subject and the deny of a fixture's restricted stream; §9's - real-bus test also holds that the tool runner's account hears a module stream's event and is refused a - subscription on a restricted stream's subject. +27. **The tests hold it.** §8's test holds exactly the three subjects for the runtime carrying the tool runner, beside + a fixture module that declares a restricted stream, and that no durable consumer and no subject to acknowledge on + that stream are derived for the tool runner's runtime (ADR 0671 §1). §9's real-bus test gains cases: the tool + runner's account, as composed, receives an event published on a fixture module's stream, and one published on a + fixture restricted stream whose grant value has no entry for the tool runner, and still receives one after that + value is changed and the accounts are composed again; it is refused a durable consumer and an acknowledgement on + that restricted stream; and a module whose `consumes` names the restricted stream with no entry in its grant value + gets no durable consumer and no read grant on it (ADR 0671 §1, §2). ## Delivery @@ -255,6 +269,61 @@ line depends on them. 595, with one table row in the module's existing tool test for the filter and one for the cut; §22's review reads the bus with it. +## A marked event waits for the operator's warrant + +*Pending the record that extends the approval mark: every line of this section. The operator, 2026-11-11, on the +wait of any event: "can it also respect any 'requires approval' config on the topics?"* The tool runner's asking, +judging and claiming for a marked call (ADR 0286 §10 to §42, design 46's milestone 7) are not built yet; which +milestone builds them first is a question open with the operator, and these lines use them as ADR 0286 states them. + +32. **A module marks an event, and a restricted stream is marked whole.** A module's manifest `needs-approval` (ADR + 0286 §1) may also name one of its own events, by the name its `emits` or one of its `streams`' `events` gives it, + with `*` and `**` as there. Every stream a module declares with a `grant` (ADR 0363 §4; `ModuleStream.Restricted`) + is marked whole, every event it carries, with no entry in `needs-approval`; a stream without a `grant` is not, a + work-queue one included. A seat's definition marks no event. The indexer's manifest validator refuses, as the + checker runs it, a manifest whose `needs-approval` names an event that neither its `emits` nor its streams' + `events` carry, naming the event, as ADR 0286 §4 refuses a mark on a tool the module does not serve. +33. **The tool runner reads the marks from the manifests on the bus.** The tool runner derives each module's marked + subjects from the indexer's manifests it already holds as a first view (`mesh-delivery-indexer.views.manifests`, + §14): for a marked event `` of module ``, `mesh.mod..event.` and + `mesh.stream..event.`, each event pattern as the bus spells it (`WirePattern`); for a stream with a + `grant`, every subject of `ModuleStream.Subjects()`. It derives them again on every change of that view, and no + module's name is written in its code. The mark is so read from the judged manifest, never from a bundle (as ADR + 0286 §5 asks of a tool's mark) and never from a membership, which names no event (ADR 0670 §2); the operator + chose the manifests on the bus on 2026-11-11, and §14 already reads them. +34. **Which wait is marked.** A wait is marked when any subject it subscribes (§13, §26, after `from`) overlaps a + marked subject of §33, that is, some subject matches both, a wildcard on either side. mesh-sdk's `events.go` (§1) + gains `Overlaps(a, b)` for it. While the manifests view is not yet read (`mesh_first_views`), a wait with any + subject outside `mesh.seat.` is marked and its refusal says the marks are not yet read, since a mark that cannot + be read runs nothing (ADR 0286 §40); a wait `from: seat` is never marked. +35. **A marked wait runs only on the operator's warrant for that one wait.** The call is an act as ADR 0286 §10 and + §11 make one: the address `mesh_wait`, the node, the module `node-tools`, its build, and every argument given + (`subjects`, `from`, `filter`, `timeout`), so the warrant binds the exact names, the filter and the bound. The tool + runner asks, judges and claims it as ADR 0286 §12, §13, §17 to §33 and §36 to §42 say of a marked tool, asking on + the seat `operator-channel` under its own module's name, `node-tools`. The ask's headline is "Wait on on + ?" (shortened as ADR 0286 §15 says), and its explanation names every event name one per line, the filter, + and the bound as the timeout in minutes, then the act's fingerprint (ADR 0286 §16). On a warrant it accepts, it + subscribes, and the wait's timeout runs from then. One warrant is one wait (ADR 0286 §31): a watcher looping on a + marked event is approved call by call. Its answer carries ADR 0286 §34's line naming the approval, the run is + recorded as ADR 0286 §35 says, and the tool runner's `approvals` (ADR 0286 §25) lists the waits asked beside the + calls. +36. **A refused marked wait is said, and never a hot loop.** A marked wait with no warrant the tool runner accepts + subscribes to nothing. Its first call publishes the ask at once; every such call answers, as an error, ADR 0286 + §37's or §38's words with "this wait" for "the call", only after its timeout or its caller's going, as §16 + answers a refused subscription. Reason: ADR 0286 §14 answers within 5 seconds, which a watcher's loop would turn + into a call every few seconds for the ask's hour; a call made again after the operator's Approve, within 30 + minutes of it, runs. An unmarked wait is heard as §13 to §16 say, with no ask. +37. **The tests hold it.** §19's table of refusals in `console/wait_test.go` gains rows, no test function: a wait on a + fixture module's marked event, and one on a fixture stream with a `grant`, each with no warrant, subscribes + nothing, asks once and answers ADR 0286 §37's words after its timeout; the same wait with a warrant bound to its + exact names, filter and timeout subscribes and answers the event published after it, so the restricted stream's + event is heard only after the warrant; with a warrant for other names, another filter or another timeout it is + refused; the same names `from: seat` ask nothing; and before the manifests view is read, a module's name is + refused as §34 says. They run on the tool runner's test bus (`meshtest`), the warrant written there as the + router's record. `Overlaps` gains rows in §1's table (`mesh.stream.mail.event.mail.>` overlaps + `mesh.stream.mail.event.mail.*.message`; `mesh.mod.mail.event.x` does not overlap `mesh.stream.mail.event.x`), and + §32's refusal a row in the indexer's `judge/rules_test.go`. + ## Acceptance - **A watcher wakes on an event no manifest names** (the operator's acceptance; live, read by tool call): at the first @@ -272,5 +341,9 @@ line depends on them. - **An answer is never taken from an event** (the code review, and unit): in the members nothing reads an operator's answer from a `decided` event's body, and `mesh_wait`'s answer for one says to read it with `claude_code_answer` (§15). -- **A stream event wakes a watcher** (live, read by tool call). *Pending the operator's answer (stream events).* A - watcher's `mesh_wait` on `mesh-delivery-checker.verdict` wakes on the checker's next verdict (§25, §26). +- **A stream event wakes a watcher** (live, read by tool call): a watcher's `mesh_wait` on + `mesh-delivery-checker.verdict` wakes on the checker's next verdict (§25, §26; ADR 0671 §1). +- **The grant does not follow a restricted stream's grant value** (integration on a real bus): §27's cases pass + (ADR 0671 §2). +- **A marked wait runs only on the operator's warrant** (unit, and on the test bus). *Pending the record that extends + the approval mark.* §37's rows pass. diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index fb60566f..af2b7985 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -5,6 +5,8 @@ code: [mesh-tools, mesh-host, mesh-controller, mesh-sdk, mesh-delivery, mesh-cat updated: 2026-11-11 decisions: - "ADR 0670" + - "ADR 0671" + - "ADR 0286" - "ADR 0043" - "ADR 0259" - "ADR 0332" @@ -131,7 +133,7 @@ repairing it. ## 3b. Waiting on the bus -*By ADR 0367, as ADR 0670 changes it.* Beside the five discovery tools of §3a and `mesh_runtimes`, the mesh MCP +*By ADR 0367, as ADR 0670 and ADR 0671 change it.* Beside the five discovery tools of §3a and `mesh_runtimes`, the mesh MCP server offers a seventh, `mesh_wait`: a session names one or more events, an optional `filter` of body fields and a `timeout` (four minutes when absent, ten at most), and is answered the first matching event or told it timed out (ADR 0367 §1). A session calls it in a loop from a background command (ADR 0367 §3), and after every answer or time-out @@ -139,8 +141,10 @@ reads the state once from the verb that owns it: an event only wakes it (ADR 036 ```mermaid flowchart LR - E[a seat's or a module's event
mesh.seat.<seat>.event.… / mesh.mod.<module>.event.…] -->|heard for the call| TR[the tool runner's account] - G[seat mesh-grants] -->|grants the two event subjects| TR + E[a seat's, a module's or a module stream's event
mesh.seat.<seat>.event.… / mesh.mod.<module>.event.… / mesh.stream.<module>.event.…] -->|heard for the call| TR[the tool runner's account] + G[seat mesh-grants] -->|grants the three event subjects| TR + M[the indexer's manifests] -->|the marked events and restricted streams| TR + TR -->|a marked wait only on the operator's warrant| OC[seat operator-channel] TR -->|mesh_wait answers it by name| S[a session] S -->|then reads the state| O[the verb that owns it] ``` @@ -158,13 +162,25 @@ bears is refused, so a misspelt wait does not run for ever. which is the session there. The tool runner's standing grant of every event is accepted under ADR 0670 §1: what a session hears through it, it may hear. -*Pending the operator's answer (stream events):* the tool runner also hears the events kept in a module's own stream -(ADR 0363 §2), the seat holders' events published there among them, but never a restricted stream (ADR 0376 §1). +**Module streams too, mail included** (ADR 0671 §1, §2). The tool runner also hears the events kept in a module's own +stream (ADR 0363 §2), the seat holders' events published there among them, a restricted stream such as mail's +included. A restricted stream's grant value decides who consumes it, never who hears it, so the tool runner's hearing +needs no `consumes` and no entry in that value, and keeps no consumer of it. + +*Pending the record that extends the approval mark (the operator, 2026-11-11):* **a marked event is heard only on +the operator's warrant.** The approval mark (ADR 0286) covers a module's events and streams as well as its tools and +verbs: a module may mark one of its events in `needs-approval`, and a restricted stream is marked whole. The tool +runner reads the marks from the indexer's manifests on the bus, and a `mesh_wait` on a marked event, or on any subject +of a marked stream, runs only on the operator's warrant for that one wait, asked on the seat `operator-channel` with +the exact names it waits on and its bound, and checked before the tool runner subscribes; without one it is refused +and says so. The bus grants the subjects, and the tool runner's own gate holds the mark, as the operator chose. An +unmarked event is heard with no ask. **What that makes audible, and what still bounds it.** Every session now hears the operator channel's `decided` of -every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, no token -that grants an act, no code, no message marked secret, which the code review asks of every change that adds an event -or a field (ADR 0670 §4). And a heard event never answers anything: the operator's answer to an ask is read only +every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, token or +code the mesh itself issues or keeps, and no message marked secret, which the code review asks of every change that +adds an event or a field (ADR 0670 §4 as ADR 0671 §3 narrows it). A mail's own content is the operator's mail and is +heard as it is, a sign-in link or a code someone sent by mail among it (ADR 0671 §3). And a heard event never answers anything: the operator's answer to an ask is read only with `claude_code_answer`, from the router's record and held to the question's digest (ADR 0670 §5, ADR 0332 §5, §6), and an asker acts only on a warrant from its own `decided` subject through its own consumer (ADR 0259 §6). @@ -199,7 +215,9 @@ address gets a refused connection, which is the truthful answer. | the composed filter for a machine carrying the mesh MCP server opens no port for it | ADR 0144 | | on a real bus, `mesh_wait` answers the first matching event within a second of its publication, and after every answer a session reads the owner's state | ADR 0367 §1, §4 | | live: a watcher's `mesh_wait` on the status view's `delivered`, an event no manifest names, wakes on it with no rereading on a timer | ADR 0670 §2, milestone every-event | -| a composition test: the tool runner's runtime account is granted exactly `mesh.seat.*.event.>` and `mesh.mod.*.event.>` beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume | ADR 0670 §1, §3 | +| a composition test: the tool runner's runtime account is granted exactly `mesh.seat.*.event.>`, `mesh.mod.*.event.>` and `mesh.stream.*.event.>` beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume | ADR 0670 §1, §3; ADR 0671 §1 | +| on a real bus, the tool runner's account hears a restricted stream's event with no entry in its grant value, still after the value changes, and is refused a durable consumer and an acknowledgement on it | ADR 0671 §1, §2 | +| *pending the record that extends the approval mark:* the tool runner's gate refuses a marked wait with no warrant and accepts one with a warrant bound to its exact names, filter and timeout; on the test bus a restricted stream's event is heard only after the warrant | the operator, 2026-11-11; ADR 0286 | | the manifest validator, as the checker runs it, refuses a manifest that carries `waits` as an unknown field, and accepts the tool runner's without it | ADR 0670 §2 | | the milestone's prepare lists every event with its body's fields, and its code review confirms none carries a secret before the grant is delivered; every later change adding an event or a field is asked the same in its review | ADR 0670 §4 | | nothing takes an operator's answer from a `decided` event's body; `claude_code_answer` is its one reader | ADR 0670 §5 | @@ -228,7 +246,7 @@ matched it by URL. | the mesh MCP server | §1 to §5: the module, its loopback endpoint, its discovery (ADR 0152) | delivered 2026-09-30 (above) | | found by address | §3a: five announced tools and addresses (ADR 0195, ADR 0197) | delivered; [as-is 13](../00-as-is/13-the-console.md) | | waiting on the bus | §3b's `mesh_wait` on a list of five families in the tool runner's manifest (ADR 0367) | delivered (issue 745); [as-is 13](../00-as-is/13-the-console.md) | -| every-event | §3b as ADR 0670 changes it: the tool runner hears every event, and the list goes; one delivery group of mesh-sdk, mesh-delivery, mesh-tools and mesh-catalog, with hq | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) | +| every-event | §3b as ADR 0670 and ADR 0671 change it: the tool runner hears every event, module streams included, a marked one on the operator's warrant, and the list goes; one delivery group of mesh-sdk, mesh-delivery, mesh-tools and mesh-catalog, with hq | designed; its part is [`34-milestones/every-event.md`](34-milestones/every-event.md) | ## What this does not settle -- 2.54.0 From ca1fbf7867d6787dae077385e9cf1f4fc4a6129c Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 02:35:51 +0100 Subject: [PATCH 05/10] Build the approval mark in milestone G and let a marked wait use it The operator answered "Build the gate in G" and wants no new records: the marked-wait lines now hold by the operator's answers, each departure said in its Why, and design 46's milestone 7 becomes a part a builder reads whole, written to the mesh as built (marks read from the bus, the capability hold, the client's claim in bus state, the hand-act seat), delivered with G and every-event. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ --- 00-META/glossary.md | 9 +- .../01-to-be/34-milestones/every-event.md | 71 +++--- 03-DESIGN/01-to-be/34-the-console.md | 12 +- .../01-to-be/46-milestones/7-approval-mark.md | 226 ++++++++++++++++++ .../46-the-conversation-with-the-operator.md | 54 ++--- 5 files changed, 298 insertions(+), 74 deletions(-) create mode 100644 03-DESIGN/01-to-be/46-milestones/7-approval-mark.md diff --git a/00-META/glossary.md b/00-META/glossary.md index 24485260..b8dd1927 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -1117,10 +1117,11 @@ asking, never the act. **Decided by** ADR 0152, 0175, 0234, 0259, 0272, 0383, 03 - **approval mark** — a tool's or verb's declaration in its manifest (`needs-approval`) that each call to it runs only on the operator's warrant for that one call, with its exact arguments; the tool runner that serves it, or the controller for its own verbs, asks and checks - (ADR 0286). It covers a module's events and streams too, once the record extending it is accepted (the operator, - 2026-11-11; design 34 every-event §32): a module may mark one of its events, a restricted stream is marked whole, - and a `mesh_wait` on a marked event runs only on the operator's warrant for that one wait, with the exact names - it waits on and its bound, which the tool runner asks and checks before it subscribes. + (ADR 0286). It covers a module's events and streams too (the operator, 2026-11-11; design 34 every-event §32): a + module may mark one of its events, a restricted stream is marked whole, and a `mesh_wait` on a marked event runs + only on the operator's warrant for that one wait, with the exact names it waits on and its bound, which the tool + runner asks and checks before it subscribes. The tool runner reads every mark from the manifests and the seat list + on the bus (design 46 7-approval-mark §5). - **operator message / input** — what arrives on `intake`, once the router has checked the sender against its own list of the operator's identities: a **trusted** one is an operator message, addressed to an agent by `@name` or thread or else to the **responder** (`@mesh`, the router's own diff --git a/03-DESIGN/01-to-be/34-milestones/every-event.md b/03-DESIGN/01-to-be/34-milestones/every-event.md index 3f4c8ae7..f3b705ba 100644 --- a/03-DESIGN/01-to-be/34-milestones/every-event.md +++ b/03-DESIGN/01-to-be/34-milestones/every-event.md @@ -36,12 +36,11 @@ for the playbook and agent lines (§24). Only these reference the list today: no part's account composition and the tool runner's manifest and manifest test too; its lead rebases it onto this milestone's heads when G's develop restarts. -**Pending lines.** The lines of *A marked event waits for the operator's warrant* (§32 to §37) write the operator's -answer of 2026-11-11 that a wait respects the approval mark, and are marked *Pending the record that extends the -approval mark*: the mark is ADR 0286's, which marks tools and verbs and has the bus enforce it, and the answer extends -it to events and streams and has the tool runner's own gate enforce it, so a record decides that before these lines -are built (playbook 02, *Answering a design question*). Only §25 leans on them: it grants the tool runner a -restricted stream's subjects, which §32 to §37 gate. +**The approval mark on events.** *A marked event waits for the operator's warrant* (§32 to §37) writes the operator's +answers of 2026-11-11: a wait respects the approval mark, and the gate that asks and checks it (design 46's milestone +7, [`7-approval-mark.md`](../46-milestones/7-approval-mark.md)) is built in design 47's milestone G and delivered in one +group with G and this milestone ("Build the gate in G"). So §25's grant of a restricted stream's subjects and the gate +that holds its mark reach the machines together. ## The event subjects, in mesh-sdk @@ -225,7 +224,9 @@ restricted stream's subjects, which §32 to §37 gate. §4) decides who consumes it, not who hears it, so changing or withdrawing that value changes nothing in the tool runner's grant (ADR 0671 §2); a module that consumes a restricted stream is composed as today (ADR 0376 §2 as ADR 0671 §2 reads it). The bus does not hold the approval mark on a restricted stream; the tool runner's own gate does, - before it subscribes for a wait (§32 to §37; the operator, 2026-11-11). + before it subscribes for a wait (§32 to §37; the operator, 2026-11-11), and that gate is delivered in the same + group as this grant (design 46 7-approval-mark §23). *Why:* ADR 0286 §8 has the bus hold a mark, and the bus + cannot grant a subscription for one approved wait alone, so the operator chose the tool runner's gate. 26. **A name hears its stream form.** `EventSubjects` (§1) answers six subjects, adding `mesh.stream..event.` and the same followed by `.>`; `from` (§13) takes `stream` too; `EventName` answers a stream subject with namespace `stream`. A seat's holder event kept in its holder's stream is named by its @@ -271,10 +272,12 @@ restricted stream's subjects, which §32 to §37 gate. ## A marked event waits for the operator's warrant -*Pending the record that extends the approval mark: every line of this section. The operator, 2026-11-11, on the -wait of any event: "can it also respect any 'requires approval' config on the topics?"* The tool runner's asking, -judging and claiming for a marked call (ADR 0286 §10 to §42, design 46's milestone 7) are not built yet; which -milestone builds them first is a question open with the operator, and these lines use them as ADR 0286 states them. +*By the operator's answers of 2026-11-11: "can it also respect any 'requires approval' config on the topics?", and +"Build the gate in G".* These lines use the gate design 46 7-approval-mark builds for a marked call (its §8 to §19), +delivered in the same group. *Why they depart from the records, said once for the section:* ADR 0286 §1 marks tools +and verbs, and the operator extended the mark to a module's events and streams; ADR 0671 §1 and §3 have every session +hear every module stream, mail included, with no exception, and the operator narrowed that to a wait the operator +approves, for a marked event or a restricted stream. 32. **A module marks an event, and a restricted stream is marked whole.** A module's manifest `needs-approval` (ADR 0286 §1) may also name one of its own events, by the name its `emits` or one of its `streams`' `events` gives it, @@ -282,35 +285,40 @@ milestone builds them first is a question open with the operator, and these line is marked whole, every event it carries, with no entry in `needs-approval`; a stream without a `grant` is not, a work-queue one included. A seat's definition marks no event. The indexer's manifest validator refuses, as the checker runs it, a manifest whose `needs-approval` names an event that neither its `emits` nor its streams' - `events` carry, naming the event, as ADR 0286 §4 refuses a mark on a tool the module does not serve. + `events` carry, naming the event, as ADR 0286 §4 refuses a mark on a tool the module does not serve (design 46 + 7-approval-mark §4's table gains the row). A module that marks only events needs neither `operator-channel` in + `uses` nor `approval-mark/1`: the tool runner asks for the wait, not the module. 33. **The tool runner reads the marks from the manifests on the bus.** The tool runner derives each module's marked subjects from the indexer's manifests it already holds as a first view (`mesh-delivery-indexer.views.manifests`, §14): for a marked event `` of module ``, `mesh.mod..event.` and `mesh.stream..event.`, each event pattern as the bus spells it (`WirePattern`); for a stream with a `grant`, every subject of `ModuleStream.Subjects()`. It derives them again on every change of that view, and no - module's name is written in its code. The mark is so read from the judged manifest, never from a bundle (as ADR - 0286 §5 asks of a tool's mark) and never from a membership, which names no event (ADR 0670 §2); the operator - chose the manifests on the bus on 2026-11-11, and §14 already reads them. + module's name is written in its code; it is design 46 7-approval-mark §5's derivation, extended to events. *Why:* + ADR 0286 §5 had the mark issued in a membership; no controller issues one now, a membership names no event (ADR + 0670 §2), and the judged manifest on the bus keeps the mark out of any bundle as §5 required (the operator, + 2026-11-11: the mark read from the manifests on the bus); §14 already reads this view. 34. **Which wait is marked.** A wait is marked when any subject it subscribes (§13, §26, after `from`) overlaps a marked subject of §33, that is, some subject matches both, a wildcard on either side. mesh-sdk's `events.go` (§1) gains `Overlaps(a, b)` for it. While the manifests view is not yet read (`mesh_first_views`), a wait with any subject outside `mesh.seat.` is marked and its refusal says the marks are not yet read, since a mark that cannot - be read runs nothing (ADR 0286 §40); a wait `from: seat` is never marked. -35. **A marked wait runs only on the operator's warrant for that one wait.** The call is an act as ADR 0286 §10 and - §11 make one: the address `mesh_wait`, the node, the module `node-tools`, its build, and every argument given - (`subjects`, `from`, `filter`, `timeout`), so the warrant binds the exact names, the filter and the bound. The tool - runner asks, judges and claims it as ADR 0286 §12, §13, §17 to §33 and §36 to §42 say of a marked tool, asking on - the seat `operator-channel` under its own module's name, `node-tools`. The ask's headline is "Wait on on - ?" (shortened as ADR 0286 §15 says), and its explanation names every event name one per line, the filter, - and the bound as the timeout in minutes, then the act's fingerprint (ADR 0286 §16). On a warrant it accepts, it - subscribes, and the wait's timeout runs from then. One warrant is one wait (ADR 0286 §31): a watcher looping on a - marked event is approved call by call. Its answer carries ADR 0286 §34's line naming the approval, the run is - recorded as ADR 0286 §35 says, and the tool runner's `approvals` (ADR 0286 §25) lists the waits asked beside the - calls. + be read runs nothing (ADR 0286 §40; design 46 7-approval-mark §19); a wait `from: seat` is never marked. +35. **A marked wait runs only on the operator's warrant for that one wait.** The call is an act as design 46 + 7-approval-mark §8 makes one: the address `mesh_wait`, the node, the module `node-tools`, its build, and every + argument given (`subjects`, `from`, `filter`, `timeout`), so the warrant binds the exact names, the filter and the + bound. The tool runner refuses, asks, judges and claims it as that part's §9, §10, §11 and §13 to §17 do for a + marked tool, asking on the seat `operator-channel` under its own module's name, `node-tools`, whose manifest gains + `operator-channel` in `uses` for it (that part's §7). The ask's headline is "Wait on on ?" + (shortened as that part's §10 says), and its explanation names every event name one per line, the filter, and the + bound as the timeout in minutes, then the act's fingerprint. On a warrant it accepts, it subscribes, and the wait's + timeout runs from then. One warrant is one wait (that part's §15): a watcher looping on a marked event is approved + call by call. Its answer carries the line naming the approval and the warrant is recorded through + `mesh-hand-acts.warranted` (that part's §16), and `mesh_approvals` (that part's §18) lists the waits asked beside + the calls. *Why:* the operator, 2026-11-11: a marked wait runs only on the operator's warrant for that one wait, + shown with the exact names it waits on and its bound. 36. **A refused marked wait is said, and never a hot loop.** A marked wait with no warrant the tool runner accepts - subscribes to nothing. Its first call publishes the ask at once; every such call answers, as an error, ADR 0286 - §37's or §38's words with "this wait" for "the call", only after its timeout or its caller's going, as §16 - answers a refused subscription. Reason: ADR 0286 §14 answers within 5 seconds, which a watcher's loop would turn + subscribes to nothing. Its first call publishes the ask at once; every such call answers, as an error, design 46 + 7-approval-mark §12's or §17's words with "this wait" for "the call", only after its timeout or its caller's going, as §16 + answers a refused subscription. *Why:* ADR 0286 §14 answers within 5 seconds, which a watcher's loop would turn into a call every few seconds for the ask's hour; a call made again after the operator's Approve, within 30 minutes of it, runs. An unmarked wait is heard as §13 to §16 say, with no ask. 37. **The tests hold it.** §19's table of refusals in `console/wait_test.go` gains rows, no test function: a wait on a @@ -345,5 +353,4 @@ milestone builds them first is a question open with the operator, and these line `mesh-delivery-checker.verdict` wakes on the checker's next verdict (§25, §26; ADR 0671 §1). - **The grant does not follow a restricted stream's grant value** (integration on a real bus): §27's cases pass (ADR 0671 §2). -- **A marked wait runs only on the operator's warrant** (unit, and on the test bus). *Pending the record that extends - the approval mark.* §37's rows pass. +- **A marked wait runs only on the operator's warrant** (unit, and on the test bus): §37's rows pass. diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index af2b7985..f62a5522 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -167,14 +167,16 @@ stream (ADR 0363 §2), the seat holders' events published there among them, a re included. A restricted stream's grant value decides who consumes it, never who hears it, so the tool runner's hearing needs no `consumes` and no entry in that value, and keeps no consumer of it. -*Pending the record that extends the approval mark (the operator, 2026-11-11):* **a marked event is heard only on -the operator's warrant.** The approval mark (ADR 0286) covers a module's events and streams as well as its tools and +**A marked event is heard only on the operator's warrant** (the operator, 2026-11-11). The approval mark (ADR 0286) covers a module's events and streams as well as its tools and verbs: a module may mark one of its events in `needs-approval`, and a restricted stream is marked whole. The tool runner reads the marks from the indexer's manifests on the bus, and a `mesh_wait` on a marked event, or on any subject of a marked stream, runs only on the operator's warrant for that one wait, asked on the seat `operator-channel` with the exact names it waits on and its bound, and checked before the tool runner subscribes; without one it is refused -and says so. The bus grants the subjects, and the tool runner's own gate holds the mark, as the operator chose. An -unmarked event is heard with no ask. +and says so. The bus grants the subjects, and the tool runner's own gate holds the mark, as the operator chose; the +gate is design 46's milestone 7, built in design 47's milestone G and delivered in one group with G and milestone +every-event, so the subjects and the gate reach the machines together. This departs from ADR 0286 §1 and §8 (tools and +verbs only, held by the bus) and from ADR 0671 §1 and §3 (every module stream heard with no exception) by the +operator's answer. An unmarked event is heard with no ask. **What that makes audible, and what still bounds it.** Every session now hears the operator channel's `decided` of every asker and the intake bench's events. So no event of a seat or a module carries a secret: no credential, token or @@ -217,7 +219,7 @@ address gets a refused connection, which is the truthful answer. | live: a watcher's `mesh_wait` on the status view's `delivered`, an event no manifest names, wakes on it with no rereading on a timer | ADR 0670 §2, milestone every-event | | a composition test: the tool runner's runtime account is granted exactly `mesh.seat.*.event.>`, `mesh.mod.*.event.>` and `mesh.stream.*.event.>` beside what its carried modules declare, with no publish on another's events, no durable consumer for them and no wider subject; on a real bus it is refused a publish on an event subject, and another module's account a subscription on an event it does not consume | ADR 0670 §1, §3; ADR 0671 §1 | | on a real bus, the tool runner's account hears a restricted stream's event with no entry in its grant value, still after the value changes, and is refused a durable consumer and an acknowledgement on it | ADR 0671 §1, §2 | -| *pending the record that extends the approval mark:* the tool runner's gate refuses a marked wait with no warrant and accepts one with a warrant bound to its exact names, filter and timeout; on the test bus a restricted stream's event is heard only after the warrant | the operator, 2026-11-11; ADR 0286 | +| the tool runner's gate refuses a marked wait with no warrant and accepts one with a warrant bound to its exact names, filter and timeout; on the test bus a restricted stream's event is heard only after the warrant | the operator, 2026-11-11; ADR 0286 | | the manifest validator, as the checker runs it, refuses a manifest that carries `waits` as an unknown field, and accepts the tool runner's without it | ADR 0670 §2 | | the milestone's prepare lists every event with its body's fields, and its code review confirms none carries a secret before the grant is delivered; every later change adding an event or a field is asked the same in its review | ADR 0670 §4 | | nothing takes an operator's answer from a `decided` event's body; `claude_code_answer` is its one reader | ADR 0670 §5 | diff --git a/03-DESIGN/01-to-be/46-milestones/7-approval-mark.md b/03-DESIGN/01-to-be/46-milestones/7-approval-mark.md new file mode 100644 index 00000000..e921e7e7 --- /dev/null +++ b/03-DESIGN/01-to-be/46-milestones/7-approval-mark.md @@ -0,0 +1,226 @@ +--- +layer: to-be +design: 46 +milestone: 7-approval-mark +updated: 2026-11-11 +code: [mesh-sdk, mesh-contracts, mesh-delivery, mesh-tools, mesh-catalog modules/zsh] +decisions: + - "ADR 0286" + - "ADR 0259" + - "ADR 0627" + - "ADR 0666" + - "ADR 0668" + - "ADR 0595" + - "ADR 0597" + - "ADR 0310" + - "ADR 0268" +--- + +# Design 46, milestone 7 — a call that needs the operator's approval + +**What it builds.** A tool or a seat verb marked in its manifest or its seat's definition runs only on the operator's +approval of that one call, with its exact arguments (ADR 0286). The first call asks the operator on the seat +`operator-channel` and runs nothing; the same call made again within 30 minutes of the operator's Approve runs once. +The tool runner (module `node-tools`) asks, judges and claims; the mark is read from the manifests and the seat list on +the bus. The shape is in the design's main file, +[§10, *A call that needs the operator's approval*](../46-the-conversation-with-the-operator.md#a-call-that-needs-the-operators-approval); +this part holds every line the milestone builds, cited as "design 46 7-approval-mark §n". + +**When.** It is built and delivered in design 47's milestone G, in one delivery group with G and with design 34's +milestone every-event, whose marked waits (design 34 every-event §32 to §37) use this gate (the operator, 2026-11-11: +"Build the gate in G"). + +**Where.** mesh-sdk (the manifest field, the seat definition's field, the seat list), mesh-contracts (the login shell's +seat definition), mesh-delivery (the manifest validator in the indexer), mesh-tools (the tool runner's gate, its state, +its tool) and mesh-catalog (the zsh module, which holds the login shell). Nothing is built in a controller: no +controller issues memberships today, bus permissions are the grants part's (seat `mesh-grants`), the hand-act log is +the seat `mesh-hand-acts`, and the asker's logic is operator-channel's client in mesh-contracts (ADR 0627 §1). + +**What changes from ADR 0286 as written.** Each line below says in its *Why* where something built since ADR 0286 +(2026-10-10) changes how a clause is met: the mark is read from the bus and not from a membership (§5), a module is held +off a runner that does not honour it by the capability hold and not by its bus permissions (§6), the digest is Go alone +(§8), the claim is kept in the runner's bus state and not a file (§13), `warranted` is a kept call and not a retry on +a timer (§16), and a fourth open ask is offered as G makes it (§17). The controller's own verbs (ADR 0286 §43) are +not built: no controller serves verbs. + +## The mark + +1. **A module marks its tools and verbs in `needs-approval`.** mesh-sdk's `catalogue.Manifest` gains `NeedsApproval`, + the manifest key `needs-approval`: a list of strings, each a tool the module serves (``) or a seat verb it + serves (`.`), and, under design 34 every-event §32, one of its events. Absent is no mark. *Why:* ADR + 0286 §1, §49 (each marking is a manifest change, reviewed like any other). *How it is checked:* a row in the + decoder's table test in `go/catalogue` decodes the key, and the manifest reads baseline gains `word:needs-approval`. +2. **A seat's definition marks a verb for every holder.** mesh-sdk's `seatdef` reads a verb's `needs-approval`, a + boolean, in a seat definition (`seats//seat.json` in mesh-contracts), and the seat list it reads into + (`seatlist`, read with `mesh-catalog.seat-definitions`) carries it on the verb. *Why:* ADR 0286 §2; the seat list + is where the tool runner already reads the seats it holds verbs of. *How it is checked:* a fixture row in + `seatdef`'s table test, and one in `seatlist`'s. +3. **Marks add up.** A verb is marked when its seat's definition or its holder's `needs-approval` marks it; a holder + cannot unmark what its seat marks. *Why:* ADR 0286 §3. *How it is checked:* a row of §5's table: a seat-marked verb + with no entry in the holder's manifest is marked. +4. **The manifest validator refuses a mark it cannot honour.** The indexer's manifest validator (`judge/rules.go`) + refuses, as the checker runs it, and sets aside to wait, as the indexer runs it, a manifest that: marks a tool its + `tools` does not list, or a verb its `claims` do not serve (ADR 0286 §4); marks a tool or a verb, or holds a seat + verb its seat marks, without `operator-channel` in `uses` (§7) or without `approval-mark/1` in `capabilities` (§6); or + marks a tool or a verb and declares a resource of type `process`, whose tools its own program answers and no tool runner + gates. Each refusal names the entry and the rule. *Why:* a mark nothing enforces would be a call that runs unasked + while saying it is guarded; ADR 0286 §43 had the controller enforce its own verbs, and no other own-process server + has a gate, so such a mark is refused rather than ignored (the design's choice under ADR 0286 §4, §40). *How it is + checked:* one row per refusal in `judge/rules_test.go`, and a row that accepts the zsh module's manifest of §21. + +## Where the tool runner reads it + +5. **From the manifests and the seat list on the bus, never from a bundle.** The tool runner derives, for each module + it serves, the set of its marked tools and verbs from the indexer's manifests it consumes as a first view + (`mesh-delivery-indexer.views.manifests`) and from the seat list it reads (`seatlist`), again on every change of + either, in a package of its own, `node-tools/internal/approval`. No module's name is written in its code. *Why:* + ADR 0286 §5 had the controller issue the mark in a membership so that a bundle could not drop it; no controller + issues memberships now, and the judged manifest on the bus is the same guarantee: the bundle never carries the mark. + *How it is checked:* a table test in `approval` derives the marks from fixture manifests and a fixture seat list, + §3's row among them. +6. **A marked module runs only where the runner honours the mark.** The tool runner states `approval-mark/1` among + its call capabilities (`runtime.CallCapabilities`), which its announcement carries and its node's node-engine takes + into the node's record; a module whose manifest asks for `approval-mark/1` in `capabilities` (§4) is held off a + machine whose runner does not state it, as every capability is (ADR 0595 §9, ADR 0597). *Why:* ADR 0286 §7 and §8 + withheld each marked tool from a runner that did not honour the mark, by leaving its subject out of that runner's + bus permissions; the capability hold, built since, keeps the module from running there at all, and the grants part + grants a module's tools as `mesh.mod..tool.>` whole, so no subject is left out (ADR 0286 §9 has nothing to + name apart). *How it is checked:* the existing capability test of `CallCapabilities` gains the entry; and in §22's + whole test a marked fixture module is not loaded by a runner built without the capability. +7. **The asker's grants are a user's of `operator-channel`.** Because the manifest says `operator-channel` in `uses` + (§4), the grants part composes, on the account of the runtime carrying the module, the publish of + `mesh.seat.operator-channel.accept.ask.` and `.cancel.`, the hearing of its warrants on + `mesh.seat.operator-channel.event.decided.` and the read of the router's record under `.` in the + seat's state `asks`, as for every user (ADR 0259 §3). Nothing new is composed for the mark. *Why:* ADR 0286 §6. + *How it is checked:* a row in the grants part's composition table test for a fixture module with a mark. + +## The act, and the first call + +8. **The act is digested with operator-channel's client.** For a call of a marked tool or verb, the tool runner + builds an `operatorchannelclient.Act` with `address` (as called), `node` (the node that serves it), `module` and + `tool`, or `seat` and `verb`, `build` (the digest of the bundle it serves the module from), and `arg.` for each + argument given, a string as itself and any other value as its JSON in RFC 8785's canonical form; an argument left + out is not in the act. Its digest is `operatorchannelclient.ActDigest`. *Why:* ADR 0286 §10, §11; the digest is in + mesh-contracts since ADR 0627 §1. The TypeScript twin ADR 0286 named is not built: the tool runner is Go, and no + TypeScript asker computes an act. *How it is checked:* table rows in `approval` for the act's fields, an omitted + argument, a number, an object and a changed build each giving another digest. +9. **A call refused without asking.** On a call with no approval for its act, the runner refuses at once, asking + nothing, a call whose arguments hold anything `outward.CheckSecret` refuses (mesh-sdk), or whose arguments are more + than 16 KiB. *Why:* ADR 0286 §12; mesh-sdk's `outward` is the content rule ADR 0310 §4 moved there. *How it is + checked:* two rows of §22's refusal table. +10. **One ask, under the module's name.** Otherwise the runner asks through an `operatorchannelclient.Client` whose + `Module` is the marked module and whose `State` is the runner's state of §13, unless an ask for the same act is + open there (§13's `act.` key), in which case it asks nothing new. The ask: `about` the act's digest; `who` + `operator`; `expires` 60 minutes after; no `on-expiry`; two options, `approve` (label Approve) and `decline` (label + Decline), both at level `approve`, each binding the act's digest; the headline "Run on ?", + "Run ?" when that is longer than the router allows, ending "Part shown" whenever an argument is not shown + whole; the explanation: the first line of the tool's own description, the node, that it was called through the + mesh's tools, which cannot tell who called, every argument one per line as "name: value", and the act's + fingerprint, its digest's first 24 hexadecimal digits in groups of four. *Why:* ADR 0286 §13, §15, §16, §20 to + §23; the client checks the ask (`Check`) before it publishes. *How it is checked:* a test in `approval` holds the + published ask's fields against a fixture call, and a second identical call publishes nothing. +11. **An argument is shown whole only where it may leave the mesh.** Each argument is shown whole where + `outward.Check` passes it, else in its shape with ‹path›, ‹address› or ‹withheld› in place of what it refuses; the + arguments are shown whole when they are at most 2000 bytes and 40 lines, otherwise their first 20 lines and 1200 + bytes, marked "NOT SHOWN IN FULL", and the explanation then says that the whole is read with `mesh_approvals` + (§18) on that node, whose fingerprint must be the one on the phone. *Why:* ADR 0286 §17 to §19. *How it is + checked:* rows of §10's test with a path, an address and a long argument. +12. **The first call answers "Not run".** The runner waits up to 5 seconds for the router's word on the ask (its + record, §14) and answers the call as an error, never as a result: "Not run: this needs the operator's approval. + Asked on at