Describe the bus on its own terms

Comments framed the new bus by what it replaces — a comparison in almost
every explanation, which reads as though NATS were a variant of the old
thing rather than the mesh's nervous system. Removed throughout, and
OverAMQP becomes OverCurrent: the seam's two sides are the bus the mesh
runs on today and the one being built, not two protocols.

What remains is the client library's own package name, which is its name.
This commit is contained in:
2026-09-26 23:51:00 +02:00
parent 92d87b0082
commit 6c12780abe
5 changed files with 27 additions and 32 deletions
+1 -1
View File
@@ -268,7 +268,7 @@ func answer(ctx context.Context, channel *amqp.Channel, publisher builder.Publis
"manifest": json.RawMessage(result.Manifest), "against": result.Against, "manifest": json.RawMessage(result.Manifest), "against": result.Against,
"made": result.Made, "made": result.Made,
} }
if err := link.EmitEvent(publishCtx, link.OverAMQP{Channel: channel}, link.KeyModuleBuilt, "builder", on, announced); err != nil { if err := link.EmitEvent(publishCtx, link.OverCurrent{Channel: channel}, link.KeyModuleBuilt, "builder", on, announced); err != nil {
// Said, not fatal: the build happened and was answered. A module the catalogue has not // Said, not fatal: the build happened and was answered. A module the catalogue has not
// heard of is a gap somebody can close; a build reported as failed because announcing // heard of is a gap somebody can close; a build reported as failed because announcing
// it failed is a lie about work that was done. // it failed is a lie about work that was done.
+4 -4
View File
@@ -142,7 +142,7 @@ func declare(ctx context.Context, args []string) error {
} }
defer server.Close() defer server.Close()
if err := link.Declare(ctx, link.OverAMQP{Channel: server.Channel()}, ident, node, raw, 15*time.Second); err != nil { if err := link.Declare(ctx, link.OverCurrent{Channel: server.Channel()}, ident, node, raw, 15*time.Second); err != nil {
return err return err
} }
fmt.Printf("sent %s a signed declaration (%d bytes)\n", node, len(raw)) fmt.Printf("sent %s a signed declaration (%d bytes)\n", node, len(raw))
@@ -310,7 +310,7 @@ func pushCommand(ctx context.Context, args []string) error {
if err != nil { if err != nil {
return err return err
} }
if err := link.Declare(ctx, link.OverAMQP{Channel: server.Channel()}, ident, s.node, body, 15*time.Second); err != nil { if err := link.Declare(ctx, link.OverCurrent{Channel: server.Channel()}, ident, s.node, body, 15*time.Second); err != nil {
return err return err
} }
// After it is away, not before. A digest recorded for something that failed to send would // After it is away, not before. A digest recorded for something that failed to send would
@@ -393,7 +393,7 @@ func pushCommand(ctx context.Context, args []string) error {
return declarationWith(held, open, node, plan, settings, gens, Allocating) return declarationWith(held, open, node, plan, settings, gens, Allocating)
}, },
func(s readyNode, body []byte) error { func(s readyNode, body []byte) error {
if err := link.Declare(ctx, link.OverAMQP{Channel: server.Channel()}, ident, s.node, body, if err := link.Declare(ctx, link.OverCurrent{Channel: server.Channel()}, ident, s.node, body,
15*time.Second); err != nil { 15*time.Second); err != nil {
return err return err
} }
@@ -611,7 +611,7 @@ func sendTo(ctx context.Context, open *stores, names []string) error {
if err != nil { if err != nil {
return err return err
} }
if err := link.Declare(ctx, link.OverAMQP{Channel: server.Channel()}, ident, s.node, body, 15*time.Second); err != nil { if err := link.Declare(ctx, link.OverCurrent{Channel: server.Channel()}, ident, s.node, body, 15*time.Second); err != nil {
return err return err
} }
record, err := inv.NodeByName(ctx, s.node) record, err := inv.NodeByName(ctx, s.node)
+9 -13
View File
@@ -1,19 +1,15 @@
// Composing the bus's own configuration. // Composing the bus's own configuration.
// //
// On AMQP an account was made by calling the broker's management API (management.go). On NATS it // An account is *composed*, never called for: the controller writes accounts, users and
// is *composed*: the controller writes accounts, users and per-subject permissions into one file // per-subject permissions into one file the host keeps current, and the server reloads it in
// the host keeps current, and the server reloads it in place (novox/hq ADR 0106 — never through a // place (novox/hq ADR 0106 — never through a management API; design 25 §4).
// management API; design 25 §4).
// //
// Everything here is pure. Given the principals, it returns the file's text — so the whole of the // Everything here is pure. Given the principals, it returns the file's text — so the whole of the
// mesh's authority model is testable as strings, with no server, which is what management.go's // mesh's authority model is testable as strings, with no server.
// `modulePermissions` already did for the half of it that could be.
// //
// **NATS closes a gap AMQP left open.** management.go records it plainly: LavinMQ has no topic // **Permissions are per subject, so a module's own name is the server's to enforce.** ADR 0042
// permissions, so an emitting module is granted the events exchange whole, and ADR 0042's origin // reserves a module's origin — it publishes only under its own name — and here that is a refusal
// reservation — a module publishes only under its own name — is "stamped by the sdk, not enforced // rather than something a library promises.
// here". NATS permissions are per subject, so that reservation becomes something the server
// refuses rather than something a library promises.
package broker package broker
import ( import (
@@ -270,8 +266,8 @@ func consumerDurable(p Principal) string {
// Server is everything the composed file needs that is not a principal. // Server is everything the composed file needs that is not a principal.
type Server struct { type Server struct {
// ClientPort carries TLS itself; there is no plaintext port beside it, which is where this // ClientPort carries TLS itself. There is no plaintext port beside it: a bus reachable
// differs from the AMQP broker's 5671/5672 pair. // without TLS is one a module can reach without TLS by mistake.
ClientPort int ClientPort int
MonitoringPort int MonitoringPort int
TLSCert string TLSCert string
+12 -13
View File
@@ -12,15 +12,14 @@ import (
// Bus is what the controller needs of the mesh's bus, **in the mesh's own words rather than a // Bus is what the controller needs of the mesh's bus, **in the mesh's own words rather than a
// transport's** (novox/hq ADR 0116 step 3). // transport's** (novox/hq ADR 0116 step 3).
// //
// Until now every one of these functions took an `*amqp.Channel`, so the transport reached every // Until now every one of these functions took the transport's own channel type, so the transport
// caller and swapping it meant touching all of them. The seam is small — the controller sends // reached every caller and changing it meant touching all of them. The seam is small — the
// exactly two kinds of message that expect no answer, and asks two kinds of question — which is // controller sends exactly two kinds of message that expect no answer, and asks two kinds of
// why the bus could be replaced at all. // question — which is why the bus can be replaced at all.
// //
// Two implementations live below. Both ship: steps 1 to 4 leave every node on AMQP // Two implementations live below, and both ship until the rollout (ADR 0116: nothing moves a
// ([ADR 0116](novox/hq)), so the controller keeps speaking it and the NATS one is selected at the // node's bus before step 5). Both shipping is what makes them comparable — the same caller, the
// rollout. That is also what makes them comparable — the same caller, the same arguments, and a // same arguments, and one conformance fixture holding them to one envelope.
// conformance fixture holding both to one envelope.
type Bus interface { type Bus interface {
// PublishEvent announces something that happened, under the emitter's own name. 1:many, and // PublishEvent announces something that happened, under the emitter's own name. 1:many, and
// nobody is obliged to act (ADR 0041). // nobody is obliged to act (ADR 0041).
@@ -32,12 +31,12 @@ type Bus interface {
PublishDeclaration(ctx context.Context, node string, body []byte) error PublishDeclaration(ctx context.Context, node string, body []byte) error
} }
// --- AMQP, the bus the mesh runs on today ----------------------------------------------------- // --- The bus the mesh runs on today -----------------------------------------------------
// OverAMQP is the bus as a channel. // OverCurrent is the bus the mesh runs on today, until the rollout.
type OverAMQP struct{ Channel *amqp.Channel } type OverCurrent struct{ Channel *amqp.Channel }
func (b OverAMQP) PublishEvent(ctx context.Context, key, source, node string, body []byte) error { func (b OverCurrent) PublishEvent(ctx context.Context, key, source, node string, body []byte) error {
id, err := eventID() id, err := eventID()
if err != nil { if err != nil {
return err return err
@@ -58,7 +57,7 @@ func (b OverAMQP) PublishEvent(ctx context.Context, key, source, node string, bo
}) })
} }
func (b OverAMQP) PublishDeclaration(ctx context.Context, node string, body []byte) error { func (b OverCurrent) PublishDeclaration(ctx context.Context, node string, body []byte) error {
// To the queue directly rather than through an exchange: a declaration is for one node, and // To the queue directly rather than through an exchange: a declaration is for one node, and
// routing it by name through a shared exchange would mean a binding per node that nothing // routing it by name through a shared exchange would mean a binding per node that nothing
// removes when a node is retired. // removes when a node is retired.
+1 -1
View File
@@ -631,7 +631,7 @@ func (s *Server) catchingUp(ctx context.Context, delivery amqp.Delivery) {
sent := 0 sent := 0
for _, a := range announcements { for _, a := range announcements {
a.Replay = true a.Replay = true
if err := EmitEvent(ctx, OverAMQP{Channel: s.channel}, KeyModuleBuilt, "control-plane", "", a); err != nil { if err := EmitEvent(ctx, OverCurrent{Channel: s.channel}, KeyModuleBuilt, "control-plane", "", a); err != nil {
// Said and abandoned rather than retried: the catalogue asks again every time it // Said and abandoned rather than retried: the catalogue asks again every time it
// starts, and half a graph delivered twice is no better than half delivered once. // starts, and half a graph delivered twice is no better than half delivered once.
s.log.Printf("replaying %s at %s failed, and the rest is abandoned: %v", s.log.Printf("replaying %s at %s failed, and the rest is abandoned: %v",