ADR 0106: the bus is NATS; issue 104 resolved #92
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
status: active
|
status: graduated
|
||||||
|
became: 02-DECISIONS/0106-the-bus-is-nats.md
|
||||||
initiated: 2026-09-23
|
initiated: 2026-09-23
|
||||||
touches:
|
touches:
|
||||||
- 02-DECISIONS/0002-nodes-communicate-over-a-broker.md
|
- 02-DECISIONS/0002-nodes-communicate-over-a-broker.md
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
topic: the mesh
|
||||||
|
status: accepted
|
||||||
|
date: 2026-09-23
|
||||||
|
deciders: jochen
|
||||||
|
reconstructed: false
|
||||||
|
extends: 02-DECISIONS/0002-nodes-communicate-over-a-broker.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# 106. The bus is NATS
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The mesh's bus is an AMQP broker. [Research 014](../01-RESEARCH/014-the-bus-on-nats/00-overview.md)
|
||||||
|
measured what that costs and what it would take to change: AMQP is spoken in three places of the
|
||||||
|
mesh's own code — the controller's link, the host's link, the tool runtime's client — and in none of
|
||||||
|
the sdk or the modules, because [ADR 0039](0039-what-the-sdk-holds-and-refuses.md) kept the client
|
||||||
|
out of the sdk for exactly this. The predecessor's world is AMQP and is being retired module by
|
||||||
|
module; it cannot move and does not need to.
|
||||||
|
|
||||||
|
The operator's direction: NATS, native — the mesh built on it, not bridged to it — and the tools a
|
||||||
|
person reaches from a workstation designed on it rather than built quickly on what exists.
|
||||||
|
|
||||||
|
## Considered Options
|
||||||
|
|
||||||
|
1. **Stay on AMQP.** Rejected by the operator.
|
||||||
|
2. **NATS behind a bridge**, the mesh unchanged. Rejected: two buses, every guarantee crossing a
|
||||||
|
seam, and the reason for changing — one native, simple, subject-addressed bus with request/reply
|
||||||
|
and accounts built in — lost at the seam.
|
||||||
|
3. **NATS native, built in the lab in parallel with the migration, cut over in one rehearsed
|
||||||
|
rollout after the migration's core is done.** Adopted.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**The mesh's bus is NATS.** Every link the mesh has — control, node queues, builds, enrolment,
|
||||||
|
reports, events, tool invocation — is carried on NATS subjects; durability, catch-up and
|
||||||
|
hold-unacknowledged-and-retry ([ADR 0083](0083-one-push-leaves-the-mesh-consistent.md)) are
|
||||||
|
JetStream's; a module's account is a NATS account with per-subject permissions derived from its
|
||||||
|
`emits` and `consumes` ([ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md)),
|
||||||
|
written as configuration the host declares and the server reloads — never through a management
|
||||||
|
API. The broker module changes; the seat `mesh-broker` does not
|
||||||
|
([ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md)).
|
||||||
|
|
||||||
|
**The sdk's broker contract does not change.** Modules are written against `request`, `handle`,
|
||||||
|
`publish`, `subscribe`; the runtime implements them on NATS; no module speaks a protocol.
|
||||||
|
|
||||||
|
**The AMQP broker the mesh adopted becomes the predecessor's compatibility broker**, kept as a
|
||||||
|
module with one purpose — the predecessor's clients — and retired with the last of them.
|
||||||
|
|
||||||
|
**It is built beside the migration and cut over after its core.** The bus is not changed under a
|
||||||
|
half-migrated node. A lab bed proves the whole path — enrolment, the store window, upgrades, tool
|
||||||
|
invocation — on NATS before any node's bus moves, and the move is one rollout: controller, every
|
||||||
|
host, every runtime together.
|
||||||
|
|
||||||
|
**What a person reaches the mesh's tools with is designed on NATS**, as part of the same design: a
|
||||||
|
person's account, scoped like a module's, and a client that speaks the bus directly.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- The architecture — subjects, streams, accounts, the enrolment handshake, the person's client — is
|
||||||
|
written as a to-be design before code, and reviewed.
|
||||||
|
- Eight records are touched: 0002 and 0033 (the substrate names a broker — now NATS), 0041 and
|
||||||
|
0042 (events keep their shape; the exchange becomes a subject prefix), 0043 (accounts as
|
||||||
|
configuration), 0078 (the broker module is `nats`), 0083 (JetStream carries the guarantee), 0039
|
||||||
|
(unchanged, and the reason this is possible).
|
||||||
|
- The lab beds that prove the bus are re-run on NATS; none is skipped.
|
||||||
|
- Until the cutover, nothing changes on any node.
|
||||||
|
|
||||||
|
## How it is checked
|
||||||
|
|
||||||
|
A lab bed raises a mesh on NATS from genesis and proves: a node enrols over TLS with a claimed
|
||||||
|
token; a push composes, is held while the store restarts and applies after; an upgrade rolls out;
|
||||||
|
a module's tools are invoked from another node and from a person's client; an event dead-letters
|
||||||
|
after its deliveries are exhausted; a module's account cannot publish outside its `emits` nor
|
||||||
|
subscribe outside its `consumes`. Then the cutover bed: a mesh on AMQP with the predecessor's
|
||||||
|
compatibility broker beside it moves its bus in one rollout with every node reporting afterwards.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [research 014](../01-RESEARCH/014-the-bus-on-nats/00-overview.md)
|
||||||
|
- [ADR 0002](0002-nodes-communicate-over-a-broker.md), [0033](0033-the-substrate-is-a-store-and-a-broker.md),
|
||||||
|
[0039](0039-what-the-sdk-holds-and-refuses.md), [0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md),
|
||||||
|
[0078](0078-the-store-and-broker-are-modules.md), [0083](0083-one-push-leaves-the-mesh-consistent.md)
|
||||||
@@ -94,6 +94,7 @@ python3 00-META/checks/index.py fail if stale
|
|||||||
- **0103** — [What an adopted node holds, and what its guard refuses](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md)
|
- **0103** — [What an adopted node holds, and what its guard refuses](0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md)
|
||||||
- **0104** — [A provision may be answered by an adapter to the predecessor](0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md)
|
- **0104** — [A provision may be answered by an adapter to the predecessor](0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md)
|
||||||
- **0105** — [The mesh adopts the predecessor's tunnel in place](0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md)
|
- **0105** — [The mesh adopts the predecessor's tunnel in place](0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md)
|
||||||
|
- **0106** — [The bus is NATS](0106-the-bus-is-nats.md)
|
||||||
|
|
||||||
### Its tiers, from the bottom up
|
### Its tiers, from the bottom up
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ code:
|
|||||||
updated: 2026-09-23
|
updated: 2026-09-23
|
||||||
decisions:
|
decisions:
|
||||||
- 02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md
|
- 02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md
|
||||||
|
- 02-DECISIONS/0106-the-bus-is-nats.md
|
||||||
- 02-DECISIONS/0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md
|
- 02-DECISIONS/0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md
|
||||||
- 02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md
|
- 02-DECISIONS/0103-what-an-adopted-node-holds-and-what-its-guard-refuses.md
|
||||||
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
- 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md
|
||||||
@@ -732,3 +733,14 @@ controller composes follows it, as readers of a setting. ADR 0100's non-overlap
|
|||||||
where a found tunnel is left running beside the mesh's; where it is adopted there is one tunnel.
|
where a found tunnel is left running beside the mesh's; where it is adopted there is one tunnel.
|
||||||
The guard admits the mesh's ports from that one interface, and the predecessor's peers arrive on
|
The guard admits the mesh's ports from that one interface, and the predecessor's peers arrive on
|
||||||
it.
|
it.
|
||||||
|
|
||||||
|
## The bus is NATS
|
||||||
|
|
||||||
|
*2026-09-23, [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md). Architecture to be written
|
||||||
|
before code; this section names the shape only.*
|
||||||
|
|
||||||
|
Every link the mesh has rides NATS subjects; durability is JetStream's; a module's account is a
|
||||||
|
NATS account with permissions derived from `emits`/`consumes`, declared as configuration the host
|
||||||
|
writes and the server reloads. The sdk's contract is unchanged. The adopted AMQP broker stays as
|
||||||
|
the predecessor's compatibility broker until its last client is gone. Built in the lab beside the
|
||||||
|
migration; cut over in one rollout after the core; the person's client is designed on it.
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
status: located
|
status: resolved
|
||||||
opened: 2026-09-23
|
opened: 2026-09-23
|
||||||
located-in: [mesh-host cmd/mesh-host]
|
located-in: [mesh-host cmd/mesh-host]
|
||||||
fixed-by:
|
fixed-by: mesh-host — a declaration for the other mode is refused, every file is refused once the mesh has spoken, and an apply previews what it would change
|
||||||
amended-design:
|
amended-design:
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user