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
This commit is contained in:
2026-11-11 02:41:11 +01:00
parent d758964ca9
commit aec610a6f4
3 changed files with 127 additions and 33 deletions
+4 -1
View File
@@ -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
+95 -22
View File
@@ -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.<module>.event.<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.<module>.event.<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.<emitter>.event.<rest>` 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 `<event>` of module `<module>`, `mesh.mod.<module>.event.<event>` and
`mesh.stream.<module>.event.<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 <names> on
<node>?" (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.
+28 -10
View File
@@ -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<br/>mesh.seat.&lt;seat&gt;.event.… / mesh.mod.&lt;module&gt;.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<br/>mesh.seat.&lt;seat&gt;.event.… / mesh.mod.&lt;module&gt;.event.… / mesh.stream.&lt;module&gt;.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