140 lines
10 KiB
Markdown
140 lines
10 KiB
Markdown
---
|
|
topic: the mesh
|
|
status: accepted
|
|
date: 2026-10-01
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md
|
|
---
|
|
|
|
# 160. The mesh issues an assignment's subjects, and a runtime serves what it is issued
|
|
|
|
## Context
|
|
|
|
A module's code names no subject. It registers tools by name and emits events by name, and design 29
|
|
§1 says the rest: *the module names its event and the mesh decides where it lands*. What was built
|
|
decided it twice. The runtime derives `mesh.mod.<module>.tool.<name>` from the module's name by a rule
|
|
compiled into it; the controller derives the same subject by the same rule compiled into it, and grants
|
|
it. They agree because two binaries carry one convention, which is the failure design 33 §2 names for
|
|
seat protocols: *discovery that reads a binary disagrees with the mesh the moment the two are on
|
|
different versions*. [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)
|
|
extended the convention this morning — a second subject per tool with the machine as its last token, a
|
|
seat's verbs served from the credential's claims — and extending it made the shape plain: every such
|
|
change is written in the runtime and in the controller, and a module whose instances must not be
|
|
confused is told apart by a rule in a binary rather than by the mesh that assigned it.
|
|
|
|
The operator put it in one sentence: the mesh knows the subjects, the modules do not; a module should
|
|
ask what to listen on. This record decides exactly that.
|
|
|
|
## Considered Options
|
|
|
|
1. **Keep the convention, keep it in two places.** Cheap until the next change; every change is two
|
|
changes, and the mesh cannot vary a subject for one assignment without a rule for all.
|
|
2. **Keep the convention in one place by putting it in the SDK alone**, and have the controller call
|
|
the SDK's rule. The controller is Go and the SDK is TypeScript; one of them would still carry a copy.
|
|
3. **The mesh issues the subjects.** For every assignment the controller composes a membership: what
|
|
this instance serves, where, in which queue if any; the seat verbs it holds; where its events land;
|
|
what it may reach and at which subjects. It publishes it to a subject only that assignment may read,
|
|
kept last-per-subject so a runtime that connects late reads the current one. The runtime serves
|
|
exactly the list and nothing it did not receive. The grant is composed from the same membership, in
|
|
the same act, so the two cannot drift.
|
|
|
|
## Decision
|
|
|
|
**Option 3.**
|
|
|
|
- **A membership per assignment.** The controller composes, for a module on a machine, one document:
|
|
the tools the module serves with the subject each is served on and the queue group if any; the seat
|
|
verbs this instance serves and their subjects; the subject each of its events lands on; what it may
|
|
reach — the tools it invokes, resolved to the subjects the mesh issued to those modules' instances —
|
|
and what it consumes. The runtime registers tools and events by name; the membership says where.
|
|
- **Published, not written into the definition.** The membership is a message on
|
|
`mesh.assignment.<node>.<module>` in a stream that keeps the last per subject, like a node's
|
|
declaration. The controller publishes it whenever the assignment's facts change: a push, a seat
|
|
handover, an instance added elsewhere, an upgrade. A runtime reads the current one when it connects,
|
|
serves it, and keeps reading, so a change reaches a running instance as a re-subscription rather
|
|
than a restart.
|
|
- **One bootstrap rule, and only one.** The credential names the node and the module; the membership's
|
|
subject follows from those two names and nothing else, and the account may subscribe it. Every
|
|
other subject is data in the membership. This is the one convention the runtime keeps, the way a
|
|
resolver keeps the address of a root.
|
|
- **The grant is the membership, read the other way.** What an account may subscribe is what its
|
|
membership says it serves plus its own membership's subject; what it may publish is what its
|
|
membership says it emits and reaches. One composition yields both, so a subject the runtime serves
|
|
without a grant, or a grant for a subject nothing serves, cannot be written.
|
|
- **Whether an instance answers for the module, or only for its machine, is the mesh's to decide.**
|
|
A module on one machine is issued the module's plain subject and its machine's. A module on several
|
|
is issued only its machine's unless its definition says its instances are interchangeable, a fact
|
|
about the software and not about the bus; then every instance is issued the plain subject in one
|
|
queue group as well. The console lists what the memberships say: a stateful module on two machines
|
|
appears once per machine; a stateless one appears once.
|
|
- **A caller composes nothing.** The console's listing carries each tool's subject; the SDK's call by
|
|
name reads the subject from the caller's own membership, where the mesh wrote what it may reach. The
|
|
shape of a subject is the controller's business and may change without any module or runtime
|
|
changing.
|
|
- **Today's shape is the shape issued first.** `mesh.mod.<module>.tool.<name>`, with the machine as the
|
|
last token for an instance, and `mesh.seat.<seat>.tool.<verb>` with the machine for a node-scoped
|
|
seat, are what the controller composes on day one, so nothing on the mesh moves when the
|
|
membership arrives; only who decides it moves. [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)
|
|
stands for what it decided — a call names the machine, every answer names it, a holder serves its
|
|
seat — and is extended in how: those facts are now issued, not derived.
|
|
|
|
## Consequences
|
|
|
|
- The runtime loses its subject rule and its claims rule; it serves a list. The controller gains one
|
|
composition and one stream; design 25 §2 and §3 gain a line each. The console loses `toolSubject`
|
|
and reads subjects from the listing. The SDK's `invokeTool` reads the caller's membership.
|
|
- A subject scheme change is a controller release and a republish of every membership, with no module
|
|
rebuilt — the opposite of this morning's forty-three builds.
|
|
- A membership can differ per assignment on purpose: an instance that holds a seat serves more; an
|
|
instance the mesh wants quiet serves less; a module the mesh is retiring can be issued nothing and
|
|
told so.
|
|
- During the move, a runtime that finds no membership for its assignment falls back to the derived
|
|
shape and says so in its log, so the wave of this change is a controller release followed by one
|
|
push, and a runtime older than the change keeps working on the convention it carries.
|
|
|
|
## How this is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| A membership composed for an assignment and the grant composed for its account name the same subjects, both ways | a controller test over a module on one machine, on two, holding a seat, and declared interchangeable |
|
|
| A runtime serves exactly the subjects its membership lists, and re-subscribes when the membership changes | a runtime test against a real bus: a membership published, served; republished with a subject removed and one added, followed |
|
|
| A runtime with no membership says so and serves the derived shape | the same test, before any membership is published |
|
|
| The console lists a stateful module on two machines once per machine, and composes no subject | the MCP conformance test |
|
|
| Live: the store's databases asked of one named machine and through the seat, after a controller release and one push, with no module rebuilt | by hand |
|
|
|
|
## Built, 2026-10-01
|
|
|
|
> **Progressive insight — 2026-10-01.** The decision stands; these are the facts of its building.
|
|
|
|
- The controller's half: mesh-controller 188 — the membership, its subject, the assignments stream
|
|
read directly, a module's account granted its own membership and nothing else of the stream, a
|
|
membership published after each push.
|
|
- The runtime's half: mesh-tools 23 — the one address derived, the membership read and followed,
|
|
exactly the issued subjects served and re-served, the derived shape with a log line until one is
|
|
issued, a seat's verbs implemented under the seat's name and never listed as the module's, the
|
|
listing carrying subjects and the console composing none. A claim may now name the verbs it
|
|
serves for its seat (mesh-controller 186), so a holder's own tools need not be the seat's.
|
|
- What the first roll-out taught: the controller's own grant did not name the assignments it issues,
|
|
so the first memberships were refused by the server and every runtime kept the derived shape —
|
|
which is exactly the fallback this record asked for, and exactly why nobody noticed
|
|
([issue 183](../04-ISSUES/183-the-controller-could-not-publish-the-memberships-it-issued/00-report.md)).
|
|
The SDK's `invokeTool` still composes a subject; it reaches a membership through the runtime's
|
|
broker, which does, so the caller-side rule is met there and not yet in the SDK's own words.
|
|
- Live, 14:55Z the same day, through the console: the console's runtime logged *was issued a new
|
|
membership; re-serving on it*; `mesh-store.databases` answered by the control node, the seat's
|
|
holder; `postgres.postgres_list_databases` with the machine named answered by that machine, on
|
|
both machines that run it; `mesh-controller.push {node}` reached the seat's verb with its own
|
|
argument intact. Three facts the proof taught: a runtime's first read of the stream must use the
|
|
subject-addressed direct get, the only form its account is granted (mesh-tools 25); a module's
|
|
bus credential is a minted secret written once, so a claim added to a definition reaches a running
|
|
module only after `module issue <module> --node <machine>` and a push (postgres, both machines);
|
|
and a registration under a seat the credential does not yet claim must be said and skipped, not
|
|
fatal (mesh-tools 26).
|
|
|
|
## References
|
|
|
|
- [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md) — extended: the same facts, issued rather than derived
|
|
- [ADR 0152](0152-the-operators-surface-is-a-module-the-console.md), [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md) — the surface and the seat's tools this applies to
|
|
- [Design 25 — The bus on NATS](../03-DESIGN/01-to-be/25-the-bus-on-nats.md) §2, §3; [Design 32 — What a module declares](../03-DESIGN/01-to-be/32-what-a-module-declares.md) §1; [Design 33](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md); [Design 34](../03-DESIGN/01-to-be/34-the-console.md)
|