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