Designs 25 and 29 disagreed about the subject space; implementing found it
29 put a module's events and tools in one namespace, 25 kept mesh.events.* and mesh.tools.*. One namespace is right — a module's authority over its own name becomes a single pattern the server enforces — but it needs a kind token, because a stream is a subject filter and mesh.mod.*.> would persist every tool call in the mesh. Tools stay on core NATS for the reason 25 already gives. So: mesh.mod.<module>.event.<name>, .tool.<name>, and seats the same shape.
This commit is contained in:
@@ -56,11 +56,20 @@ mesh.control.enrol an enrolment request (JetStream: CONTR
|
||||
mesh.control.built a build's outcome (JetStream: CONTROL)
|
||||
mesh.node.<node>.declare a declaration for a node (JetStream: NODES, last-per-subject)
|
||||
mesh.build.request work for the build machine (JetStream: BUILDS, work queue)
|
||||
mesh.events.<module>.<event> an event (JetStream: EVENTS)
|
||||
mesh.tools.<module>.<tool> a tool invocation (core request/reply)
|
||||
mesh.mod.<module>.event.<event> an event (JetStream: EVENTS)
|
||||
mesh.mod.<module>.tool.<tool> a tool invocation (core request/reply)
|
||||
mesh.seat.<seat>.accept.<verb> work submitted to a role (JetStream: per-seat work queue)
|
||||
mesh.seat.<seat>.event.<verb> a role's own event (JetStream: EVENTS)
|
||||
mesh.seat.<seat>.tool.<verb> a role's tool (core request/reply)
|
||||
mesh.ask.<node>.<command> the controller's command api (core request/reply)
|
||||
```
|
||||
|
||||
**Revised 2026-09-26** ([design 29](29-what-a-module-declares.md)): a module's events and tools
|
||||
moved from `mesh.events.*` / `mesh.tools.*` into one namespace per module, `mesh.mod.<module>.>`,
|
||||
so a module's authority over its own name is a single subject pattern the server enforces — and
|
||||
each carries a **kind token**, without which an events stream's filter would capture tool calls.
|
||||
Seats are the same shape, one namespace per role.
|
||||
|
||||
Two things this buys over the exchanges: **request/reply is native** — a tool call is one
|
||||
`request` on `mesh.tools.<module>.<tool>` answered by whichever runtime serves it (a queue group per
|
||||
tool, so several nodes may serve one tool); and **a declaration is last-per-subject** — the NODES
|
||||
@@ -93,7 +102,7 @@ Core NATS is at-most-once. Everything the mesh must not lose lives in a JetStrea
|
||||
| CONTROL | `mesh.control.>` except `alive` | work queue, one consumer (the controller), explicit ack | the store-window guarantee ([ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md)): the controller `nak`s with a delay while its store is away and the message is redelivered; nothing is dropped |
|
||||
| NODES | `mesh.node.>` | last per subject | one declaration per node, always the newest |
|
||||
| BUILDS | `mesh.build.>` | work queue, explicit ack | at least once; a builder that dies mid-build has its message redelivered |
|
||||
| EVENTS | `mesh.events.>` | limits (age, size), durable consumer per subscribing module | a subscriber that was down catches up; after `max-deliver` attempts the advisory feeds `mesh.events.dead` (its own small stream) |
|
||||
| EVENTS | `mesh.mod.*.event.>` | limits (age, size), durable consumer per subscribing module | a subscriber that was down catches up; after `max-deliver` attempts the advisory feeds `mesh.events.dead` (its own small stream) |
|
||||
|
||||
Tool calls and heartbeats stay on core NATS: a lost heartbeat is the next heartbeat; a lost tool
|
||||
call is a timeout the caller already handles.
|
||||
|
||||
@@ -34,11 +34,19 @@ the catalogue, and the mesh would have hundreds of copies of a decision it made
|
||||
|
||||
| declared | derived |
|
||||
|---|---|
|
||||
| `emits: order.placed` | publish on `mesh.mod.<module>.order.placed` |
|
||||
| `consumes: billing.order.placed` | durable consumer on `mesh.mod.billing.order.placed` |
|
||||
| `emits: order.placed` | publish on `mesh.mod.<module>.event.order.placed` |
|
||||
| `consumes: billing.order.placed` | durable consumer on `mesh.mod.billing.event.order.placed` |
|
||||
| `serves: status` | queue-group subscription on `mesh.mod.<module>.tool.status` |
|
||||
| seat `telegram-sender`, `accepts: send` | work-queue consumer on `mesh.seat.telegram-sender.send` |
|
||||
| `uses: telegram-sender` | publish on that seat's `accepts` subjects, and nothing else |
|
||||
| seat `telegram-sender`, `accepts: send` | work-queue consumer on `mesh.seat.telegram-sender.accept.send` |
|
||||
| `uses: telegram-sender` | publish on that seat's `accept` subjects, and nothing else |
|
||||
|
||||
**The `event` / `tool` / `accept` token is load-bearing, not decoration.** Revision, found while
|
||||
defining the streams: a stream is defined by a subject filter, so a namespace holding both a
|
||||
module's events and its tool calls cannot be filtered into an events stream without capturing
|
||||
every tool invocation in the mesh — and a tool call must never be persisted
|
||||
([design 25](25-the-bus-on-nats.md) §3 keeps tools on core NATS, where a lost call is a timeout the
|
||||
caller already handles). The kind token is what makes `mesh.mod.*.event.>` a safe filter. The
|
||||
first draft of this table had no token, which reads better and cannot be implemented.
|
||||
|
||||
**The test this must pass: the manifest survives the wire changing.** Reorganise the subject space
|
||||
and every manifest in the catalogue is still correct. That is the property
|
||||
|
||||
Reference in New Issue
Block a user