Building the bus: the decisions the work needed, and what it taught back #150

Merged
jschoubben merged 45 commits from feat/nats-genesis into main 2026-09-27 17:06:40 +00:00
Showing only changes of commit 0a61531c42 - Show all commits
+45 -8
View File
@@ -284,17 +284,51 @@ pays for itself furthest away.
manifest and authority cannot come from a declaration that does not exist.
Still outstanding: a build's own shape, which travels with the builder in step 4.
- [~] 3.5 the host's link on NATS — **the outbound half is through a seam**, mirroring the
- [x] 3.5 the host's link on NATS — **all three halves are through seams**, mirroring the
controller's and still importing nothing of the mesh's own (ADR 0005): the host's own
interface over its own libraries, agreeing with the controller only because a fixture holds
interfaces over its own libraries, agreeing with the controller only because a fixture holds
both to one envelope. A report goes through JetStream because it is the message the
store-window guarantee is about; a heartbeat stays on core, because a heartbeat in a stream
is the mesh's least valuable message competing for retention with its most valuable.
store-window guarantee is about; a heartbeat stays on core, because a heartbeat in a stream is
the mesh's least valuable message competing for retention with its most valuable.
Still on the old client: dialling, the declaration consumer, and enrolment. The host's
**"newest wins" window narrows at the rollout rather than disappearing** — last-per-subject
makes catch-up the stream's and sequence orders definitively, but three pushes to a
connected node are still three deliveries. Recorded in the code where it is read.
`Link` is dialling, hearing and saying in one interface, because **dialling is where the
transport is chosen** and choosing it twice is how one half of a node ends up on a different
bus from the other. `Asking` is the enrolment conversation, and it is separate for the
opposite reason: almost nothing about it is the same, and a node that fails there is not in
the mesh at all.
**What the new bus took away, and what it would not give.** A host declares nothing here: on
the old bus it declares its own queue, because a queue that is not there means a node that
hears nothing, but the object it reads through now is a durable consumer and a host's account
reaches no part of the JetStream API. So it **binds** to one the mesh made, and a missing one
is said as the mesh's to answer rather than quietly created with whatever the client defaults
to. Two things that had to be built for that: a node's declaration consumer (named after the
node, because its ack grant is derived from the node's name, so any other name is a delivery
it cannot acknowledge), and the enrolment user's **inbox** — design 25 §6 names it and the
composer granted none, so an enrolling node would have published its request and waited out
its timeout against a mesh that answered.
**The reply address travels in the payload, and that is now proved from both ends.** The
controller reads it from there (3.4) and the host writes it there and waits on it, and the
test asserts the transport's own reply field held the *consumer's ack address* by the time the
request arrived — so a future server that stopped claiming that field fails a test rather than
letting the reason quietly become folklore.
The host's **"newest wins" window narrows at the rollout rather than disappearing**, and that
is now measured rather than predicted: three declarations pushed to an absent node leave one
on the stream and it is the newest, so the catch-up half is the stream's — but three pushes to
a connected node are still three deliveries, which is the half that stays.
**The pin turned out easier here than in the tool runtime, not harder.** The Go client takes a
`*tls.Config`, so the same pinned configuration with the same verify callback does the work;
the subject-alternative-name constraint recorded under 3.6 is that client's, because it takes
PEM strings with no verify hook. A host checks the fingerprint and nothing else.
Still outstanding: **something that composes an enrolment user per live token.** Nothing does,
on either bus — on the old one the account is made imperatively through the broker's
management API when a token is issued, and here there is no management API, so issuing a token
has to recompose the server's configuration. That is the last piece of enrolment on the new
bus, and it is the only thing between the two links and a mesh raised on NATS from nothing.
- [x] 3.6 the tool runtime's client on NATS, behind the unchanged sdk contract — round-tripped
against a real server: a tool answered across two connections, a throwing handler reaching
the caller as an error rather than a timeout, an event delivered once with its key, body,
@@ -380,6 +414,9 @@ it, and the beds that need a mesh living on NATS can finally run.
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
— **waiting on one thing only**: an enrolment user composed per live token (3.5). Both links
speak NATS and every claim above has a unit test or a check against a running server behind
it; what no test can stand in for is a mesh raising itself, which is what this bed is
- [ ] 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 — **blocked by
[issue 127](../../04-ISSUES/127-a-module-event-derives-a-subject-nothing-publishes/00-report.md)**