The bus module's have names what its server runs, and a node's have what its agent keeps (ADR 0594 §4, §8)

This commit is contained in:
2026-11-03 08:52:08 +01:00
parent 5bc8d829ff
commit 209ebeee03
2 changed files with 68 additions and 1 deletions
+23 -1
View File
@@ -360,6 +360,12 @@ type Have struct {
// slot that holds it, so a provider and a consumer of one value on one node each keep their own. Written in Sealed's
// place by a node-engine that knows it; Sealed is read only from a have written before.
SealedAt Seals `json:"sealed-at,omitempty"`
// Server is, in the have of the bus module, what the bus server runs (novox/hq ADR 0594 §4): the fingerprint whose
// server the node agent last made, or, from the take-over of a server a node's own composition made, that old-form
// build's name (`build-...`). FP is the fingerprint applied: the two differ while a build changed the server and no
// warranted want replaced it yet. Empty on every other have, and on a bus module's have from a node agent that does
// not keep the server.
Server string `json:"server,omitempty"`
}
// UserListWritten is the bus's user list as the node-engine of the bus node wrote it to the bus module's file, applied
@@ -421,8 +427,15 @@ type HaveNode struct {
// controller ended may still say `controller` (composedByTheEndedController), which is read and never written.
Composes string `json:"composes,omitempty"`
ComposesWhy string `json:"composes-why,omitempty"`
// Keeps names what the node agent keeps as it runs on a want without the warrant's mark (novox/hq ADR 0594 §8):
// KeepsBusServer for a node agent that leaves the bus server alone. Written at each start; the assigner writes the
// bus module's want only to a node whose have names it. Empty from a node agent before it.
Keeps []string `json:"keeps,omitempty"`
}
// KeepsBusServer is a node agent that keeps the bus server unless a want is warranted (HaveNode.Keeps, ADR 0594 §8).
const KeepsBusServer = "bus-server"
// ComposesOwn is a node applying its own compositions (HaveNode.Composes).
const ComposesOwn = "own"
@@ -494,6 +507,10 @@ func (h Have) check() error {
return err
}
}
if h.Server != "" && checkFingerprint(h.Server, "the bus server's fingerprint") != nil &&
(!strings.HasPrefix(h.Server, "build-") || checkName("bus server's build", h.Server) != nil) {
return fmt.Errorf("delivery: the bus server runs %q, neither a fingerprint nor a build's name", h.Server)
}
switch h.Health {
case HealthOK, HealthFailed, HealthWaits, HealthUnknown:
default:
@@ -529,6 +546,11 @@ func (n HaveNode) check() error {
default:
return fmt.Errorf("delivery: a node composes %q, not %s", n.Composes, ComposesOwn)
}
for _, k := range n.Keeps {
if k == "" {
return fmt.Errorf("delivery: a node keeps an empty name")
}
}
for group, a := range n.Candidates {
if group == "" {
return fmt.Errorf("delivery: a node's candidate answer names no group")
@@ -615,7 +637,7 @@ func SameHaveNode(a, b HaveNode) bool {
sameJSON(a.Strays, b.Strays) && sameJSON(a.Filters, b.Filters) && sameJSON(a.Profile, b.Profile) &&
(a.Record == nil) == (b.Record == nil) && (a.Record == nil || SameNodeRecord(*a.Record, *b.Record)) &&
jsonEqual(a.Sealed, b.Sealed) && maps.Equal(a.SealedAt, b.SealedAt) && jsonEqual(a.Candidates, b.Candidates) && a.Composes == b.Composes &&
a.ComposesWhy == b.ComposesWhy
a.ComposesWhy == b.ComposesWhy && slices.Equal(a.Keeps, b.Keeps)
}
// SamePresence says whether two presences are the same value.
+45
View File
@@ -282,6 +282,51 @@ func TestRoundTrip(t *testing.T) {
t.Error("a list was sealed to no key")
}
})
// 774/S2: the bus module's have names the fingerprint applied and what the server runs, a fingerprint or, at the
// take-over, an old-form build's name (ADR 0594 §4); a have written before decodes as it was.
t.Run("have of the bus with its server", func(t *testing.T) {
fpA, fpB := fp2of('a'), fp2of('b')
for _, server := range []string{fpA, "build-1792674595610176744"} {
in := Have{FP: fpB, Server: server, Phase: PhaseRunning, Health: HealthOK}
b, err := EncodeHave(in)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), `"server":"`+server+`"`) {
t.Errorf("the have says %s", b)
}
out, err := DecodeHave(b)
in.V = Version
if err != nil || !SameHave(out, in) || SameHave(out, Have{V: Version, FP: fpB, Phase: PhaseRunning, Health: HealthOK}) {
t.Errorf("got %+v, %v", out, err)
}
}
for _, server := range []string{"nats", "build-a.b", "src2:abc"} {
if _, err := EncodeHave(Have{FP: fpB, Server: server, Phase: PhaseRunning, Health: HealthOK}); err == nil {
t.Errorf("a server running %q was encoded", server)
}
}
old, err := DecodeHave([]byte(`{"v":1,"build":"build-17","phase":"running","health":"ok"}`))
if err != nil || old.Server != "" || !SameHave(old, Have{V: Version, Build: "build-17", Phase: PhaseRunning, Health: HealthOK}) {
t.Errorf("an old have read %+v, %v", old, err)
}
keeper := HaveNode{Composes: ComposesOwn, Keeps: []string{KeepsBusServer}}
b, err := EncodeHaveNode(keeper)
if err != nil || !strings.Contains(string(b), `"keeps":["bus-server"]`) {
t.Fatalf("the keeper's have is %s, %v", b, err)
}
back, err := DecodeHaveNode(b)
keeper.V = Version
if err != nil || !SameHaveNode(back, keeper) || SameHaveNode(back, HaveNode{V: Version, Composes: ComposesOwn}) {
t.Errorf("the keeper came back %+v, %v", back, err)
}
if older, err := DecodeHaveNode([]byte(`{"v":1,"composes":"own"}`)); err != nil || older.Keeps != nil {
t.Errorf("an older node's have read %+v, %v", older, err)
}
if _, err := EncodeHaveNode(HaveNode{Keeps: []string{""}}); err == nil {
t.Error("a node keeping an empty name was encoded")
}
})
t.Run("have node", func(t *testing.T) {
in := HaveNode{Windows: json.RawMessage(`{"a":1}`), Busy: json.RawMessage(`true`)}
b, err := EncodeHaveNode(in)