From 99eb322de53affabe209966d8add38a52a59dd59 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 1 Oct 2026 14:33:56 +0200 Subject: [PATCH] ADR 0160: the mesh issues an assignment's subjects, and a runtime serves what it is issued; designs 25, 32, 33, 34 --- ...-and-a-runtime-serves-what-it-is-issued.md | 110 ++++++++++++++++++ 02-DECISIONS/README.md | 1 + 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 11 ++ .../01-to-be/32-what-a-module-declares.md | 7 ++ .../01-to-be/33-the-tools-the-mesh-answers.md | 5 +- 03-DESIGN/01-to-be/34-the-console.md | 5 +- 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md diff --git a/02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md b/02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md new file mode 100644 index 0000000..1517a86 --- /dev/null +++ b/02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md @@ -0,0 +1,110 @@ +--- +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..tool.` 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..` 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..tool.`, with the machine as the + last token for an instance, and `mesh.seat..tool.` 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 | + +## 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) diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index f392b0c..e6f2c09 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -173,6 +173,7 @@ python3 00-META/checks/index.py fail if stale - **0157** — [A build says what it does on the bus, as it happens](0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md) - **0158** — [A provider with one credential shares it with every consumer, and the vault remakes it for all of them at once](0158-a-provider-with-one-credential-shares-it-with-every-consumer.md) - **0159** — [A tool call names the machine it is for, every answer says which machine answered, and a holder's runtime serves its seat's verbs](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md) +- **0160** — [The mesh issues an assignment's subjects, and a runtime serves what it is issued](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md) ### Its tiers, from the bottom up diff --git a/03-DESIGN/01-to-be/25-the-bus-on-nats.md b/03-DESIGN/01-to-be/25-the-bus-on-nats.md index 7fcd9ef..55af4bc 100644 --- a/03-DESIGN/01-to-be/25-the-bus-on-nats.md +++ b/03-DESIGN/01-to-be/25-the-bus-on-nats.md @@ -9,6 +9,7 @@ code: - mesh-sdk src (the protocol's NATS binding, step 3) updated: 2026-10-01 decisions: + - 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md - 02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md - 02-DECISIONS/0106-the-bus-is-nats.md - 02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md @@ -81,8 +82,18 @@ mesh.seat..accept. work submitted to a role (JetStream: per-s mesh.seat..event. a role's own event (JetStream: EVENTS) mesh.seat..tool. a role's tool (core request/reply) mesh.ask.. the controller's command api (core request/reply) +mesh.assignment.. an assignment's membership (JetStream: ASSIGNMENTS, last-per-subject) ``` +**Revised 2026-10-01** ([ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)): the rows above +for a module's and a seat's tools are the shapes the controller *issues*, not rules a runtime carries. +Every assignment is published a membership — what it serves and where, in which queue, its seat verbs, +where its events land, what it may reach — on `mesh.assignment..`, kept last per subject +like a declaration, republished when the assignment's facts change. The runtime serves exactly that +list; the account's grant is the same membership read the other way; the console's listing carries each +tool's subject. The one rule a runtime keeps is the membership's own subject, from the two names in its +credential. + **Revised 2026-09-27** ([ADR 0129](../../02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md)): **`mesh.build.request`, `mesh.control.built` and the BUILDS stream are gone.** A build is work submitted to a role, and the mesh already has a shape for that — a seat's `accept` subjects, on a work queue with a queue group of diff --git a/03-DESIGN/01-to-be/32-what-a-module-declares.md b/03-DESIGN/01-to-be/32-what-a-module-declares.md index 2462aa8..a67f86b 100644 --- a/03-DESIGN/01-to-be/32-what-a-module-declares.md +++ b/03-DESIGN/01-to-be/32-what-a-module-declares.md @@ -13,6 +13,7 @@ code: - mesh-catalog modules/mesh-catalog updated: 2026-09-28 decisions: + - 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md - 02-DECISIONS/0126-a-module-declares-its-own-seats.md - 02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md - 02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md @@ -120,6 +121,12 @@ and a module consuming one event from two emitters could tell them apart only by The subject already carries the emitter, so the key a module sees names it too — which makes a disagreement between a manifest and the code a typo rather than a category error. +*2026-10-01 ([ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)):* where a name lands is now +*issued* to each assignment as a membership the controller publishes, rather than derived by a rule +the runtime carries; a module still declares only names, and gains one fact about itself — whether its +instances are interchangeable — which decides whether the mesh issues it the module's plain subject +beside its machine's. + ## 2. Three namespaces, and nothing else **Its own** — `mesh.mod..>`. Its events and its tools. Nothing else may publish into it, diff --git a/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md b/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md index d759e58..f04437e 100644 --- a/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md +++ b/03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md @@ -4,6 +4,7 @@ status: implemented code: [mesh-controller, mesh-tools] updated: 2026-10-01 decisions: + - 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md - 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md - 02-DECISIONS/0154-the-meshs-own-verbs-are-the-controller-seats-tools.md - 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md @@ -86,7 +87,9 @@ this section describes for seats: each instance also serves `mesh.mod..t a caller writes `.@`, and every answer names the machine that gave it. And §3 is built for every holder, not only the controller: the credential names the seats a module claims and their verbs, the runtime serves each with the tool of the same name on the seat's subject, and the -bus admits it only where the module holds the seat. +bus admits it only where the module holds the seat. *Later the same day ([ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)):* +the subjects a holder serves, and a module's own, stop being derived in the runtime and are issued to +the assignment as a membership the controller publishes; the shape stays, the deciding moves. ## 5. Discovery diff --git a/03-DESIGN/01-to-be/34-the-console.md b/03-DESIGN/01-to-be/34-the-console.md index 172e38c..c17e6ce 100644 --- a/03-DESIGN/01-to-be/34-the-console.md +++ b/03-DESIGN/01-to-be/34-the-console.md @@ -4,6 +4,7 @@ status: implemented code: [mesh-catalog, mesh-tools, mesh-controller] updated: 2026-10-01 decisions: + - 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md - 02-DECISIONS/0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md - 02-DECISIONS/0152-the-operators-surface-is-a-module-the-console.md - 02-DECISIONS/0132-a-seat-carries-the-tools-its-holder-must-serve.md @@ -69,7 +70,9 @@ kept for a short while and refreshed, so an agent asking on every turn does not an optional `node` the console lists on each one, puts into the subject and never hands to the module, for a module that runs on several machines; without it whichever instance answers first does, and the console appends *answered by * to every answer. A seat's verb takes none; the seat's scope -decides. +decides. *Later the same day ([ADR 0160](../../02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)):* the console composes no +subject at all; each tool's subject comes with the listing, and a stateful module on two machines is +listed once per machine because the mesh issued it no plain subject. **A tool that was not listed can still be called.** Listing is discovery; calling is the grant. An agent that knows a tool's name asks for it by `.` and the module answers or the bus says why not. -- 2.54.0