The mesh bus is required, not ambient
Design 29 said no module requires the bus. The catalogue disagrees: 49 of 72 modules take a broker credential and 23 do not, so an ambient connection mints 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 bootstrap argument that made it ambient was narrower than it looked. "A provisioner needs an account before it can run" is true of a provisioner process and says nothing about a provision the controller answers, and the controller is not waiting on a bus account to compose one. So: the mesh-broker seat delivers mesh-bus; a module requires it and gets an address, a sealed credential and the trust to verify the server; a module that requires nothing has no account at all. The requirement delivers the connection, the declarations shape the authority, and declaring a subject without requiring the bus is refused as incoherent. mesh-bus and nats are deliberately two names: a module may run its own NATS as a backing service exactly as one provides amqp, and a manifest saying "nats" would otherwise mean either the mesh's nervous system or a private queue. The seat's Delivers was wrong twice today — amqp, then empty — and the comment says so rather than reading as though it were always right.
This commit is contained in:
@@ -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/<module>/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.
|
||||||
@@ -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)
|
- **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)*
|
- **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)
|
- **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
|
### Its tiers, from the bottom up
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ code:
|
|||||||
updated: 2026-09-26
|
updated: 2026-09-26
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0118-a-module-declares-its-own-seats.md
|
- 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/0111-a-build-source-is-on-the-git-seat-or-external.md
|
||||||
- 02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.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-controller` | — | mesh | — | the controller |
|
||||||
| `mesh-store` | — | mesh | — | the store the mesh's own records live in |
|
| `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-vault` | — | mesh | `secret`, reserved | the vault |
|
||||||
| `mesh-artifact-store` | `the-artifact-store` | mesh | `artifact-store` | the artifact registry |
|
| `mesh-artifact-store` | `the-artifact-store` | mesh | `artifact-store` | the artifact registry |
|
||||||
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue |
|
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue |
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ updated: 2026-09-26
|
|||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0118-a-module-declares-its-own-seats.md
|
- 02-DECISIONS/0118-a-module-declares-its-own-seats.md
|
||||||
- 02-DECISIONS/0119-amqp-is-a-provision-not-the-bus.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/0106-the-bus-is-nats.md
|
||||||
- 02-DECISIONS/0041-events-are-a-relationship.md
|
- 02-DECISIONS/0041-events-are-a-relationship.md
|
||||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.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
|
# 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
|
**A module that speaks to the mesh requires the bus, and receives what it needs to connect**
|
||||||
module gets a connection and an identity whether it asks or not. What a module declares are
|
([ADR 0120](../../02-DECISIONS/0120-the-mesh-bus-is-required-not-ambient.md)). What a module
|
||||||
*relationships*; subjects, streams, consumers and permissions are all derived from those, and a
|
declares are *relationships*; subjects, streams, consumers and permissions are all derived from
|
||||||
manifest never contains one.
|
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 —
|
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.
|
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
|
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.
|
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 addresses survive
|
||||||
|
|
||||||
"Where is it?" is two different problems, and the bus solves one of them completely and the other
|
"Where is it?" is two different problems, and the bus solves one of them completely and the other
|
||||||
|
|||||||
Reference in New Issue
Block a user