From fe0c1e9da2dcd370f8f46003d8df9f7b45f552a8 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 18:19:56 +0200 Subject: [PATCH 1/4] Divide the bus work into five steps, each proved on its own The NATS change was recorded as one undivided item, which hid three gaps: a mesh already running had no adoption path, the protocol specification did not know its transport was being replaced, and nothing was runnable until everything was. Dividing it is what surfaced them. --- .../0115-the-bus-is-built-in-five-steps.md | 142 ++++++++++++++++ 02-DECISIONS/README.md | 1 + 03-DESIGN/01-to-be/19-the-module-protocol.md | 25 ++- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 158 ++++++++++++++---- 4 files changed, 294 insertions(+), 32 deletions(-) create mode 100644 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md diff --git a/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md b/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md new file mode 100644 index 0000000..2d4c9ed --- /dev/null +++ b/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md @@ -0,0 +1,142 @@ +--- +topic: the mesh +status: accepted +date: 2026-09-26 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0106-the-bus-is-nats.md +--- + +# 115. The bus is built in five steps, and the protocol moves with it + +## Context + +[ADR 0106](0106-the-bus-is-nats.md) decided the bus is NATS and described the change as one thing: +built beside the migration, cut over in one rollout after its core. The architecture was written as +[design 25](../03-DESIGN/01-to-be/25-the-bus-on-nats.md) and revised once after review. What neither +says is how the work is divided, and three gaps follow from that. + +**The whole of the build is one point.** Design 25 §9 numbers four items. The first — "the `nats` +module, the controller's and host's link on NATS, the runtime's client — built and proven in the +lab" — is every line of code the change requires; the other three are the rollout. A step of that +size ends at nothing provable until it ends at everything, which is the failure +[design 22](../03-DESIGN/01-to-be/22-the-work-ahead.md) opens by naming: *a phase that ends at a +claim is a phase that went missing without anything complaining.* + +**There is no adoption path.** Design 25 §5 says the broker "is raised at genesis like the store, +adopted as a module in the same phase" — which describes a mesh being raised from nothing. The mesh +this is for is already running, and a running node gets a foundation module by adoption in place +([ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md)), not by genesis. §9 goes +straight from "built beside" to "one rollout" and never crosses that gap. + +**The wire changes and the protocol specification does not know it.** Design 25 §8 says a module +sees nothing new. That is true of the SDK's contract — `request`, `handle`, `publish`, `subscribe` +— and false of the wire underneath it. +[ADR 0074](0074-the-wire-is-specified-not-the-types.md) settled that what an SDK implements is a +*specified wire*, checked by fixtures that must match byte for byte, precisely because two +implementations that disagree about an envelope do not fail to compile. That specification is +[design 19](../03-DESIGN/01-to-be/19-the-module-protocol.md), and it is written entirely in AMQP: +exchanges, a durable per-consumer queue named `..events`, a shared `serve.` +queue — sixteen occurrences of an AMQP term across the document. Design 25 does not cite design 19 +anywhere, and design 19 does not cite ADR 0106. So the record that says *what two implementations +may not disagree about* still describes the bus being replaced. + +## Considered Options + +1. **Keep ADR 0106's shape — build it all, cut over once.** Rejected: not for its rollout, which + is right, but because it leaves the build a single step of unknown length with no intermediate + anyone can run. The three gaps above were found by dividing it; they were invisible while it + was one item. +2. **Cut over incrementally by traffic kind** — events to NATS first, then tools, then control, + the mesh on two buses meanwhile. Rejected: ADR 0106 already rejected two buses, and this is + that with extra steps. The store-window guarantee + ([ADR 0083](0083-one-push-leaves-the-mesh-consistent.md)) is exactly the one that cannot cross + a seam, and control is exactly the traffic that carries it. +3. **Five steps, each ending at something provable, the cutover still one rollout.** The build is + divided; the bus still moves once. Adopted. + +## Decision + +**The bus is built in five steps. Each ends at something a lab bed proves, and no step's proof +waits for the one after it. The cutover remains a single rollout** — dividing the build does not +divide the bus. + +**Step 1 — genesis raises the broker.** The `nats` module and a mesh raised on it from nothing. +This is built and proven even though the mesh it is for will never travel this path, because +genesis is where the foundation is *defined*: the only place the mesh comes from nothing, and the +definition every other path is measured against. A genesis path that exists only on paper is one +nobody discovers is wrong until there is a second mesh. + +**Step 2 — adoption puts the broker in the seat.** A mesh already running receives the broker by +adoption in place, and the seat it claims is **`mesh-broker`** — unchanged. +[ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md) named the foundation seats +after the server's *role* rather than the product for exactly this case, and ADR 0106 restated it: +the broker module changes, the seat does not. A seat named after the product would have to be +renamed by every change the seat exists to survive. + +**Step 3 — the protocol gets a NATS binding.** ADR 0074 stands unamended: the mesh defines a module +protocol, an SDK is an implementation of it in one language and nothing more, the protocol is split +per capability, and conformance is executable fixtures per capability rather than prose. What +changes is what the specification specifies. Design 19's wire section is rewritten from exchanges +and queues to subjects and streams; the fixtures are recaptured on NATS; every SDK claims the +capabilities it passes, and a language may arrive with connection and events alone. + +**The SDK gains no conveniences in the process.** [ADR 0039](0039-what-the-sdk-holds-and-refuses.md) +refuses frequent-and-cascading code in the shared library, and ADR 0074 restates it — an SDK is +"not a convenience layer, not a place for helpers to accumulate." A new transport is the moment +that pressure is highest and the reason to hold hardest: the predecessor's shared library is the +cautionary tale ADR 0039 opens with, and it did not become that in one decision. Code shared among +a module's own features stays in that module. + +**Step 4 — the core speaks NATS.** The controller's link, the host's link and the tool runtime's +client, and with them the flows that are today carried by something other than the bus: a build +source's change reaching the builder, an installation, a module's own reports. Each is a +conversion with a named before and after, not a rewrite. Observation — heartbeats, conditions, +key-value state — is [research 017](../01-RESEARCH/017-a-mesh-that-heals-itself/00-overview.md)'s +and stays there; that effort already reserves it for after the move, and this step does not +pre-empt its design. + +**Step 5 — deployment.** The rollout ADR 0106 decided, unchanged: the controller, every host and +every runtime move together, each node confirmed to have heard before AMQP stops. What this step +adds is that steps 1 to 4 *ship ahead of it* without moving any node's bus — the module exists in +the catalogue, the protocol is specified, the code is written and beds pass, and the running mesh +is still on AMQP throughout. The bus moves on one day, at the end, once. + +## Consequences + +- **Design 25 §9 is replaced by these five steps** and §10's beds are attributed to the step each + proves, so no proof waits for the last step. +- **Design 19 is stale in its wire section from today** and says so in its own frontmatter and + opening until step 3 rewrites it. A specification that describes the bus being replaced is worse + than an absent one, because it reads as current. +- **`nats-broker` is not a seat.** The module is `nats`; the seat is `mesh-broker`. +- **Steps 1 to 4 leave every node on AMQP.** A step can be abandoned, or reordered after step 2, + without a rollback — the cost of being wrong is bounded until step 5. +- **What got harder:** five steps mean five proofs rather than one, and step 3 rewrites a design + other designs cite, so their references are checked when it lands. Dividing the work does not + reduce it. + +## How it is checked + +- **Per step, a bed, and the bed named in design 25 §10 against the step it belongs to.** Step 1: + a mesh raised on NATS from genesis. Step 2: the broker adopted into a mesh already running, and + a second holder of `mesh-broker` refused at resolution. Step 3: the conformance suite passing per + capability, on NATS, for every SDK that claims it — and a module built before the binding serving + its tools unchanged. Step 4: each converted flow proved against the behaviour it replaced. Step 5: + the cutover bed, then the rollout with every node reporting. +- **Step 3 is not done when the code runs.** It is done when the fixtures match byte for byte + across implementations, which is ADR 0074's own test and the only one that catches two SDKs + quietly ignoring each other. +- **A step that cannot name what its bed proves is not a step**, and is divided further before it + is started. + +## References + +- [ADR 0106](0106-the-bus-is-nats.md) — the decision this divides. +- [ADR 0074](0074-the-wire-is-specified-not-the-types.md) — the protocol and its conformance suite; + step 3 is its NATS binding, not a replacement. +- [ADR 0039](0039-what-the-sdk-holds-and-refuses.md) — why step 3 adds no helpers. +- [ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md) — why the seat is + `mesh-broker`. +- [ADR 0100](0100-a-node-in-use-is-adopted-before-it-is-converged.md) — the adoption step 2 uses. +- [design 25](../03-DESIGN/01-to-be/25-the-bus-on-nats.md), [design 19](../03-DESIGN/01-to-be/19-the-module-protocol.md) — the two documents this changes. diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 6b46026..a54f2ac 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -95,6 +95,7 @@ python3 00-META/checks/index.py fail if stale - **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) - **0106** — [The bus is NATS](0106-the-bus-is-nats.md) +- **0115** — [The bus is built in five steps, and the protocol moves with it](0115-the-bus-is-built-in-five-steps.md) ### Its tiers, from the bottom up diff --git a/03-DESIGN/01-to-be/19-the-module-protocol.md b/03-DESIGN/01-to-be/19-the-module-protocol.md index 8f0775b..5f37ac8 100644 --- a/03-DESIGN/01-to-be/19-the-module-protocol.md +++ b/03-DESIGN/01-to-be/19-the-module-protocol.md @@ -5,9 +5,11 @@ code: - mesh-sdk src - mesh-tools src/broker-amqp.ts - mesh-controller internal/link -updated: 2026-09-21 +updated: 2026-09-26 decisions: - 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md + - 02-DECISIONS/0106-the-bus-is-nats.md + - 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md - 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md - 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md - 02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md @@ -23,6 +25,27 @@ language and nothing more ([ADR 0074](../../02-DECISIONS/0074-the-wire-is-specif This is a specification, so it says what is required rather than how anything is arranged. Where it describes current behaviour that is *not yet* specified-and-conformed, it says so. +> **The wire below is the bus being replaced.** *2026-09-26.* +> [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md) moved the mesh's bus to NATS. Everything +> in this document that names an exchange, a queue or a routing key — the event exchanges, the +> durable `..events` queue, the shared `serve.` queue — describes the transport +> being retired, and the conformance fixtures were captured against it. +> +> What does **not** change is this document's model, which is the part ADR 0074 decided: a floor +> plus independent capabilities, an SDK that implements what it claims and is legitimate when it +> claims less, identity taken from the sealed credential rather than the environment, at-least-once +> with dedup on `x-event-id`, and conformance as executable fixtures per capability rather than +> prose. The envelope keeps its shape ([ADR 0042](../../02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md)); +> it becomes the message body. +> +> Rewriting the wire sections onto the subjects and streams of +> [design 25](25-the-bus-on-nats.md) §2–§3, and recapturing the fixtures there, is **step 3 of +> [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)**. Until that lands, read +> the sections below for what two implementations may not disagree *about*, and design 25 for what +> they will disagree about it *on*. A specification that silently described a retired transport +> would be worse than an absent one, because it reads as current — hence this note rather than a +> quiet edit. + ## The shape of it A **floor** every implementation needs, and three **capabilities** that are independent of each 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 3ffe4f7..beaa57a 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 @@ -6,9 +6,14 @@ code: - mesh-host internal/link (to be replaced) - mesh-tools src/broker-amqp.ts (to be replaced) - mesh-catalog modules/nats (to be written) -updated: 2026-09-24 + - mesh-sdk src (the protocol's NATS binding, step 3) +updated: 2026-09-26 decisions: - 02-DECISIONS/0106-the-bus-is-nats.md + - 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md + - 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md + - 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md + - 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md - 02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md - 02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md - 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md @@ -221,39 +226,94 @@ bridged. It is three things: Nothing is built of this before §10's bed passes; the MCP surface is a thin adapter over (2). -## 8. What a module sees +## 8. What a module sees, and what the wire does -Nothing new. `publish` on an envelope becomes a publish on `mesh.events..`; -`subscribe` with a pattern becomes a durable JetStream consumer on the matching subject filter; -`request`/`handle` become a NATS request and a queue-group subscription on -`mesh.tools..`. The envelope's shape ([ADR 0042](../../02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md)) -is unchanged; it is the message body. A module built today runs on the new runtime without a -rebuild — that is the test of ADR 0039, and it is in §10. +**The contract a module is written against does not change.** `publish` on an envelope becomes a +publish on `mesh.events..`; `subscribe` with a pattern becomes a durable JetStream +consumer on the matching subject filter; `request`/`handle` become a NATS request and a queue-group +subscription on `mesh.tools..`. The envelope's shape +([ADR 0042](../../02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md)) is unchanged; it is the +message body. A module built today runs on the new runtime without a rebuild — that is the test of +ADR 0039, and it is in §10. -## 9. Moving from the bus the mesh has +**The wire underneath it changes completely, and that is a specification, not an implementation +detail.** Revision, second review ([ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)): +an earlier draft of this section said "nothing new" and stopped there, which read as though the +change were contained inside the runtime. It is not. +[ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md) settled that an SDK is an +implementation of a *specified wire*, checked by fixtures that must match byte for byte — because +two implementations that disagree about an envelope do not fail to compile, they ignore each other +while both keep running. That specification is +[design 19](19-the-module-protocol.md), and it is written in exchanges, a durable per-consumer queue +named `..events`, and a shared `serve.` queue. Every one of those is gone here. -Per ADR 0106: built beside, cut over once, after the core. +So design 19 is rewritten from exchanges and queues to the subjects and streams of §2 and §3, its +fixtures are recaptured on NATS, and each SDK re-claims the capabilities it passes. That is step 3 +of §9, and until it lands design 19 says in its own opening that its wire section describes the bus +being replaced. What is *not* rewritten: ADR 0074's model — protocol split per capability, an SDK +that implements the floor and events alone is legitimate, conformance executable per capability — +and ADR 0039's refusal. A new transport is when the pressure to grow the shared library is highest; +nothing is added to it here. -1. The `nats` module, the controller's and host's link on NATS, the runtime's client — built and - proven in the lab (§10) while the migration continues on AMQP. Modules converted meanwhile - target the sdk contract and are untouched by this. -2. The cutover is one rollout, previewed: the controller assigns `nats` to the hub (raised beside - the AMQP broker on its own ports), composes every node's and module's account into it, then - rolls out the controller, every host and every runtime built for NATS. Each node's host connects - to the new bus as it comes up and reports; the controller confirms every node heard before it - stops listening on AMQP. The predecessor's clients never notice: their broker is the - compatibility module and stays. -3. The AMQP-side mesh accounts are removed from the compatibility broker; it keeps only the - predecessor's users. The bus's port settings follow ADR 0100 like any port. -4. The compatibility broker retires when its retirement condition holds. +## 9. Moving from the bus the mesh has: five steps -What is not done: no dual-bus period for the mesh's own traffic, no bridge, no module rebuilt. +Per ADR 0106 the bus moves once. Per +[ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md) the *build* is five steps, +each ending at something §10 proves, so that no part of this waits on the whole of it. Dividing the +build does not divide the bus: steps 1 to 4 leave every node on AMQP, and step 5 is still one +rollout. + +**Step 1 — genesis raises the broker.** The `nats` module of §5, and a mesh raised on it from +nothing. This is built and proven although the mesh it is for will never travel this path: genesis +is where the foundation is *defined*, the one place the mesh comes from nothing, and the definition +every other path is measured against. A genesis path that exists only on paper is one nobody finds +wrong until there is a second mesh. *Ends at: the genesis bed.* + +**Step 2 — adoption puts the broker in its seat.** A mesh already running does not get a foundation +module by being raised again; it adopts one in place +([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)). The +server is raised beside the AMQP broker on its own ports, carrying no mesh traffic yet, and the +`nats` module is adopted onto it. The seat it claims is **`mesh-broker`**, unchanged — the +foundation seats are named after the server's role rather than the product +([ADR 0079](../../02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md)) for +exactly this case, and a seat named after the product would need renaming by every change the seat +exists to survive. *Ends at: the adoption bed.* + +**Step 3 — the protocol gets its NATS binding.** §8's other half: design 19 rewritten from +exchanges and queues to subjects and streams, its conformance fixtures recaptured on NATS, each SDK +re-claiming the capabilities it passes. ADR 0074's model is unamended and ADR 0039's refusal holds — +no helper layer arrives with the new transport. A language may still arrive in pieces: connection +and events first, tools and provisioning when something needs them. *Ends at: the conformance suite, +per capability, per implementation.* + +**Step 4 — the core speaks NATS.** The controller's link, the host's link, the tool runtime's +client — and with them the flows carried today by something other than the bus: a build source's +change reaching the builder, an installation, a module's own reports. Each is a conversion with a +named before and after. Observation — heartbeats, conditions, key-value state — belongs to +[research 017](../../01-RESEARCH/017-a-mesh-that-heals-itself/00-overview.md), which already +reserves it for after the move; this step does not pre-empt its design. Modules converted meanwhile +target the sdk contract and are untouched by any of it. *Ends at: each converted flow proved against +the behaviour it replaced.* + +**Step 5 — the rollout.** Unchanged from ADR 0106 and previewed: the controller composes every +node's and module's account into the server standing since step 2, then rolls out the controller, +every host and every runtime built for NATS. Each node's host connects to the new bus as it comes up +and reports; the controller confirms every node heard before it stops listening on AMQP. The +predecessor's clients never notice — their broker is the compatibility module and stays. Then the +AMQP-side mesh accounts are removed from it, leaving only the predecessor's users, and it retires +when its retirement condition holds. The bus's port settings follow ADR 0100 like any port. +*Ends at: the cutover bed, then the rollout itself.* + +What is not done, at any step: no dual-bus period for the mesh's own traffic, no bridge, no module +rebuilt. ## 10. How it is checked -Two lab beds, both required green before any node's bus moves. +**A bed per step, and each is green before the step after it starts** — the division in §9 is only +real if the proofs divide with it. A step that cannot name what its bed proves is not a step, and is +divided further before it is started. -**The bus bed** — a mesh raised on NATS from genesis: +**Step 1 — the genesis bed**, a mesh raised on NATS from nothing: - a node enrols over TLS with a claimed token, and the enrolment user cannot read a declaration; - a push composes; the store is stopped; the push is held (nak with delay), the store returns, the push applies, nothing was lost or duplicated; @@ -273,12 +333,40 @@ Two lab beds, both required green before any node's bus moves. field a consumer's ack has already claimed; - the `nats` container is not recreated when only its composed configuration file changes, and a change to that file is live (a new user can connect, a revoked one cannot) within one - watcher-poll interval, without a restart; -- a module built before this design serves its tools unchanged on the new runtime. + watcher-poll interval, without a restart. -**The cutover bed** — a mesh on AMQP with a predecessor stand-in on the compatibility broker moves -its bus in one rollout; every node reports on NATS afterwards; the stand-in's client on AMQP is -still connected throughout. +**Step 2 — the adoption bed**, a mesh already running that has never had this server: +- the server is raised beside the AMQP broker on its own ports and the `nats` module is adopted + onto it in place, holding the data and the configuration it was raised with; +- the seat it claims is `mesh-broker`, and a second assignment of it anywhere in the mesh is + refused at resolution — *one per mesh*, as ADR 0079 requires; +- every node stays on AMQP throughout and nothing routes to the adopted server. This is the + check that makes steps 3 and 4 safe to run against a live mesh: adoption that quietly carried + traffic would be step 5 arriving early and unrehearsed. + +**Step 3 — conformance, per capability, per implementation:** +- the fixtures of design 19, recaptured on NATS, produced and consumed byte for byte by every SDK + that claims the capability — the test ADR 0074 set, and the only one that catches two + implementations quietly ignoring each other; +- an SDK implementing connection and events alone passes those two and claims nothing more, + rather than failing as a whole; +- a module built before this design serves its tools unchanged on the new runtime — the test of + ADR 0039; +- the shared library gained nothing but the binding: its surface is the protocol and the + primitives, and a helper that arrived with the transport is a review failure, not a detail. + +**Step 4 — each converted flow against the behaviour it replaced:** +- a build source's change reaches the builder over the bus, and the build that follows is the one + the change asked for; +- an installation completes over the bus, with the same outcome the path it replaces produced; +- a module's reports arrive, and a node that was unreachable catches up rather than losing them; +- nothing of research 017's observation work is present — no heartbeat semantics, no condition + store — because that design is not written yet and a flow built ahead of it would have to be + rebuilt. + +**Step 5 — the cutover bed:** a mesh on AMQP with a predecessor stand-in on the compatibility +broker moves its bus in one rollout; every node reports on NATS afterwards; the stand-in's client +on AMQP is still connected throughout. Unit tests hold the controller to composing accounts from `emits`/`consumes` and nothing else, to creating the four streams and asserting them idempotently, and to spending a token exactly once; @@ -287,7 +375,15 @@ to mapping the sdk contract onto subjects exactly as §8 says. ## 11. Open, for the review -**Closed by this revision** (first review, recorded in `MIGRATION-LOG.md`, 2026-09-24): the +**Closed by the second revision** (2026-09-26, [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)): +the build was one undivided item (§9 is now five steps, each with its own bed in §10); there was no +adoption path for a mesh already running (§9 step 2); and §8 said a module sees "nothing new" +without distinguishing the sdk's contract, which does not change, from the specified wire, which +changes entirely and is design 19's (§8, §9 step 3). Two smaller corrections: the seat is +`mesh-broker` and not the product's name, and the shared library gains no conveniences with the new +transport. + +**Closed by the first revision** (recorded in `MIGRATION-LOG.md`, 2026-09-24): the `reload-on`/container mismatch (§5), the eaten reply subject on a CONTROL-stream message (§2, §6), the missing ack permission (§4), and the un-scoped reply inbox under one account (§4). Each is named where it was wrong, not silently fixed, so a reader comparing against the first version can From 6ab113e6c68fa06731dd63697c747c7d4851992a Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 18:24:14 +0200 Subject: [PATCH 2/4] Break the bus work down, measured, in dependency order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Counting the surface first changed the plan twice: the genesis bed cannot run until the links exist, so it belongs to step 4, and there is no conformance suite to recapture — step 3 builds one against the current bus before moving it. Both corrections are recorded in the breakdown rather than edited into ADR 0115. Also indexes design 25, which was never listed. --- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 3 + 03-DESIGN/01-to-be/28-building-the-bus.md | 251 ++++++++++++++++++++++ 03-DESIGN/01-to-be/README.md | 2 + 3 files changed, 256 insertions(+) create mode 100644 03-DESIGN/01-to-be/28-building-the-bus.md 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 beaa57a..0b0fc45 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 @@ -307,6 +307,9 @@ when its retirement condition holds. The bus's port settings follow ADR 0100 lik What is not done, at any step: no dual-bus period for the mesh's own traffic, no bridge, no module rebuilt. +**The work of these five, broken down and measured, is [design 28](28-building-the-bus.md)** — +including the two places their dependencies put a bed later than the step that names it. + ## 10. How it is checked **A bed per step, and each is green before the step after it starts** — the division in §9 is only diff --git a/03-DESIGN/01-to-be/28-building-the-bus.md b/03-DESIGN/01-to-be/28-building-the-bus.md new file mode 100644 index 0000000..ca5f952 --- /dev/null +++ b/03-DESIGN/01-to-be/28-building-the-bus.md @@ -0,0 +1,251 @@ +--- +layer: to-be +status: proposed +code: [] +updated: 2026-09-26 +decisions: + - 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md + - 02-DECISIONS/0106-the-bus-is-nats.md + - 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md + - 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md + - 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md + - 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md +--- + +# 28. Building the bus + +**The work of [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)'s five steps, +in the order its dependencies allow, with what each ends at.** +[Design 25](25-the-bus-on-nats.md) is the architecture and stays the authority on *what is built*; +this document holds only the order, the sizes and the proofs, and it is wrong the moment it +disagrees with design 25 rather than the other way round. + +Each step ends at something runnable. A step that cannot name what its bed proves is not a step, +and is divided further before it is started. + +## How this is built, and when it is run + +**Written as code with unit tests, committed per change, and taken to the lab once the pieces that +would change the outcome are in place.** The mistake this avoids is the one +[design 22](22-the-work-ahead.md) records: running a long bed against a mesh mid-transformation and +debugging paths the next step deletes. Where a fault can be reasoned out of the code path, it is — +reading, not running. + +So the beds below are acceptance tests at the end of assembled work, not the tool for finding each +bug, and a step's bed is run when that step is finished rather than while it is being written. + +## What the work is, measured + +Counted 2026-09-26, non-test source only. The point of counting is that none of this is unknown +territory: every piece has a shape already standing beside it. + +| Piece | Today | Size | Becomes | +|---|---|---|---| +| the controller's link | Go, one package | ~1 800 lines | the same package on NATS | +| the host's link | Go, one package, mirroring the contracts rather than importing them | ~1 000 lines | the same, on NATS | +| the tool runtime's client | TypeScript, one file | ~390 lines | the same, on NATS | +| the sdk's messaging surface | TypeScript: messaging, events, tools, contracts, primitives | ~360 lines across five | **unchanged**, see below | +| the broker module | the adopted AMQP broker: client, tools, provisioner, bootstrap, image, manifest | ~340 lines of module code | the `nats` module, same shape | +| the beds | 39 lab scenarios, including a broker bed, an adoption bed, a genesis bed and a store-window bed | — | four analogues and one new | + +**Two measurements are worth stating on their own, because they change what the steps are.** + +**The sdk speaks no AMQP, and never did.** The word appears in its source three times, in three +comments; its messaging module says in as many words that it "carries the contract, not a specific +AMQP client build." [ADR 0039](../../02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md) put the +client in the runtime, and the payoff is collected here: **no module is rebuilt for this change, and +the sdk's own diff is three comments.** That is the whole reason a bus can be replaced under a live +mesh at all. + +**The wire therefore has three implementations, not two, and no suite pins any of them.** +[ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md) spoke of "the existing +two implementations" — Go and TypeScript. Measured, the Go side is *two separate packages* that +mirror rather than share (the host imports nothing, by +[ADR 0005](../../02-DECISIONS/0005-the-node-host.md)), so the count is the controller's link, the +host's link, and the runtime's client. And a search for conformance fixtures finds none anywhere in +the four repositories: design 22's Phase 1.2 — the suite — has not been built. + +> **A correction to ADR 0115, found in the measuring.** That record says step 3's fixtures are +> *recaptured* on NATS. There is nothing to recapture: the suite does not exist. Step 3 **builds** +> it, and its first job is to pin the wire the mesh has before changing it, because a suite written +> only against the new bus certifies whatever the new bus happens to do. The record's decision is +> unaffected; the word was wrong, and is corrected here rather than edited there +> ([`02-DECISIONS/README.md`](../../02-DECISIONS/README.md): a record's meaning is never edited). + +## The order the work actually allows + +**The five steps are chunks of capability; the build order is not simply 1 to 5, and pretending +otherwise would put two beds where they cannot run.** Three edges decide it: + +- **A specification precedes the implementations it governs.** ADR 0074's whole argument is that + agreement is specified and checked, not hoped for. So the wire's NATS binding is written *before* + the three implementations are, even though it is step 3 — and its conformance half can only + *finish* once two implementations exist to disagree. +- **A mesh cannot be raised on a bus nothing speaks.** A bed that raises a mesh on NATS from + genesis — enrolling a node, holding a push while the store restarts, rolling out an upgrade — + needs the controller and the host to speak NATS already. That is the implementations, and they + arrive with step 3. +- **Adoption needs the module and nothing else.** Step 2 puts a correctly configured server into a + running mesh that continues to ignore it, which depends on no link at all. + +So step 1's bed proves *the server, from genesis, configured* — not a mesh living on it. The full +genesis bed is step 4's, where it can first run. + +> **This differs from ADR 0115's check list, deliberately.** That record attributes "a mesh raised +> on NATS from genesis" to step 1. Measuring the dependency showed that bed cannot run until the +> links exist, and a step whose proof cannot run is the exact failure the record was written to +> prevent. The five steps, their names and the single rollout are unchanged; only where two beds +> run has moved. If that reads as a change of meaning rather than a correction of fact, the fix is +> a superseding record, not an edit. + +``` +step 1 module, genesis places it ──┐ +step 2 adoption into a running mesh ──┤ neither needs a link + │ +step 3 the wire specified ──► three implementations ──► the suite + │ +step 4 the flows, and the full genesis bed + │ +step 5 the rollout +``` + +## Step 1 — the module, and genesis raises it + +**Why here.** Everything else needs a server to talk to, and genesis is where the foundation is +defined. The mesh this is for will never travel this path — it is already running, and takes step 2 +— but genesis is the definition every other path is measured against, and one that exists only on +paper is wrong until there is a second mesh to find out. + +- [ ] 1.1 the `nats` module: manifest, image, one container, its client, TLS and monitoring ports, + JetStream on a named volume — the shape of design 25 §5, and the same shape the broker module + beside it already has +- [ ] 1.2 the composed configuration as a **directory** resource, and the entrypoint that watches + the one file and signals the server itself — design 25 §5's correction, kept inside the module + because a container has no reload and a recreate would drop every connection the mesh has +- [ ] 1.3 the controller composes that file: accounts, permissions, TLS, JetStream — permissions + derived from `emits` and `consumes` and nothing else, plus each user's own ack subject and its + own inbox prefix (design 25 §4) +- [ ] 1.4 the four streams, created at genesis and asserted idempotently on start, by the controller + as their only writer +- [ ] 1.5 genesis raises it as foundation, claiming the seat **`mesh-broker`** — the seat is the + server's role, not the product +- [ ] 1.6 the genesis-broker bed + +**Done when.** A mesh raised from nothing has the server standing with the streams asserted and +every account and permission composed from the manifests; the monitoring port is refused from +anything but the private network; a change to the composed file is live within one watcher interval +without a restart, and the container is not recreated by it. No mesh traffic is on it yet — that is +step 4's bed, not this one's. + +## Step 2 — adoption puts it in the seat + +**Why here.** It needs only step 1's module, it is the path the mesh that exists will actually take, +and it is what makes steps 3 and 4 safe to develop against a live mesh. A running mesh does not get +a foundation module by being raised again; it adopts one in place +([ADR 0100](../../02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md)). + +- [ ] 2.1 the server raised beside the existing broker on its own ports, carrying nothing +- [ ] 2.2 the `nats` module adopted onto it in place, holding the data and configuration it was + raised with +- [ ] 2.3 the seat claim, and the resolver's refusal of a second holder mesh-wide +- [ ] 2.4 the adoption bed + +**Done when.** A mesh already running has the server adopted, holding `mesh-broker`; a second +assignment anywhere is refused at resolution — *one per mesh*; and every node is still on the old +bus with nothing routed to the new one. **That last check is the point of the step**: adoption that +quietly carried traffic would be step 5 arriving early and unrehearsed. + +## Step 3 — the protocol on NATS + +**Why here.** The implementations cannot be written against an unwritten wire, and this is the step +that decides what "agreeing" means for everything after it. It is the largest step and the one that +pays for itself furthest away. + +- [ ] 3.1 **the suite first, on the bus the mesh has** — fixtures for the envelope and its required + headers, the contributions file, a served tool call and a grant, capturing what the three + implementations do *today*. Written first because a suite born on the new bus certifies + whatever the new bus happens to do +- [ ] 3.2 design 19 rewritten from exchanges, queues and routing keys to the subjects and streams of + design 25 §2–§3, per capability, with ADR 0074's model untouched: floor plus capabilities, an + implementation legitimate when it claims less, identity from the sealed credential, dedup on + `x-event-id` +- [ ] 3.3 the fixtures restated on NATS, and the capability each implementation claims +- [ ] 3.4 the controller's link on NATS +- [ ] 3.5 the host's link on NATS — mirroring, still importing nothing +- [ ] 3.6 the tool runtime's client on NATS, behind the unchanged sdk contract +- [ ] 3.7 the sdk's three stale comments, and nothing else in it + +**Done when.** The fixtures are produced and consumed byte for byte by every implementation that +claims the capability, and a module built before any of this serves its tools unchanged on the new +runtime. **The step is not done when the code runs** — two implementations that disagree about an +envelope do not fail to compile, they ignore each other while both keep running, which is the +failure ADR 0074 exists to catch. + +**And the shared library gained nothing but the binding.** A new transport is when the pressure to +add conveniences is highest, and ADR 0039's rule does not bend for it: a helper that arrives with +the bus is a review failure, not a detail. Code shared among a module's own features stays in that +module. + +## Step 4 — the core speaks it + +**Why here.** The links exist from step 3, so the flows that are not on the bus at all can move onto +it, and the beds that need a mesh living on NATS can finally run. + +- [ ] 4.1 **the full genesis bed** — a mesh raised on NATS from nothing: a node enrols over TLS with + a claimed token and the enrolment user cannot read a declaration; a push is held while the + store restarts and applies after, nothing lost or duplicated; a node that was away gets + exactly the newest declaration and refuses a replayed older one by sequence; an upgrade rolls + out to two nodes; an event dead-letters after `max-deliver`; a module cannot publish outside + its `emits`, ack another module's delivery, or subscribe another's inbox prefix; and an + enrolment held by a `nak`-with-delay cycle still reaches the enrolling node, proving the reply + travels in the payload and not the transport field the consumer's ack has claimed +- [ ] 4.2 a build source's change reaches the builder over the bus, and the build that follows is + the one the change asked for +- [ ] 4.3 an installation completes over the bus, with the same outcome as the path it replaces +- [ ] 4.4 a person's client: the account, the client that speaks the bus, and the tool surface over + it (design 25 §7) — a module's tool invoked from another node and from a person, refused from + an account that may not +- [ ] 4.5 reports and catch-up: a node that was unreachable catches up rather than losing them + +**Done when.** Each converted flow is proved against the behaviour it replaced, and the full genesis +bed is green. **Observation is not in this step** — heartbeats, conditions and key-value state are +[research 017](../../01-RESEARCH/017-a-mesh-that-heals-itself/00-overview.md)'s, that effort already +reserves them for after the move, and a flow built ahead of its design would be rebuilt. + +## Step 5 — the rollout + +**Why here.** It is the only step that moves a node's bus, and it moves every node's at once. + +- [ ] 5.1 the cutover bed: a mesh on AMQP with a predecessor stand-in on the compatibility broker + moves its bus in one rollout, every node reporting on NATS afterwards, the stand-in's own + client still connected throughout +- [ ] 5.2 the rollout: accounts composed, then the controller, every host and every runtime + together; every node confirmed heard before AMQP stops +- [ ] 5.3 the mesh's accounts removed from the compatibility broker, leaving the predecessor's users +- [ ] 5.4 the compatibility broker retires when its condition holds — no client connected for the + period the operator sets + +**Done when.** Every node reports on NATS and the predecessor's clients never noticed. + +## The through-line + +The order is dependency, not preference. **Steps 1 to 4 leave every node on AMQP**, so the cost of +being wrong is bounded until the last step: a step may be abandoned, or reordered after step 2, +without a rollback. The server stands before anything speaks to it; the wire is specified before it +is implemented three times; the flows move once there is something to move them onto; and the bus +itself moves once, at the end, on one day. + +## What is deliberately not here + +- **Observation** — research 017's, after the move, by its own design. +- **Leaf nodes** — design 25 §11 keeps this out of scope and says so; a leaf per machine is a later + question, noted so it is not forgotten. +- **The predecessor's world.** It is AMQP, it cannot move, and it does not need to: its broker is + the compatibility module until its last client is gone. + +## How this list is kept true + +A task is ticked when its change is committed, not when it is written. A step is done when its bed +is green, not when its tasks are ticked. If a step's tasks are all ticked and its bed has not run, +the step is **in progress** and this document says so — that gap is the thing the whole shape is +built to make visible. diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index 3c8f3d3..7e08068 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -34,8 +34,10 @@ document is written and this one's status becomes `implemented`. | [`22-the-work-ahead.md`](22-the-work-ahead.md) | Everything decided and not yet built, in dependency order, each phase ending at a run | [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md), [ADR 0075](../../02-DECISIONS/0075-two-stores-and-which-provides-what.md), [ADR 0014](../../02-DECISIONS/0014-no-npm-workspace.md) | | [`23-choosing-a-provider.md`](23-choosing-a-provider.md) | Which of several providers of a kind serves a consumer, and when a module carries its own instead | [ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md), [ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md) | | [`24-the-secrets-vault.md`](24-the-secrets-vault.md) | The module that owns a secret — a `secret` provision, and the boundary of what it owns | [ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md), [ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md), [ADR 0048](../../02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md) | +| [`25-the-bus-on-nats.md`](25-the-bus-on-nats.md) | **Proposed.** The architecture of the mesh's bus on NATS: what rides which subject under which guarantee and whose account, how a node joins, how a person reaches a tool, and how the mesh moves from the bus it has | [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md), [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md), [ADR 0043](../../02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md), [ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md) | | [`26-the-seats.md`](26-the-seats.md) | **Proposed.** What a mesh can have one of, who fills each, and a seat's holder answering for the provision it delivers — including the `git` seat a build's source can live on | [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), [ADR 0111](../../02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md), [ADR 0109](../../02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md) | | [`27-a-module-requires-the-mesh-resolves.md`](27-a-module-requires-the-mesh-resolves.md) | **Proposed.** One concept for everything a module needs: a requirement with a contract, answered by one of four kinds of provider, resolved at assignment or refused. Retires settings, placeholders, facts and paths in definitions | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md), [ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md), [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md) | +| [`28-building-the-bus.md`](28-building-the-bus.md) | **Proposed.** The five steps of the bus work in the order their dependencies allow, each ending at a bed — with the surface measured, so no step's size is a guess | [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md), [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md), [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md) | ## Not yet written From 1c808898a5f07e9503334da83dab31cb6544634e Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 18:39:38 +0200 Subject: [PATCH 3/4] Allow progressive insight, and apply two to the bus record MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A record can assert a fact that goes stale while the decision it supports stays right. Superseding for that buries a sound record under a second one and makes every reader work out which is live. So a correction of fact is now made in place, marked and dated, with the old wording quoted — bounded by three conditions and checked by records.py, which fires on an unmarked, undated or back-dated note. Judgements still supersede. Applied to 0115: no conformance suite exists to recapture, and the full genesis bed cannot run until the links exist. Designs 25 and 28 follow. --- 00-META/checks/records.py | 54 +++++++++++++++++++ 00-META/process/02-graduation.md | 8 ++- .../0115-the-bus-is-built-in-five-steps.md | 51 ++++++++++++++---- 02-DECISIONS/README.md | 41 +++++++++++++- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 54 ++++++++++--------- 03-DESIGN/01-to-be/28-building-the-bus.md | 53 +++++++++--------- AGENTS.md | 8 ++- 7 files changed, 205 insertions(+), 64 deletions(-) diff --git a/00-META/checks/records.py b/00-META/checks/records.py index 83e3d1d..0484dd8 100644 --- a/00-META/checks/records.py +++ b/00-META/checks/records.py @@ -284,6 +284,59 @@ def check_numbering(failures, records): ) +def check_progressive_insights(failures, records): + """A correction made inside a record is marked and dated, or it is a silent rewrite. + + A record may be corrected in place when a *fact* in it went stale and the decision still + stands (`02-DECISIONS/README.md`, "Progressive insight"). The whole safety of that allowance + is that the correction is legible in the record rather than only in a diff nobody reads, so + the form is what is checked here: every mention of an insight is the marker, the marker + carries an ISO date, and that date is not earlier than the decision's own — an insight + predating the decision it corrects is a copied marker, not a correction. + + What this cannot check is an edit made with no marker at all. Nothing mechanical can; that + one is the reviewer's, reading the diff. The check keeps the *marked* path honest so that an + unmarked change stands out as the anomaly it is. + """ + phrase = re.compile(r"progressive insight", re.I) + marker = re.compile(r"\*\*Progressive insights?\s*[\u2014\u2013-]\s*(\d{4}-\d{2}-\d{2})\.?\*\*") + loose = re.compile(r"\*\*[^*]*[Pp]rogressive insights?[^*]*\*\*") + iso = re.compile(r"^\d{4}-\d{2}-\d{2}$") + + for number, record in sorted(records.items()): + text = record["text"] + if not phrase.search(text): + continue + decided = str(record["front"].get("date", "")) + good = [(m.start(), m.end(), m.group(1)) for m in marker.finditer(text)] + + for m in loose.finditer(text): + if any(s <= m.start() and m.end() <= e for s, e, _ in good): + continue + failures.add("insights", rel(record["path"]), + "a progressive insight is not in the dated marked form " + "'**Progressive insight \u2014 YYYY-MM-DD.**': %s" % m.group(0)) + + for _, _, stamp in good: + if decided and iso.match(decided) and stamp < decided: + failures.add("insights", rel(record["path"]), + "a progressive insight dated %s predates the decision (%s)" + % (stamp, decided)) + + covered = [(s, e) for s, e, _ in good] + for m in phrase.finditer(text): + if any(s <= m.start() and m.end() <= e for s, e in covered): + continue + line = text.rfind("\n", 0, m.start()) + 1 + if text[line:m.start()].lstrip().startswith("#"): + continue + if loose.search(text, line, text.find("\n", m.end()) + 1 or len(text)): + continue + failures.add("insights", rel(record["path"]), + "'progressive insight' appears unmarked; a correction is marked and " + "dated, or it is a silent rewrite") + + def check_status_against_code(failures): """A design document naming specific code may not still call itself `designed`. @@ -325,6 +378,7 @@ def main(): check_numbering(failures, records) check_topics(failures, records) check_status_against_code(failures) + check_progressive_insights(failures, records) print(f"records: {len(records)} decision records checked") return failures.report() diff --git a/00-META/process/02-graduation.md b/00-META/process/02-graduation.md index e135f16..487c293 100644 --- a/00-META/process/02-graduation.md +++ b/00-META/process/02-graduation.md @@ -33,7 +33,10 @@ A design changes only through a decision. 1. Write the decision record. If it reverses an earlier one, the earlier record's `status:` - becomes `superseded-by: 02-DECISIONS/NNNN-....md` — **its text is never edited**. + becomes `superseded-by: 02-DECISIONS/NNNN-....md` — **its reasoning is never rewritten**. If the + earlier record is sound and only a *fact* in it went stale, that is a **progressive insight**, + corrected in place and marked in the record rather than superseded + ([`02-DECISIONS/README.md`](../../02-DECISIONS/README.md)). 2. Edit the to-be design document and set `updated:` to today. 3. If the amendment came from an issue, set that issue's `amended-design:` to the document path. @@ -54,4 +57,5 @@ Implementation state is a third axis, independent of both design and decision. - Do not move a to-be document into `00-as-is/`. Write the as-is document; both stand. - Do not edit an as-is document to describe an intention. That is what the to-be layer is for. -- Do not change a decision record's meaning. Supersede it. +- Do not change a decision record's meaning. Supersede it. Correcting a fact it got wrong, while + the decision stands, is a progressive insight — marked and dated in the record, never silent. diff --git a/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md b/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md index 2d4c9ed..b980838 100644 --- a/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md +++ b/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md @@ -61,8 +61,9 @@ may not disagree about* still describes the bus being replaced. waits for the one after it. The cutover remains a single rollout** — dividing the build does not divide the bus. -**Step 1 — genesis raises the broker.** The `nats` module and a mesh raised on it from nothing. -This is built and proven even though the mesh it is for will never travel this path, because +**Step 1 — genesis raises the broker.** The `nats` module, and genesis placing it in the +foundation. This is built and proven even though the mesh it is for will never travel this path, +because genesis is where the foundation is *defined*: the only place the mesh comes from nothing, and the definition every other path is measured against. A genesis path that exists only on paper is one nobody discovers is wrong until there is a second mesh. @@ -78,8 +79,9 @@ renamed by every change the seat exists to survive. protocol, an SDK is an implementation of it in one language and nothing more, the protocol is split per capability, and conformance is executable fixtures per capability rather than prose. What changes is what the specification specifies. Design 19's wire section is rewritten from exchanges -and queues to subjects and streams; the fixtures are recaptured on NATS; every SDK claims the -capabilities it passes, and a language may arrive with connection and events alone. +and queues to subjects and streams; the conformance suite is built — first against the bus the +mesh has, then restated on NATS; every SDK claims the capabilities it passes, and a language may +arrive with connection and events alone. **The SDK gains no conveniences in the process.** [ADR 0039](0039-what-the-sdk-holds-and-refuses.md) refuses frequent-and-cascading code in the shared library, and ADR 0074 restates it — an SDK is @@ -119,17 +121,48 @@ is still on AMQP throughout. The bus moves on one day, at the end, once. ## How it is checked - **Per step, a bed, and the bed named in design 25 §10 against the step it belongs to.** Step 1: - a mesh raised on NATS from genesis. Step 2: the broker adopted into a mesh already running, and - a second holder of `mesh-broker` refused at resolution. Step 3: the conformance suite passing per - capability, on NATS, for every SDK that claims it — and a module built before the binding serving - its tools unchanged. Step 4: each converted flow proved against the behaviour it replaced. Step 5: - the cutover bed, then the rollout with every node reporting. + a mesh raised from nothing has the server standing, its streams asserted and every account + composed from the manifests — no mesh traffic on it yet. Step 2: the broker adopted into a mesh + already running, a second holder of `mesh-broker` refused at resolution, and nothing routed to it. + Step 3: the conformance suite passing per capability, on NATS, for every implementation that + claims it — and a module built before the binding serving its tools unchanged. Step 4: each + converted flow proved against the behaviour it replaced, **and the full genesis bed** — a mesh + enrolling, holding a push, and rolling out an upgrade on NATS. Step 5: the cutover bed, then the + rollout with every node reporting. - **Step 3 is not done when the code runs.** It is done when the fixtures match byte for byte across implementations, which is ADR 0074's own test and the only one that catches two SDKs quietly ignoring each other. - **A step that cannot name what its bed proves is not a step**, and is divided further before it is started. +## Progressive insights + +Corrections of fact made in this record after it was accepted, under the rule in +[`README.md`](README.md). The decision — five steps, each proved, one rollout — is untouched by +both; each corrects something this record asserted about the *state of the code*, which nobody had +measured when it was written. + +> **Progressive insight — 2026-09-26.** *There is no conformance suite to recapture.* This record +> said step 3's "fixtures are recaptured on NATS", and design 19's wire was described as specified +> and conformed. Measuring the four repositories found no conformance fixtures in any of them: +> [design 22](../03-DESIGN/01-to-be/22-the-work-ahead.md)'s Phase 1.2, which would have built the +> suite, is still open. Step 3 therefore **builds** it, and builds it first against the bus the +> mesh has — a suite born on the new bus certifies whatever the new bus happens to do. The step's +> place in the order, and ADR 0074's model it implements, are unchanged. + +> **Progressive insight — 2026-09-26.** *The full genesis bed belongs to step 4, not step 1.* This +> record attributed "a mesh raised on NATS from genesis" to step 1, and described that step as "the +> `nats` module and a mesh raised on it from nothing". A bed that enrols a node, holds a push and +> rolls out an upgrade needs the controller and the host to speak NATS — which is step 3's +> implementations and step 4's flows. A step whose proof cannot run is exactly the failure this +> record was written to prevent, so step 1 now ends at the server standing from genesis, correctly +> configured and carrying nothing, and the full bed is named under step 4. The five steps, their +> names, their order and the single rollout are unchanged; only where two beds run has moved. +> +> Recorded here rather than superseded because the decision this record makes — that the work is +> divided and each division is proved — is what *produced* the correction: the dependency was +> invisible while the build was one item. + ## References - [ADR 0106](0106-the-bus-is-nats.md) — the decision this divides. diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index a54f2ac..09145ed 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -8,8 +8,45 @@ the decision is recorded here, and only then is the design written. Following th numbers walks the process in the order it happens. One file per decision, numbered, never deleted. A superseded record has its `status:` changed -and gains a pointer to what replaced it — **its text is never edited**. The reasoning that was -rejected is the expensive half to rediscover. +and gains a pointer to what replaced it — **its reasoning is never rewritten**. The reasoning that +was rejected is the expensive half to rediscover. + +## Progressive insight + +A record is a decision, not a snapshot of everything that was true the day it was written, and +those two fail differently. **A fact a record asserted can turn out to be wrong while the decision +it supports stays right** — a count taken before anyone measured, a file named that does not +exist, a proof attributed to a step that cannot run it. Superseding a record for that buries a +correct decision under a second one, and teaches every reader to first work out which of two +records is live. Done a few times, the reading order stops being one. + +So: **a correction of fact that leaves the decision standing is made in the record, in place, +marked and dated.** + +> **Progressive insight — YYYY-MM-DD.** What was found, what the record said before, and what it +> says instead. + +Three conditions, all of which hold: + +- **It corrects a fact, not a judgement.** That a suite does not exist is a fact. That building it + is the wrong order is a judgement, and judgements supersede. +- **It adds; it never quietly replaces.** Where body text changes, the note says what stood there + before, so a reader who followed a citation to the old wording can find out what happened to it. + A correction nobody can see is indistinguishable from a record that was always right, which is + the failure the immutability rule exists to prevent. +- **The decision, the options weighed and the consequences stand untouched.** If the correction + changes what was decided, which alternatives were rejected, or a consequence another record + relies on, it is not an insight — write the superseding record. + +**What still supersedes**, without exception: reversing a decision, changing its scope, rejecting +an option it accepted, or making a consequence false that a later record cites. The test is not +how large the edit looks in a diff; it is whether a reader who acted on the old text would now be +wrong about *what was decided* rather than about *a detail the decision did not rest on*. + +**How this is checked.** `00-META/checks/records.py` requires every insight to be marked in the +form above and dated no earlier than the record's own `date:` — an unmarked edit is a rule +violation the reviewer looks for in the diff, and a marked one is legible in the record itself. +The git history is the backstop, not the record of intent; the note is the record of intent. The records run in the order the decisions were taken, oldest first. 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 0b0fc45..77f70ca 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 @@ -316,26 +316,20 @@ including the two places their dependencies put a bed later than the step that n real if the proofs divide with it. A step that cannot name what its bed proves is not a step, and is divided further before it is started. -**Step 1 — the genesis bed**, a mesh raised on NATS from nothing: -- a node enrols over TLS with a claimed token, and the enrolment user cannot read a declaration; -- a push composes; the store is stopped; the push is held (nak with delay), the store returns, the - push applies, nothing was lost or duplicated; -- a node that was away gets exactly the newest declaration, and a replayed older one is refused - by sequence; -- an upgrade rolls out to two nodes; -- a module's tool is invoked from another node and from a person's client, each with an account - that can invoke it, and refused from one that cannot; -- a module's account cannot publish outside its `emits` nor subscribe outside its `consumes` — - refused by the server; -- a module acks a delivery from its own durable consumer, and is refused acking another module's; -- a user subscribes another module's or person's inbox prefix and is refused by the server, not - by the client's own good behaviour; -- an event whose consumer keeps failing dead-letters after `max-deliver`; -- an enrolment request held by a `nak`-with-delay cycle still reaches the enrolling node's inbox - once the controller answers — proving the reply travels in the payload and not the transport - field a consumer's ack has already claimed; -- the `nats` container is not recreated when only its composed configuration file changes, and - a change to that file is live (a new user can connect, a revoked one cannot) within one +**Step 1 — the genesis-broker bed**, a mesh raised from nothing, the server standing on it. The +mesh does not yet *live* on this bus — nothing speaks it until step 3's implementations exist — so +what this bed proves is the server, its configuration and the permissions, each of which the server +itself enforces and a plain client can therefore check: +- the four streams exist, asserted idempotently on a second start, with the retention of §3; +- every account and permission in the composed file is derived from the manifests' `emits` and + `consumes` and nothing else, with each user's own ack subject and its own inbox prefix; +- a user cannot publish outside its `emits` nor subscribe outside its `consumes` — refused by the + server, not by convention; +- a user cannot ack another user's delivery, and cannot subscribe another's inbox prefix — refused + by the server, not by the client's own good behaviour; +- the monitoring port is refused from anything but the private network; +- the `nats` container is not recreated when only its composed configuration file changes, and a + change to that file is live (a new user can connect, a revoked one cannot) within one watcher-poll interval, without a restart. **Step 2 — the adoption bed**, a mesh already running that has never had this server: @@ -358,14 +352,24 @@ divided further before it is started. - the shared library gained nothing but the binding: its surface is the protocol and the primitives, and a helper that arrived with the transport is a review failure, not a detail. -**Step 4 — each converted flow against the behaviour it replaced:** +**Step 4 — the mesh living on it.** The implementations exist from step 3, so this is where a mesh +can first be raised on NATS and run: +- a node enrols over TLS with a claimed token, and the enrolment user cannot read a declaration; +- a push composes; the store is stopped; the push is held (nak with delay), the store returns, the + push applies, nothing was lost or duplicated; +- a node that was away gets exactly the newest declaration, and a replayed older one is refused + by sequence; +- an upgrade rolls out to two nodes; +- an enrolment request held by a `nak`-with-delay cycle still reaches the enrolling node's inbox + once the controller answers — proving the reply travels in the payload and not the transport + field a consumer's ack has already claimed; +- an event whose consumer keeps failing dead-letters after `max-deliver`; +- a module's tool is invoked from another node and from a person's client, each with an account + that can invoke it, and refused from one that cannot; - a build source's change reaches the builder over the bus, and the build that follows is the one the change asked for; - an installation completes over the bus, with the same outcome the path it replaces produced; -- a module's reports arrive, and a node that was unreachable catches up rather than losing them; -- nothing of research 017's observation work is present — no heartbeat semantics, no condition - store — because that design is not written yet and a flow built ahead of it would have to be - rebuilt. +- a node that was unreachable catches up on its reports rather than losing them. **Step 5 — the cutover bed:** a mesh on AMQP with a predecessor stand-in on the compatibility broker moves its bus in one rollout; every node reports on NATS afterwards; the stand-in's client diff --git a/03-DESIGN/01-to-be/28-building-the-bus.md b/03-DESIGN/01-to-be/28-building-the-bus.md index ca5f952..d96aebb 100644 --- a/03-DESIGN/01-to-be/28-building-the-bus.md +++ b/03-DESIGN/01-to-be/28-building-the-bus.md @@ -65,12 +65,12 @@ mirror rather than share (the host imports nothing, by host's link, and the runtime's client. And a search for conformance fixtures finds none anywhere in the four repositories: design 22's Phase 1.2 — the suite — has not been built. -> **A correction to ADR 0115, found in the measuring.** That record says step 3's fixtures are -> *recaptured* on NATS. There is nothing to recapture: the suite does not exist. Step 3 **builds** -> it, and its first job is to pin the wire the mesh has before changing it, because a suite written -> only against the new bus certifies whatever the new bus happens to do. The record's decision is -> unaffected; the word was wrong, and is corrected here rather than edited there -> ([`02-DECISIONS/README.md`](../../02-DECISIONS/README.md): a record's meaning is never edited). +> **This corrected the record.** ADR 0115 said step 3's fixtures were *recaptured* on NATS. There +> is nothing to recapture, so step 3 **builds** the suite, and its first job is to pin the wire the +> mesh has before changing it — a suite written only against the new bus certifies whatever the new +> bus happens to do. A fact went stale while the decision stood, which is a **progressive insight** +> ([`02-DECISIONS/README.md`](../../02-DECISIONS/README.md)): it is marked and dated in ADR 0115 +> itself rather than left to be discovered here. ## The order the work actually allows @@ -91,12 +91,12 @@ otherwise would put two beds where they cannot run.** Three edges decide it: So step 1's bed proves *the server, from genesis, configured* — not a mesh living on it. The full genesis bed is step 4's, where it can first run. -> **This differs from ADR 0115's check list, deliberately.** That record attributes "a mesh raised -> on NATS from genesis" to step 1. Measuring the dependency showed that bed cannot run until the -> links exist, and a step whose proof cannot run is the exact failure the record was written to -> prevent. The five steps, their names and the single rollout are unchanged; only where two beds -> run has moved. If that reads as a change of meaning rather than a correction of fact, the fix is -> a superseding record, not an edit. +> **This corrected the record too.** ADR 0115 first attributed "a mesh raised on NATS from genesis" +> to step 1. That bed cannot run until the links exist, and a step whose proof cannot run is the +> exact failure the record was written to prevent — so step 1 now ends at the server standing, +> correctly configured and carrying nothing, and the full bed is named under step 4. The five +> steps, their names, their order and the single rollout are unchanged; only where two beds run has +> moved. Marked and dated in ADR 0115 as a progressive insight, with what the record said before. ``` step 1 module, genesis places it ──┐ @@ -132,10 +132,15 @@ paper is wrong until there is a second mesh to find out. - [ ] 1.6 the genesis-broker bed **Done when.** A mesh raised from nothing has the server standing with the streams asserted and -every account and permission composed from the manifests; the monitoring port is refused from -anything but the private network; a change to the composed file is live within one watcher interval -without a restart, and the container is not recreated by it. No mesh traffic is on it yet — that is -step 4's bed, not this one's. +every account and permission composed from the manifests; a user cannot publish outside its +`emits`, subscribe outside its `consumes`, ack another user's delivery or subscribe another's inbox +prefix; the monitoring port is refused from anything but the private network; a change to the +composed file is live within one watcher interval without a restart, and the container is not +recreated by it. + +**The permission checks belong here rather than later** because the server enforces them itself — a +plain client proves them, no link required — and they are the whole of what ADR 0043 asks for. No +mesh traffic is on the bus yet; that is step 4's bed, not this one's. ## Step 2 — adoption puts it in the seat @@ -191,14 +196,14 @@ module. **Why here.** The links exist from step 3, so the flows that are not on the bus at all can move onto it, and the beds that need a mesh living on NATS can finally run. -- [ ] 4.1 **the full genesis bed** — a mesh raised on NATS from nothing: a node enrols over TLS with - a claimed token and the enrolment user cannot read a declaration; a push is held while the - store restarts and applies after, nothing lost or duplicated; a node that was away gets - exactly the newest declaration and refuses a replayed older one by sequence; an upgrade rolls - out to two nodes; an event dead-letters after `max-deliver`; a module cannot publish outside - its `emits`, ack another module's delivery, or subscribe another's inbox prefix; and an - enrolment held by a `nak`-with-delay cycle still reaches the enrolling node, proving the reply - travels in the payload and not the transport field the consumer's ack has claimed +- [ ] 4.1 **the full genesis bed** — a mesh raised on NATS from nothing and living on it: a node + enrols over TLS with a claimed token and the enrolment user cannot read a declaration; a push + is held while the store restarts and applies after, nothing lost or duplicated; a node that + was away gets exactly the newest declaration and refuses a replayed older one by sequence; an + upgrade rolls out to two nodes; an event dead-letters after `max-deliver`; and an enrolment + held by a `nak`-with-delay cycle still reaches the enrolling node, proving the reply travels + in the payload and not the transport field the consumer's ack has claimed. The server-enforced + permissions were proved at step 1 and are not re-proved here - [ ] 4.2 a build source's change reaches the builder over the bus, and the build that follows is the one the change asked for - [ ] 4.3 an installation completes over the bus, with the same outcome as the path it replaces diff --git a/AGENTS.md b/AGENTS.md index 82d7069..71762df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,8 +52,12 @@ vocabulary — *controller* (not "control plane"), *foundation* (not "substrate" docs (`layer`, `status`, `code`, `updated`), issue reports (`status`, `located-in`, `fixed-by`, `amended-design`) and decision records (`status`, `date`, `deciders`). Never create a central status file; cross-cutting views are generated from frontmatter. -- **`02-DECISIONS/` records are immutable.** Supersede with a new record; never edit meaning. Fixing a - broken link or path is allowed. +- **`02-DECISIONS/` records hold their meaning.** Supersede with a new record rather than rewriting + what was decided, the options weighed, or a consequence another record relies on. Fixing a broken + link or path is allowed, and so is a **progressive insight** — a correction of *fact* that leaves + the decision standing, made in place, marked and dated in the record's own words + ([`02-DECISIONS/README.md`](02-DECISIONS/README.md)). A fact going stale is not the decision going + wrong, and superseding a sound record for one buries it. - **Design docs are prose and diagrams only** — no code. A manifest field may be named; a manifest may not be pasted. - **Two layers, never mixed.** [`03-DESIGN/00-as-is/`](03-DESIGN/00-as-is/) describes the mesh From 77a1493df4c569fc5ef8b1132deb03ae64e6c028 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 18:56:34 +0200 Subject: [PATCH 4/4] Renumber to 0116: another record took 0115 on main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #133 landed a different 0115 while this branch was open. The bus record is now 0116, with every citation in designs 19, 25, 28 and the index following it. Note: cycle.py and records.py both fail on main as merged, on that record — nothing cites it, and it rests on 0112, which is still proposed. Both pre-date this branch and are left for their own change. --- ...eps.md => 0116-the-bus-is-built-in-five-steps.md} | 2 +- 02-DECISIONS/README.md | 3 ++- 03-DESIGN/01-to-be/19-the-module-protocol.md | 4 ++-- 03-DESIGN/01-to-be/25-the-bus-on-nats.md | 8 ++++---- 03-DESIGN/01-to-be/28-building-the-bus.md | 12 ++++++------ 03-DESIGN/01-to-be/README.md | 4 ++-- 6 files changed, 17 insertions(+), 16 deletions(-) rename 02-DECISIONS/{0115-the-bus-is-built-in-five-steps.md => 0116-the-bus-is-built-in-five-steps.md} (99%) diff --git a/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md b/02-DECISIONS/0116-the-bus-is-built-in-five-steps.md similarity index 99% rename from 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md rename to 02-DECISIONS/0116-the-bus-is-built-in-five-steps.md index b980838..19a7319 100644 --- a/02-DECISIONS/0115-the-bus-is-built-in-five-steps.md +++ b/02-DECISIONS/0116-the-bus-is-built-in-five-steps.md @@ -7,7 +7,7 @@ reconstructed: false extends: 02-DECISIONS/0106-the-bus-is-nats.md --- -# 115. The bus is built in five steps, and the protocol moves with it +# 116. The bus is built in five steps, and the protocol moves with it ## Context diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 09145ed..a70214a 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -132,7 +132,7 @@ python3 00-META/checks/index.py fail if stale - **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) - **0106** — [The bus is NATS](0106-the-bus-is-nats.md) -- **0115** — [The bus is built in five steps, and the protocol moves with it](0115-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) ### Its tiers, from the bottom up @@ -198,6 +198,7 @@ python3 00-META/checks/index.py fail if stale - **0112** — [A module definition names no node, no mesh and no path: everything it needs is a requirement the mesh resolves](0112-a-module-definition-names-no-node-mesh-or-path.md) *(proposed)* - **0113** — [The vault makes every shared secret, a provider makes resources and data, and the mesh carries both](0113-the-vault-makes-every-secret.md) *(proposed)* - **0114** — [A credential two parties hold rotates over two credentials; one a single party holds rotates in place, staged; and retiring a credential never removes what it reached](0114-a-shared-credential-rotates-over-two-credentials.md) *(proposed)* +- **0115** — [One assignment of a module per node: the module's name is the assignment's identity](0115-one-assignment-of-a-module-per-node.md) ### How it is built diff --git a/03-DESIGN/01-to-be/19-the-module-protocol.md b/03-DESIGN/01-to-be/19-the-module-protocol.md index 5f37ac8..b95b42b 100644 --- a/03-DESIGN/01-to-be/19-the-module-protocol.md +++ b/03-DESIGN/01-to-be/19-the-module-protocol.md @@ -9,7 +9,7 @@ updated: 2026-09-26 decisions: - 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md - 02-DECISIONS/0106-the-bus-is-nats.md - - 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md + - 02-DECISIONS/0116-the-bus-is-built-in-five-steps.md - 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md - 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md - 02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md @@ -40,7 +40,7 @@ describes current behaviour that is *not yet* specified-and-conformed, it says s > > Rewriting the wire sections onto the subjects and streams of > [design 25](25-the-bus-on-nats.md) §2–§3, and recapturing the fixtures there, is **step 3 of -> [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)**. Until that lands, read +> [ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md)**. Until that lands, read > the sections below for what two implementations may not disagree *about*, and design 25 for what > they will disagree about it *on*. A specification that silently described a retired transport > would be worse than an absent one, because it reads as current — hence this note rather than a 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 77f70ca..6b92b3a 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 @@ -10,7 +10,7 @@ code: updated: 2026-09-26 decisions: - 02-DECISIONS/0106-the-bus-is-nats.md - - 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md + - 02-DECISIONS/0116-the-bus-is-built-in-five-steps.md - 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md - 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md - 02-DECISIONS/0100-a-node-in-use-is-adopted-before-it-is-converged.md @@ -237,7 +237,7 @@ message body. A module built today runs on the new runtime without a rebuild — ADR 0039, and it is in §10. **The wire underneath it changes completely, and that is a specification, not an implementation -detail.** Revision, second review ([ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)): +detail.** Revision, second review ([ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md)): an earlier draft of this section said "nothing new" and stopped there, which read as though the change were contained inside the runtime. It is not. [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md) settled that an SDK is an @@ -258,7 +258,7 @@ nothing is added to it here. ## 9. Moving from the bus the mesh has: five steps Per ADR 0106 the bus moves once. Per -[ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md) the *build* is five steps, +[ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md) the *build* is five steps, each ending at something §10 proves, so that no part of this waits on the whole of it. Dividing the build does not divide the bus: steps 1 to 4 leave every node on AMQP, and step 5 is still one rollout. @@ -382,7 +382,7 @@ to mapping the sdk contract onto subjects exactly as §8 says. ## 11. Open, for the review -**Closed by the second revision** (2026-09-26, [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)): +**Closed by the second revision** (2026-09-26, [ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md)): the build was one undivided item (§9 is now five steps, each with its own bed in §10); there was no adoption path for a mesh already running (§9 step 2); and §8 said a module sees "nothing new" without distinguishing the sdk's contract, which does not change, from the specified wire, which diff --git a/03-DESIGN/01-to-be/28-building-the-bus.md b/03-DESIGN/01-to-be/28-building-the-bus.md index d96aebb..b0352f9 100644 --- a/03-DESIGN/01-to-be/28-building-the-bus.md +++ b/03-DESIGN/01-to-be/28-building-the-bus.md @@ -4,7 +4,7 @@ status: proposed code: [] updated: 2026-09-26 decisions: - - 02-DECISIONS/0115-the-bus-is-built-in-five-steps.md + - 02-DECISIONS/0116-the-bus-is-built-in-five-steps.md - 02-DECISIONS/0106-the-bus-is-nats.md - 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md - 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md @@ -14,7 +14,7 @@ decisions: # 28. Building the bus -**The work of [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md)'s five steps, +**The work of [ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md)'s five steps, in the order its dependencies allow, with what each ends at.** [Design 25](25-the-bus-on-nats.md) is the architecture and stays the authority on *what is built*; this document holds only the order, the sizes and the proofs, and it is wrong the moment it @@ -65,11 +65,11 @@ mirror rather than share (the host imports nothing, by host's link, and the runtime's client. And a search for conformance fixtures finds none anywhere in the four repositories: design 22's Phase 1.2 — the suite — has not been built. -> **This corrected the record.** ADR 0115 said step 3's fixtures were *recaptured* on NATS. There +> **This corrected the record.** ADR 0116 said step 3's fixtures were *recaptured* on NATS. There > is nothing to recapture, so step 3 **builds** the suite, and its first job is to pin the wire the > mesh has before changing it — a suite written only against the new bus certifies whatever the new > bus happens to do. A fact went stale while the decision stood, which is a **progressive insight** -> ([`02-DECISIONS/README.md`](../../02-DECISIONS/README.md)): it is marked and dated in ADR 0115 +> ([`02-DECISIONS/README.md`](../../02-DECISIONS/README.md)): it is marked and dated in ADR 0116 > itself rather than left to be discovered here. ## The order the work actually allows @@ -91,12 +91,12 @@ otherwise would put two beds where they cannot run.** Three edges decide it: So step 1's bed proves *the server, from genesis, configured* — not a mesh living on it. The full genesis bed is step 4's, where it can first run. -> **This corrected the record too.** ADR 0115 first attributed "a mesh raised on NATS from genesis" +> **This corrected the record too.** ADR 0116 first attributed "a mesh raised on NATS from genesis" > to step 1. That bed cannot run until the links exist, and a step whose proof cannot run is the > exact failure the record was written to prevent — so step 1 now ends at the server standing, > correctly configured and carrying nothing, and the full bed is named under step 4. The five > steps, their names, their order and the single rollout are unchanged; only where two beds run has -> moved. Marked and dated in ADR 0115 as a progressive insight, with what the record said before. +> moved. Marked and dated in ADR 0116 as a progressive insight, with what the record said before. ``` step 1 module, genesis places it ──┐ diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index 7e08068..584c25b 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -34,10 +34,10 @@ document is written and this one's status becomes `implemented`. | [`22-the-work-ahead.md`](22-the-work-ahead.md) | Everything decided and not yet built, in dependency order, each phase ending at a run | [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md), [ADR 0075](../../02-DECISIONS/0075-two-stores-and-which-provides-what.md), [ADR 0014](../../02-DECISIONS/0014-no-npm-workspace.md) | | [`23-choosing-a-provider.md`](23-choosing-a-provider.md) | Which of several providers of a kind serves a consumer, and when a module carries its own instead | [ADR 0084](../../02-DECISIONS/0084-which-provider-serves-a-consumer.md), [ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md) | | [`24-the-secrets-vault.md`](24-the-secrets-vault.md) | The module that owns a secret — a `secret` provision, and the boundary of what it owns | [ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md), [ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md), [ADR 0048](../../02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md) | -| [`25-the-bus-on-nats.md`](25-the-bus-on-nats.md) | **Proposed.** The architecture of the mesh's bus on NATS: what rides which subject under which guarantee and whose account, how a node joins, how a person reaches a tool, and how the mesh moves from the bus it has | [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md), [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md), [ADR 0043](../../02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md), [ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md) | +| [`25-the-bus-on-nats.md`](25-the-bus-on-nats.md) | **Proposed.** The architecture of the mesh's bus on NATS: what rides which subject under which guarantee and whose account, how a node joins, how a person reaches a tool, and how the mesh moves from the bus it has | [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md), [ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md), [ADR 0043](../../02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md), [ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md) | | [`26-the-seats.md`](26-the-seats.md) | **Proposed.** What a mesh can have one of, who fills each, and a seat's holder answering for the provision it delivers — including the `git` seat a build's source can live on | [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md), [ADR 0111](../../02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md), [ADR 0109](../../02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md) | | [`27-a-module-requires-the-mesh-resolves.md`](27-a-module-requires-the-mesh-resolves.md) | **Proposed.** One concept for everything a module needs: a requirement with a contract, answered by one of four kinds of provider, resolved at assignment or refused. Retires settings, placeholders, facts and paths in definitions | [ADR 0112](../../02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md), [ADR 0113](../../02-DECISIONS/0113-the-vault-makes-every-secret.md), [ADR 0114](../../02-DECISIONS/0114-a-shared-credential-rotates-over-two-credentials.md), [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md) | -| [`28-building-the-bus.md`](28-building-the-bus.md) | **Proposed.** The five steps of the bus work in the order their dependencies allow, each ending at a bed — with the surface measured, so no step's size is a guess | [ADR 0115](../../02-DECISIONS/0115-the-bus-is-built-in-five-steps.md), [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md), [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md) | +| [`28-building-the-bus.md`](28-building-the-bus.md) | **Proposed.** The five steps of the bus work in the order their dependencies allow, each ending at a bed — with the surface measured, so no step's size is a guess | [ADR 0116](../../02-DECISIONS/0116-the-bus-is-built-in-five-steps.md), [ADR 0106](../../02-DECISIONS/0106-the-bus-is-nats.md), [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md) | ## Not yet written