Files
mesh-controller/internal/broker/onnats.go
T
jschoubben 4de10e32e3 The bus's objects are raised on every start, and one switch says which bus
Two of 1.7's three remaining pieces.

**Raised on every start, not created once at genesis.** A stream somebody deleted,
a mesh raised from a restored backup, or a bus whose data directory was replaced
all have records and no objects — and a node whose consumer is missing hears
nothing while everything else about it looks correct.

The order is not a preference: a consumer on a stream that does not exist is
refused *naming the stream*, so somebody reading that refusal goes looking for a
deletion instead of a reversed pair of lines. Pinned by a test, along with the one
thing about seats that reads like an omission and is not — a seat's work queue is
asserted whether or not anybody holds it, because work queues until a holder
appears, so installing the module a week later flushes the backlog instead of
having lost it.

Against a real server: every object accepted, asserting twice changes nothing (a
start that failed the second time is a controller that cannot restart), a machine
joining an already-raised bus is accepted, each node's consumer is bound to its own
declaration subject and no other's, and CONTROL does not dead-letter — because the
store window's bound belongs to the controller and a server that gave up first
would discard the push the stream exists to protect.

**Which bus this mesh is on is one fact, read in one place.** Every seam the change
went behind ships both implementations; this is what the rollout flips. Being told
about both is refused at start rather than warned about: a mesh half on each is one
where a declaration goes out on one bus and the report comes back on the other, and
every component logs success while it happens — ADR 0074's failure arriving through
configuration instead of through code. The refusal names both variables and says
which to unset, because whoever reads it has to choose and the wrong choice is a
rollout half done.
2026-09-27 02:59:05 +02:00

59 lines
2.5 KiB
Go

package broker
import (
"fmt"
"strings"
"github.com/novox/mesh-controller/internal/envfile"
)
// Whether this mesh's own traffic is on the bus being built.
//
// **One switch, read in one place** (novox/hq ADR 0116 step 5). Every seam the bus change went
// behind ships both implementations, and until the rollout every one of them chooses the bus the
// mesh runs on today. This is what the rollout flips, and it is deliberately a single fact rather
// than a fact per component: a controller whose outbound is on one bus and whose inbound is on the
// other is a mesh that hears nothing, and no test of either half would catch it.
// NATSVar is where the controller finds the bus being built. Unset is the ordinary case and means
// the mesh runs on the bus it has always run on.
const NATSVar = "MESH_BUS_NATS"
// OnNATS is the address of the bus being built, and whether the mesh is on it.
//
// Read from the node's own settings rather than baked in, for the reason the broker's address is
// (novox/hq 04-ISSUES/102): an address recorded once does not follow a node's ports.
func OnNATS() (address string, on bool, err error) {
address, err = envfile.Placed(NATSVar)
if err != nil {
return "", false, err
}
address = strings.TrimSpace(address)
if address == "" {
return "", false, nil
}
return address, true, nil
}
// MustBeOneBus refuses a configuration that names both buses for the mesh's own traffic.
//
// **Both clients ship and that is the point; both being live is not.** The rollout moves every node
// at once (ADR 0116 step 5): a mesh half on each is one where a declaration goes out on one bus and
// the report comes back on the other, and nothing anywhere says so — every component would log
// success. Refused at start, where it can be said in one sentence.
func MustBeOneBus(amqp, nats string) error {
if strings.TrimSpace(amqp) != "" && strings.TrimSpace(nats) != "" {
return fmt.Errorf(
"this control plane is told about both buses (%s and %s) and can only be on one. A mesh "+
"half on each is one where a declaration goes out on one and the report comes back "+
"on the other, and every component reports success while it happens. The rollout "+
"moves every node at once: unset %s to stay, or unset %s to move",
AMQPVarName, NATSVar, NATSVar, AMQPVarName)
}
return nil
}
// AMQPVarName is the variable naming the bus the mesh runs on today. Named here rather than
// imported from the link package, for the one direction of dependency.
const AMQPVarName = "MESH_BROKER_AMQP"