diff --git a/cmd/mesh-host/nodeagent.go b/cmd/mesh-host/nodeagent.go index a62df63..08cbecb 100644 --- a/cmd/mesh-host/nodeagent.go +++ b/cmd/mesh-host/nodeagent.go @@ -633,7 +633,9 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus env.Composed, env.SelfComposed, env.Policy = composer, true, composer.Policy env.HoldsData = composer.HoldsData // what runs from the kept declaration says Data as its manifest does env.BusServer = composer.BusServer // the bus server an unwarranted want leaves as it runs (ADR 0594 §2) - session.NoteComposes(linked, delivery.ComposesOwn, "it composes its own modules, and applies no composition made elsewhere") + // And its agent keeps the bus server as it runs unless a want carries the warrant's mark (ADR 0594 §8). + session.NoteComposes(linked, delivery.ComposesOwn, "it composes its own modules, and applies no composition made elsewhere", + delivery.KeepsBusServer) ag = agent.New(env) own.set(ag) current.set(ag) diff --git a/internal/link/have.go b/internal/link/have.go index 37cb7ed..46b017c 100644 --- a/internal/link/have.go +++ b/internal/link/have.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "slices" "sort" "strings" "sync" @@ -55,6 +56,9 @@ type haveState struct { // nodeComposes and nodeComposesWhy are whose compositions the node applies and why (HaveNode.Composes), written into // its key. nodeComposes, nodeComposesWhy string + // nodeKeeps is what the node agent keeps as it runs on a want without the warrant's mark (HaveNode.Keeps, novox/hq + // ADR 0594 §8), written into its key. + nodeKeeps []string // written is the build of each module this node-engine wrote a have key for and that the declaration it applied // still holds; removed is each such module the latest declaration no longer holds, with the build its key last // said, until the key is written once with the phase removed (issue 1369, novox/hq ADR 0408 §4: never deleted). @@ -428,6 +432,7 @@ func (q *Session) writeHaves(ctx context.Context, bus Bus) { facts.SealedAt = s.nodeSealed facts.Candidates = s.nodeCandidates facts.Composes, facts.ComposesWhy = s.nodeComposes, s.nodeComposesWhy + facts.Keeps = s.nodeKeeps if raw, err = delivery.EncodeHaveNode(facts); err == nil { want := facts if !q.putHave(write, bucket, key, raw, func(current []byte) bool { @@ -757,14 +762,17 @@ func (q *Session) NoteCandidates(ctx context.Context, answers map[string]deliver q.writeHaves(ctx, bus) } -// NoteComposes says, in the node's own key, whose compositions the node applies (delivery.ComposesOwn) and why: written -// at each start of its agent, where every have is read (issue 1126). -func (q *Session) NoteComposes(ctx context.Context, composes, why string) { +// NoteComposes says, in the node's own key, whose compositions the node applies (delivery.ComposesOwn) and why, and what +// its agent keeps as it runs on a want without the warrant's mark (delivery.KeepsBusServer, novox/hq ADR 0594 §8): written +// at each start of its agent, where every have is read (issue 1126). The assigner writes the bus module's want only to +// a node whose key says it keeps the bus server. +func (q *Session) NoteComposes(ctx context.Context, composes, why string, keeps ...string) { q.init() s := &q.haves s.mu.Lock() - changed := s.nodeComposes != composes || s.nodeComposesWhy != why + changed := s.nodeComposes != composes || s.nodeComposesWhy != why || !slices.Equal(s.nodeKeeps, keeps) s.nodeComposes, s.nodeComposesWhy = composes, why + s.nodeKeeps = slices.Clone(keeps) known := s.node != nil s.mu.Unlock() if !known || !changed { diff --git a/internal/link/have_test.go b/internal/link/have_test.go index f092ed2..f6fc2c7 100644 --- a/internal/link/have_test.go +++ b/internal/link/have_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "slices" "strings" "sync" "testing" @@ -579,6 +580,17 @@ func TestAnAppliedModulesHaveNamesTheFingerprintAndTheDigestAndFollowsItsHealth( if n, _ := delivery.DecodeHaveNode(l.kv["n1.node"].value); n.Candidates != nil { t.Fatalf("an answer left after its group went: %+v", n.Candidates) } + // Issue 774, ADR 0594 §4, §8: the bus module's have names what its server runs beside the fingerprint applied, and the + // node's key says its agent keeps the bus server. + q.NoteModule(context.Background(), ModuleFact{Module: "nats", FP: fp, Composed: dig, Server: "build-1792674595610176744"}) + if got := l.have(t, "n1.nats"); got.FP != fp || got.Server != "build-1792674595610176744" { + t.Fatalf("the bus's have: %+v", got) + } + q.NoteComposes(context.Background(), delivery.ComposesOwn, "it composes its own", delivery.KeepsBusServer) + if n, err := delivery.DecodeHaveNode(l.kv["n1.node"].value); err != nil || !slices.Equal(n.Keeps, []string{delivery.KeepsBusServer}) || + n.Composes != delivery.ComposesOwn { + t.Fatalf("the node key does not say it keeps the bus server: %+v %v", n, err) + } } // A module applied and running that has nothing long-running to judge is ok once the engine has looked: there is nothing