diff --git a/02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md b/02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md new file mode 100644 index 0000000..f6af86e --- /dev/null +++ b/02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md @@ -0,0 +1,116 @@ +--- +topic: the mesh +status: accepted +date: 2026-09-26 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md +--- + +# 120. The mesh bus is required, not ambient + +## Context + +[Design 29](../03-DESIGN/01-to-be/29-what-a-module-declares.md) opened by saying the bus is +*ambient*: "No module requires it, the way no module requires a filesystem. Every module gets a +connection and an identity whether it asks or not." + +**Two counts say that is wrong.** Of the 72 modules in the catalogue, **49 declare an own-secret +named `broker` and 23 do not.** So the bus is not universal — nearly a third of the catalogue +never speaks to it — and an ambient connection would mint an account, a password and a permission +set for every one of those 23, each a credential nothing uses and everything must rotate. + +And the 49 that do take one **each hand-write the path it lands at** +(`own-secrets: { broker: "/var/lib//broker" }`). That is a special case doing badly what +provisioning already does well: a consumer names where a credential lands, the mesh seals it +there, and rotation and removal follow the same path as every other credential. + +**The argument that made the bus ambient was narrower than it looked.** +[ADR 0117](0117-the-bus-is-the-only-broker.md) reasoned that bus accounts cannot be provisioned +because a provisioner is itself a module that needs an account before it can run. That is true of +a **provisioner process**, and it is not true of a provision: the mesh's bus accounts are composed +by the *controller*, into configuration, and the controller is not waiting on a bus account to +exist. The circularity is real for one mechanism and absent for the other, and the earlier record +applied it to both. + +## Considered Options + +1. **Keep the bus ambient.** Rejected on the counts above: it over-grants to 23 modules and keeps + a hand-written path in 49. +2. **Derive the requirement** from whether a module declares any `emits`, `consumes`, `serves` or + `uses`. Rejected: it is the ambient model with extra inference. A reader of a manifest still + cannot see that the module holds a bus credential, and the rule would have to be re-derived + every time the set of bus-facing declarations grew. +3. **The mesh bus is a provision a module requires**, delivered by the seat that holds it. + Adopted. + +## Decision + +**A module that speaks to the mesh requires `mesh-bus`, and receives what it needs to connect.** +The contract is an address, a credential sealed to the module, and the trust to verify the +server. It lands where the module's manifest says, like any provision. A module that does not +require it gets no account, no password and no permissions — and 23 modules in the catalogue +should get none. + +**The `mesh-broker` seat delivers `mesh-bus`.** Its holder is the mesh's own bus, and what +holding it delivers is the connection to that bus — which is what a seat delivering a provision +has always meant ([design 26](../03-DESIGN/01-to-be/26-the-seats.md)). + +**The requirement delivers the connection; the declarations shape the authority.** They are two +different things and both stay explicit. `requires: mesh-bus` says *this module talks to the +mesh*; `emits`, `consumes`, `serves`, `uses` and a declared seat say *what it may say and hear*, +and the permission set is derived from those and nothing else +([ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md)). Requiring the bus +grants no subject; declaring a subject without requiring the bus is refused at registration as +incoherent. + +**The `mesh-bus` provision is answered by the controller, not by a provisioner.** This is the +surviving kernel of ADR 0117's bootstrap argument, narrowed to what it actually supports: the +bus's accounts are configuration the controller composes and the server reloads +([ADR 0106](0106-the-bus-is-nats.md) — never through a management API), so there is no provisioner +process in the path and nothing waiting on a bus account to create bus accounts. It is a provision +whose provider is the mesh itself. + +**A module may also provide a NATS server of its own, and that is a different interface.** Exactly +as the AMQP broker provides `amqp` ([ADR 0119](0119-amqp-is-a-provision-not-the-bus.md)), a module +may run its own NATS and offer it as a backing service. That interface is **`nats`**; the mesh's +own bus is **`mesh-bus`**; the two are never the same name, because a manifest that said `nats` +could mean either and the difference is the whole architecture. The rule from 0119 decides which +is legitimate: a private bus is a backing service, never a channel to another module. + +## Consequences + +- **Design 29's opening is reversed.** The bus is not ambient; it is required, and the document's + first paragraph says the opposite of this. +- **The seat's `Delivers` is `mesh-bus`** — corrected twice in one day, which is worth recording + rather than tidying: it read `amqp`, which was the old broker's interface; ADR 0117 emptied it, + on the reasoning that a bus cannot be provisioned; and it is neither. The seat delivers the + mesh's bus. +- **`own-secrets: { broker: ... }` is retired** in favour of the provision's own delivery, across + 49 manifests. That is a mechanical change, and it belongs with the conversions in step 4 rather + than step 1. +- **23 modules lose a credential they never used.** Not a regression — an over-grant removed, and + the smallest honest statement of what this buys. +- **What got harder:** one more line in most manifests. The trade is that the line is true, and + its absence is also true. + +## How it is checked + +- **A module with no `requires: mesh-bus` has no account.** A composition test: the derived user + list contains exactly the modules that require it, and the 23 that do not appear nowhere in it. +- **Declaring a subject without requiring the bus is refused.** A registration test on a manifest + with `emits` and no requirement, naming the contradiction. +- **Requiring the bus grants no subject on its own.** A composition test: a module that requires + `mesh-bus` and declares nothing else gets a connection and an empty permission set. +- **`nats` and `mesh-bus` are distinct interfaces.** A resolution test: a module requiring `nats` + is answered by a module providing it, never by the seat holder, and vice versa. + +## References + +- [ADR 0117](0117-the-bus-is-the-only-broker.md) — superseded by 0119; its bootstrap argument is + narrowed here to the case it supports. +- [ADR 0119](0119-amqp-is-a-provision-not-the-bus.md) — a broker as a backing service; this + applies the same shape to the mesh's own bus and separates the two names. +- [ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) — authority from + declarations, which this leaves untouched. +- [design 26](../03-DESIGN/01-to-be/26-the-seats.md) — a seat delivering a provision. diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index abf0392..740bca0 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -135,6 +135,7 @@ python3 00-META/checks/index.py fail if stale - **0116** — [The bus is built in five steps, and the protocol moves with it](0116-the-bus-is-built-in-five-steps.md) - **0117** — [The bus is the only broker](0117-the-bus-is-the-only-broker.md) *(superseded)* - **0119** — [AMQP is a provision, not the bus](0119-amqp-is-a-provision-not-the-bus.md) +- **0120** — [The mesh bus is required, not ambient](0120-the-mesh-bus-is-required-not-ambient.md) ### Its tiers, from the bottom up diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index c64e07b..0ac67a1 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -11,6 +11,7 @@ code: updated: 2026-09-26 decisions: - 02-DECISIONS/0118-a-module-declares-its-own-seats.md + - 02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md - 02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md - 02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md --- @@ -64,7 +65,7 @@ convention, which later seats departed from. |---|---|---|---| | `mesh-controller` | — | mesh | — | the controller | | `mesh-store` | — | mesh | — | the store the mesh's own records live in | -| `mesh-broker` | — | mesh | — | the broker carrying the mesh's own bus | +| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus | | `mesh-vault` | — | mesh | `secret`, reserved | the vault | | `mesh-artifact-store` | `the-artifact-store` | mesh | `artifact-store` | the artifact registry | | `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue | diff --git a/03-DESIGN/01-to-be/29-what-a-module-declares.md b/03-DESIGN/01-to-be/29-what-a-module-declares.md index 51498ce..1351bbe 100644 --- a/03-DESIGN/01-to-be/29-what-a-module-declares.md +++ b/03-DESIGN/01-to-be/29-what-a-module-declares.md @@ -6,6 +6,7 @@ updated: 2026-09-26 decisions: - 02-DECISIONS/0118-a-module-declares-its-own-seats.md - 02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md + - 02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md - 02-DECISIONS/0106-the-bus-is-nats.md - 02-DECISIONS/0041-events-are-a-relationship.md - 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md @@ -14,10 +15,22 @@ decisions: # 29. What a module declares, and what the bus makes of it -**The bus is ambient.** No module requires it, the way no module requires a filesystem. Every -module gets a connection and an identity whether it asks or not. What a module declares are -*relationships*; subjects, streams, consumers and permissions are all derived from those, and a -manifest never contains one. +**A module that speaks to the mesh requires the bus, and receives what it needs to connect** +([ADR 0120](../../02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md)). What a module +declares are *relationships*; subjects, streams, consumers and permissions are all derived from +those, and a manifest never contains one. + +> **Revised 2026-09-26.** This document opened by calling the bus *ambient* — "no module requires +> it, the way no module requires a filesystem". Two counts say otherwise: of 72 modules in the +> catalogue, **49 take a broker credential and 23 do not**, so an ambient connection would mint an +> account for a third of the catalogue that never speaks; and the 49 each hand-write the path it +> lands at, which is provisioning done badly by hand. The bus is required, and a module that does +> not require it has no account at all. + +**The requirement delivers the connection; the declarations shape the authority.** `requires: +mesh-bus` says *this module talks to the mesh* and grants no subject by itself. `emits`, +`consumes`, `serves`, `uses` and a declared seat say what it may say and hear. Declaring a subject +without requiring the bus is incoherent and refused at registration. This document is the declaration model. [Design 25](25-the-bus-on-nats.md) is the bus itself — subjects, streams, accounts, enrolment — and stays the authority on the wire. @@ -254,6 +267,20 @@ What stays different, and must not be unified away: a provision has a **per-cons a sealed credential**, created and destroyed per consumer. A seat protocol has neither — it is a role you send to. Collapsing them would mean pretending a database is a subject. +### `mesh-bus` and `nats` are two interfaces, never one name + +The mesh's own bus is **`mesh-bus`**, delivered by the `mesh-broker` seat and answered by the +controller — because the bus's accounts are configuration rather than something a provisioner +creates, so there is no provisioner process in the path and nothing waiting on a bus account in +order to make bus accounts. A module that runs a NATS server of its own and offers it as a +backing service provides **`nats`**, exactly as the AMQP broker provides `amqp` +([ADR 0119](../../02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.md)). + +They are never the same name. A manifest saying `nats` could otherwise mean either the mesh's +nervous system or a private queue, and the difference between those is the whole architecture. +0119's rule decides which is legitimate: a private bus is a backing service, never a channel to +another module. + ### Where addresses survive "Where is it?" is two different problems, and the bus solves one of them completely and the other