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
This commit is contained in:
2026-11-11 02:41:11 +01:00
parent 024627f658
commit c232b1fb81
2 changed files with 196 additions and 103 deletions
+182 -100
View File
@@ -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 `<emitter>.<event>`, optionally followed
by further tokens (`operator-channel.decided.claude-code`); each token matches `[A-Za-z0-9_-]+`. It answers four
subjects: `mesh.seat.<emitter>.event.<rest>`, the same followed by `.>`, `mesh.mod.<emitter>.event.<rest>`, and
the same followed by `.>`, where `<rest>` 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 `<rest>` 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.<emitter>.event.<rest>` or
`mesh.mod.<emitter>.event.<rest>` it answers the name `<emitter>.<rest>` and whether the emitter is a seat; any
other subject is answered as not an event.
`mesh.mod.<emitter>.event.<rest>` it answers the name `<emitter>.<rest>` 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>.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>.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.<kind>` and on
`mesh.seat.intake.proof.code.<kind>` (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 <subjects>: 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, `<seat or module>.<event>`, 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.<asker>` 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 <event> and reread <read> 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.<module>.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.<module>.event.<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.<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.
## 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).
+14 -3
View File
@@ -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