Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce9e20fbbc | ||
|
|
878690697e | ||
|
|
ad97297576 | ||
|
|
683b1ed693 | ||
|
|
04f9f378b0 | ||
|
|
1c3f44a526 | ||
|
|
89e152dfe2 | ||
|
|
f2f526a60a | ||
|
|
4b4c7e0e0d |
@@ -162,7 +162,13 @@ func declare(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
server, err := connectLink(ctx, nil, nil, nil)
|
||||
// **With the inventory, so the bus is raised** (novox/hq ADR 0134, design 30). A module's
|
||||
// declaration and how it hears what it consumes move together: its consumer is derived from the
|
||||
// same records this declaration is composed from. Raised only when the control plane started
|
||||
// serving, a module that gained a `consumes` was sent a declaration it could act on and a
|
||||
// consumer that never delivered the event — and nothing anywhere said the two disagreed
|
||||
// (found on review, 2026-09-28). Everything the raise does is idempotent.
|
||||
server, err := connectLink(ctx, inv, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/broker"
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
@@ -27,4 +28,17 @@ func TestTheFactsTheGrantPermitsAreTheFactsTheMeshStates(t *testing.T) {
|
||||
if len(broker.ControllerStates) != 3 {
|
||||
t.Errorf("the grant permits %v, which is more than the mesh states", broker.ControllerStates)
|
||||
}
|
||||
// **And the seat says it.** A seat carries the protocol of its role (novox/hq ADR 0129), so the
|
||||
// facts the control plane states are the seat's `emits` — which is what lets anything else declare
|
||||
// that it consumes them, and what the subject-agreement check reads to know they have an owner.
|
||||
var declared []string
|
||||
for _, seat := range catalogue.SeatsWithAProtocol() {
|
||||
if seat.Name == broker.ControllerSeat {
|
||||
declared = seat.Emits
|
||||
}
|
||||
}
|
||||
if !slices.Equal(declared, broker.ControllerStates) {
|
||||
t.Errorf("the %s seat emits %v and the grant permits %v", broker.ControllerSeat,
|
||||
declared, broker.ControllerStates)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ func (r Registry) has(ctx context.Context, url string, accept ...string) (bool,
|
||||
return false, err
|
||||
}
|
||||
for _, media := range accept {
|
||||
request.Header.Set("Accept", media)
|
||||
request.Header.Add("Accept", media)
|
||||
}
|
||||
response, err := r.client().Do(request)
|
||||
if err != nil {
|
||||
|
||||
@@ -1685,7 +1685,10 @@ func prepared(from map[string]any) map[string]any {
|
||||
for k, v := range from {
|
||||
step[k] = v
|
||||
}
|
||||
step["id"] = fmt.Sprint(from["id"]) + ".prepare"
|
||||
// **A hyphen, not a dot.** A resource's id is `<module>.<its own id>`, and a module's name may
|
||||
// itself contain a dot (`novox.be`), so the module is everything before the *last* dot — which
|
||||
// only works if what the mesh derives adds no dot of its own.
|
||||
step["id"] = fmt.Sprint(from["id"]) + "-prepare"
|
||||
step["name"] = fmt.Sprint(from["name"]) + "-prepare"
|
||||
step["run-once"] = true
|
||||
step["args"] = []any{PreparationArgument}
|
||||
|
||||
@@ -3,6 +3,7 @@ package catalogue
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -57,13 +58,18 @@ func TestThePreparationRunsTheModulesOwnCodeAndComesRightBeforeIt(t *testing.T)
|
||||
ids := idsOf(out)
|
||||
at := -1
|
||||
for i, id := range ids {
|
||||
if id == "gitea.runtime.prepare" {
|
||||
if id == "gitea.runtime-prepare" {
|
||||
at = i
|
||||
}
|
||||
}
|
||||
if at < 0 {
|
||||
t.Fatalf("nothing prepares this module's state: %v", ids)
|
||||
}
|
||||
// A module's name may contain a dot, so a resource's module is everything before the last one —
|
||||
// which the derived id must not add to, or a machine reads the wrong owner from it.
|
||||
if strings.Count("gitea.runtime-prepare", ".") != 1 {
|
||||
t.Fatal("the derived id adds a dot, so what owns it cannot be read from it")
|
||||
}
|
||||
if ids[at+1] != "gitea.runtime" {
|
||||
t.Fatalf("the preparation is not immediately before the module's own code: %v", ids)
|
||||
}
|
||||
@@ -79,7 +85,7 @@ func TestThePreparationRunsTheModulesOwnCodeAndComesRightBeforeIt(t *testing.T)
|
||||
func TestThePreparationIsGivenWhatTheModuleIsGiven(t *testing.T) {
|
||||
out := declaredFor(t, aPreparingModule())
|
||||
declared := byID(out)
|
||||
step, workload := declared["gitea.runtime.prepare"], declared["gitea.runtime"]
|
||||
step, workload := declared["gitea.runtime-prepare"], declared["gitea.runtime"]
|
||||
if step == nil || workload == nil {
|
||||
t.Fatalf("expected both, got %v", idsOf(out))
|
||||
}
|
||||
@@ -108,7 +114,7 @@ func TestAModuleThatPreparesNothingGetsNoStep(t *testing.T) {
|
||||
m := aPreparingModule()
|
||||
m.Prepares = false
|
||||
for _, id := range idsOf(declaredFor(t, m)) {
|
||||
if id == "gitea.runtime.prepare" {
|
||||
if id == "gitea.runtime-prepare" {
|
||||
t.Fatal("a module that prepares nothing was given a preparation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,11 @@ type Seat struct {
|
||||
//
|
||||
// In the order a person reads it: the mesh's own, then a node's.
|
||||
var defaultSeats = []Seat{
|
||||
{Name: "mesh-controller", Scope: ScopeMesh, Decision: "novox/hq ADR 0079"},
|
||||
// The control plane states what it did under the seat it holds (novox/hq ADR 0134): a role's
|
||||
// events belong to the role, so they keep their address while the holder is replaced. No accepts,
|
||||
// so no work queue is raised for it — only what its holder may say.
|
||||
{Name: "mesh-controller", Scope: ScopeMesh, Decision: "novox/hq ADR 0079",
|
||||
Emits: []string{"applied", "refused", "built-before"}},
|
||||
{Name: "mesh-store", Scope: ScopeMesh, Delivers: "postgres-database", Decision: "novox/hq ADR 0079"},
|
||||
// **Delivers the mesh's own bus, not `amqp`.** Those were the same word until
|
||||
// ADR 0127 separated them: `amqp` is a backing service a module may require, and this seat is
|
||||
|
||||
+9
-13
@@ -476,7 +476,15 @@ func (s *Server) catchingUp(ctx context.Context, m Control) {
|
||||
// module's event from a module called "control-plane", which does not exist — so the
|
||||
// controller's own account refused it, every catalogue that asked what it missed was
|
||||
// answered with nothing, and its graph kept the gap (found 2026-09-28).
|
||||
if err := s.bus.PublishSeatEvent(ctx, MeshControllerSeat, KeyBuiltBefore, replayed(a)); err != nil {
|
||||
body, err := json.Marshal(a)
|
||||
if err != nil {
|
||||
// A body that cannot be written is this program's fault, not the bus's, and publishing
|
||||
// an empty one would put a fact on the mesh that says nothing.
|
||||
s.log.Printf("cannot re-announce %s at %s: %v", a.Module, short(a.Commit), err)
|
||||
_ = m.Took()
|
||||
return
|
||||
}
|
||||
if err := s.bus.PublishSeatEvent(ctx, MeshControllerSeat, KeyBuiltBefore, body); err != nil {
|
||||
// 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.
|
||||
s.log.Printf("replaying %s at %s failed, and the rest is abandoned: %v",
|
||||
@@ -601,15 +609,3 @@ func (s *Server) saysWhatItDid(ctx context.Context, report Report) {
|
||||
s.log.Printf("could not say that %s %s: %v", report.Node, event, err)
|
||||
}
|
||||
}
|
||||
|
||||
// replayed is one announcement as the control plane states it. The same body the build machine's
|
||||
// outcome carries, because what the catalogue does with it is the same.
|
||||
func replayed(a Announcement) []byte {
|
||||
raw, err := json.Marshal(a)
|
||||
if err != nil {
|
||||
// A body that cannot be marshalled is a programming error, not a bus failure, and an empty
|
||||
// one is refused by the reader rather than silently taken as an announcement of nothing.
|
||||
return nil
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user