Design 25: the store window, and what moving it into the server changes

The guarantee is the same and the mechanism is simpler — a nak with a
delay, no parked list, nothing lost when the controller restarts. It costs
one thing: a naked message comes back whatever happened meanwhile, so an
older report is redelivered after a newer was applied. A report already
carries the digest of the declaration it answers, so supersession becomes a
check rather than memory — ordering settled by what a message says, not by
when it arrived.
This commit is contained in:
2026-09-27 00:06:15 +02:00
parent 9946e852e1
commit 00817fb9e3
2 changed files with 38 additions and 2 deletions
+27
View File
@@ -128,6 +128,33 @@ call is a timeout the caller already handles.
Streams and consumers are objects the controller creates at genesis and asserts on start; a module
declares nothing about them. The controller is the only writer of stream definitions.
### The store window, and what moving it into the server changes
The guarantee ([ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md)) is
that a push the controller cannot record because its store is restarting is **held and retried** —
never dropped, never falsely acknowledged. Here that is a `nak` with a delay: the server holds
the message and redelivers it, so the controller keeps no list of parked messages and one that
restarts mid-window loses nothing it was holding.
**That is a plain win, and it introduces one problem worth naming.** Holding a delivery in memory
let the controller drop an older report when a newer one for the same node arrived, because
acting on the older after the newer would undo the newer. A `nak`ed message belongs to the server
and comes back whatever happened meanwhile — so the older report is redelivered *after* the newer
was applied.
The answer was already in the message. A report carries the **digest of the declaration it is
about**, which exists because an earlier attempt to order reports by time lost the race it
invited: an apply that began under the previous declaration finishes after the next is sent, and
its report reads as newer than the send. Clocks cannot answer *which*.
So supersession stops being something the controller remembers and becomes something it checks —
a report whose digest is not the one outstanding for that node is acknowledged without being
acted on. The same shape as a node refusing a superseded declaration by sequence
([issue 107](../../04-ISSUES/107-a-declaration-carries-no-order/00-report.md)): **ordering settled
by what a message says, not by when it arrived.** And staleness is checked before the store is
waited on, so a redelivery that lost its race does not hold a slot in the window that a current
message needs.
## 4. Accounts
[ADR 0043](../../02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) says a
+11 -2
View File
@@ -250,8 +250,17 @@ pays for itself furthest away.
rather than from the code that wrote it.
The seam turned out to be eight call sites — the same smallness that said this bus could be
replaced at all. Still on `*amqp.Channel`: `RequestBuild` and `Ask`, which carry reply-queue
machinery, and the whole consume side (the control loop, enrolment, serving).
replaced at all.
**The consume side's hard part is decided and tested**: the store window is a `nak` with a
delay rather than a delivery held in memory, which also means a controller restarting
mid-window loses nothing. Moving the holding into the server costs one thing — an older
report is redelivered after a newer was applied — and a report already carries the digest
of the declaration it is about, so supersession becomes a check rather than something the
controller remembers. Pure and tested without a bus, a store or a clock.
Still outstanding: wiring that decision into the loop, `RequestBuild` and `Ask`, enrolment,
and serving.
- [~] 3.5 the host's link on NATS — **the outbound half is through a seam**, 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