The bus in five steps: a decomposition, a breakdown, and progressive insight #134

Merged
jschoubben merged 6 commits from feat/the-bus-in-five-steps into main 2026-09-26 17:05:44 +00:00
9 changed files with 707 additions and 48 deletions
+54
View File
@@ -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): def check_status_against_code(failures):
"""A design document naming specific code may not still call itself `designed`. """A design document naming specific code may not still call itself `designed`.
@@ -325,6 +378,7 @@ def main():
check_numbering(failures, records) check_numbering(failures, records)
check_topics(failures, records) check_topics(failures, records)
check_status_against_code(failures) check_status_against_code(failures)
check_progressive_insights(failures, records)
print(f"records: {len(records)} decision records checked") print(f"records: {len(records)} decision records checked")
return failures.report() return failures.report()
+6 -2
View File
@@ -33,7 +33,10 @@
A design changes only through a decision. A design changes only through a decision.
1. Write the decision record. If it reverses an earlier one, the earlier record's `status:` 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. 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 3. If the amendment came from an issue, set that issue's `amended-design:` to the document
path. 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 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 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.
@@ -0,0 +1,175 @@
---
topic: the mesh
status: accepted
date: 2026-09-26
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0106-the-bus-is-nats.md
---
# 116. 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 `<node>.<module>.events`, a shared `serve.<key>`
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 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.
**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 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
"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 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.
- [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.
+40 -2
View File
@@ -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. numbers walks the process in the order it happens.
One file per decision, numbered, never deleted. A superseded record has its `status:` changed 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 and gains a pointer to what replaced it — **its reasoning is never rewritten**. The reasoning that
rejected is the expensive half to rediscover. 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. The records run in the order the decisions were taken, oldest first.
@@ -95,6 +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) - **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) - **0106** — [The bus is NATS](0106-the-bus-is-nats.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 ### Its tiers, from the bottom up
+24 -1
View File
@@ -5,9 +5,11 @@ code:
- mesh-sdk src - mesh-sdk src
- mesh-tools src/broker-amqp.ts - mesh-tools src/broker-amqp.ts
- mesh-controller internal/link - mesh-controller internal/link
updated: 2026-09-21 updated: 2026-09-26
decisions: decisions:
- 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md - 02-DECISIONS/0095-the-control-plane-is-the-way-to-ask-a-module.md
- 02-DECISIONS/0106-the-bus-is-nats.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/0074-the-wire-is-specified-not-the-types.md
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.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 - 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 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. 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 `<node>.<module>.events` queue, the shared `serve.<key>` 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 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
> quiet edit.
## The shape of it ## The shape of it
A **floor** every implementation needs, and three **capabilities** that are independent of each A **floor** every implementation needs, and three **capabilities** that are independent of each
+144 -41
View File
@@ -6,9 +6,14 @@ code:
- mesh-host internal/link (to be replaced) - mesh-host internal/link (to be replaced)
- mesh-tools src/broker-amqp.ts (to be replaced) - mesh-tools src/broker-amqp.ts (to be replaced)
- mesh-catalog modules/nats (to be written) - 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: decisions:
- 02-DECISIONS/0106-the-bus-is-nats.md - 02-DECISIONS/0106-the-bus-is-nats.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
- 02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.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/0083-one-push-leaves-the-mesh-consistent.md
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md - 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
@@ -221,64 +226,154 @@ bridged. It is three things:
Nothing is built of this before §10's bed passes; the MCP surface is a thin adapter over (2). 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.<module>.<event>`; **The contract a module is written against does not change.** `publish` on an envelope becomes a
`subscribe` with a pattern becomes a durable JetStream consumer on the matching subject filter; publish on `mesh.events.<module>.<event>`; `subscribe` with a pattern becomes a durable JetStream
`request`/`handle` become a NATS request and a queue-group subscription on consumer on the matching subject filter; `request`/`handle` become a NATS request and a queue-group
`mesh.tools.<module>.<tool>`. The envelope's shape ([ADR 0042](../../02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md)) subscription on `mesh.tools.<module>.<tool>`. The envelope's shape
is unchanged; it is the message body. A module built today runs on the new runtime without a ([ADR 0042](../../02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md)) is unchanged; it is the
rebuild — that is the test of ADR 0039, and it is in §10. 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 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
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 `<node>.<module>.events`, and a shared `serve.<key>` 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 ## 9. Moving from the bus the mesh has: five steps
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.
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 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.
**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.
**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 ## 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-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:
- 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 — 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 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 - 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; push applies, nothing was lost or duplicated;
- a node that was away gets exactly the newest declaration, and a replayed older one is refused - a node that was away gets exactly the newest declaration, and a replayed older one is refused
by sequence; by sequence;
- an upgrade rolls out to two nodes; - 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 - 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 once the controller answers — proving the reply travels in the payload and not the transport
field a consumer's ack has already claimed; field a consumer's ack has already claimed;
- the `nats` container is not recreated when only its composed configuration file changes, and - an event whose consumer keeps failing dead-letters after `max-deliver`;
a change to that file is live (a new user can connect, a revoked one cannot) within one - a module's tool is invoked from another node and from a person's client, each with an account
watcher-poll interval, without a restart; that can invoke it, and refused from one that cannot;
- a module built before this design serves its tools unchanged on the new runtime. - 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 node that was unreachable catches up on its reports rather than losing them.
**The cutover bed** — a mesh on AMQP with a predecessor stand-in on the compatibility broker moves **Step 5 — the cutover bed:** a mesh on AMQP with a predecessor stand-in on the compatibility
its bus in one rollout; every node reports on NATS afterwards; the stand-in's client on AMQP is broker moves its bus in one rollout; every node reports on NATS afterwards; the stand-in's client
still connected throughout. on AMQP is still connected throughout.
Unit tests hold the controller to composing accounts from `emits`/`consumes` and nothing else, to 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; creating the four streams and asserting them idempotently, and to spending a token exactly once;
@@ -287,7 +382,15 @@ to mapping the sdk contract onto subjects exactly as §8 says.
## 11. Open, for the review ## 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 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
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), `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 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 named where it was wrong, not silently fixed, so a reader comparing against the first version can
+256
View File
@@ -0,0 +1,256 @@
---
layer: to-be
status: proposed
code: []
updated: 2026-09-26
decisions:
- 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
- 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 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
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.
> **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 0116
> itself rather than left to be discovered here.
## 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 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 0116 as a progressive insight, with what the record said before.
```
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; 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
**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 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
- [ ] 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.
+2
View File
@@ -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) | | [`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) | | [`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) | | [`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 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) | | [`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) | | [`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 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 ## Not yet written
+6 -2
View File
@@ -52,8 +52,12 @@ vocabulary — *controller* (not "control plane"), *foundation* (not "substrate"
docs (`layer`, `status`, `code`, `updated`), issue reports (`status`, `located-in`, docs (`layer`, `status`, `code`, `updated`), issue reports (`status`, `located-in`,
`fixed-by`, `amended-design`) and decision records (`status`, `date`, `deciders`). `fixed-by`, `amended-design`) and decision records (`status`, `date`, `deciders`).
Never create a central status file; cross-cutting views are generated from frontmatter. 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 - **`02-DECISIONS/` records hold their meaning.** Supersede with a new record rather than rewriting
broken link or path is allowed. 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 - **Design docs are prose and diagrams only** — no code. A manifest field may be named; a
manifest may not be pasted. manifest may not be pasted.
- **Two layers, never mixed.** [`03-DESIGN/00-as-is/`](03-DESIGN/00-as-is/) describes the mesh - **Two layers, never mixed.** [`03-DESIGN/00-as-is/`](03-DESIGN/00-as-is/) describes the mesh