diff --git a/cmd/mesh-host/enrol_seat.go b/cmd/mesh-host/enrol_seat.go index 787bb90..f86d187 100644 --- a/cmd/mesh-host/enrol_seat.go +++ b/cmd/mesh-host/enrol_seat.go @@ -65,7 +65,7 @@ func enrolThroughSeat(ctx context.Context, opts options, token identity.Token, n } fmt.Printf("\nenrolled as %s through the seat mesh-node-enroller, dialled as %s and confirmed\n", answer.Node, answer.Login) fmt.Printf(" identity %s\n", identityPath) - fmt.Println(" nothing is applied until the placement of this node names a module") + fmt.Println(" nothing is applied until the assignment of this node names a module") return nil } diff --git a/cmd/mesh-host/nodeagent.go b/cmd/mesh-host/nodeagent.go index e7d8642..46a8fd7 100644 --- a/cmd/mesh-host/nodeagent.go +++ b/cmd/mesh-host/nodeagent.go @@ -46,7 +46,7 @@ func (w bucketWatch) Watch(ctx context.Context, pattern string) (<-chan agent.Up return w.updates(ctx, in), nil } -// WatchAwait is Watch for a state that its holder makes when it starts (the placement seat's): a bucket not on the bus +// WatchAwait is Watch for a state that its holder makes when it starts (the assignment seat's): a bucket not on the bus // yet is waited for on the bus's word that it was created (link.Session.KVWatchAwait), and waiting is told why. func (w bucketWatch) WatchAwait(ctx context.Context, pattern string, waiting func(error)) (<-chan agent.Update, error) { in, err := w.session.KVWatchAwait(ctx, w.bucket, pattern, waiting) @@ -454,7 +454,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus return } var ag *agent.Agent - // The kept release-5 declaration is no longer read once every module the seat's placement puts here has a have of its + // The kept release-5 declaration is no longer read once every module the seat's assignment puts here has a have of its // own (ADR 0550 §2: its signature is the controller's, trusted no more after the lease write). // Nor before this engine keeps the node's own as it runs (applied/node.json): until then the kept declaration is the one // record of what the node's own composition carries, its folded modules among them (issue 774, review of the @@ -637,7 +637,7 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus return certifyThisNode(ctx, opts.state, node, trust, session.AskVault, func() { sayFacts(ctx, ag) }) }, } - // And its answer to every placement candidate, placed or not, applying nothing (novox/hq ADR 0559 §4): written into + // And its answer to every assignment candidate, placed or not, applying nothing (novox/hq ADR 0559 §4): written into // its own key, `.node`, under candidates. shadowHooks := hooks shadowHooks.refused, shadowHooks.kept, shadowHooks.certify, shadowHooks.seatList = nil, nil, nil, nil @@ -650,10 +650,10 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus go shadow.Keep(run, link.Backoff) // this run's alone: a switch begins a run, and its shadow, again shadowNow.Store(shadow) // what the verb compose-candidate composes a check's candidate with // Its own composer, always (issue 1126, A7): a composition made elsewhere (the controller's, in the node's - // composition bucket) is never applied. While the seat's placement holds no key for this node it composes - // nothing and keeps running what it runs; a placement it cannot watch stops the agent, said, and it begins again. + // composition bucket) is never applied. While the seat's assignment holds no key for this node it composes + // nothing and keeps running what it runs; a assignment it cannot watch stops the agent, said, and it begins again. composer := selfComposer(session, node, hooks, say) - machinesNow.Store(composer) // the machines a failing network part points at, by the placements (issue 774) + machinesNow.Store(composer) // the machines a failing network part points at, by the assignments (issue 774) composerNow.Store(composer) // what the verb compose asks a pass of, this run's alone env.Composed, env.SelfComposed, env.Policy, env.ComposedToday = composer, true, composer.Policy, composer.ComposedToday env.HoldsData = composer.HoldsData // what runs from the kept declaration says Data as its manifest does @@ -792,7 +792,7 @@ func busyModules() []string { return out } -// machinesNow is the composer whose placements name the mesh's machines by address, for the health report's targets +// machinesNow is the composer whose assignments name the mesh's machines by address, for the health report's targets // (writeHealthReport); nil on a node that does not compose its own, whose parts then name no target. var machinesNow atomic.Pointer[selfcompose.Composer] @@ -800,11 +800,11 @@ var machinesNow atomic.Pointer[selfcompose.Composer] // compositions or no run is going: what the verb compose asks a pass of (issue 774). var composerNow atomic.Pointer[selfcompose.Composer] -// shadowNow is the shadow of the run now, which answers placement candidates and the verb compose-candidate; nil +// shadowNow is the shadow of the run now, which answers assignment candidates and the verb compose-candidate; nil // between runs. var shadowNow atomic.Pointer[selfcompose.Shadow] -// keptRetired says the kept declaration is read no more: every module the seat's placement puts on this node has a have of +// keptRetired says the kept declaration is read no more: every module the seat's assignment puts on this node has a have of // its own (placedAllHave), so what runs is the agent's alone (ADR 0550 §2, design 47's L3 s9). var keptRetired atomic.Bool @@ -1001,8 +1001,8 @@ type composerHooks struct { seatListFailed bool } -// selfComposer is the node's own composer: it composes what the seat's placement places on the node, and nothing while -// the placement holds no key for it (issue 1126). +// selfComposer is the node's own composer: it composes what the seat's assignment places on the node, and nothing while +// the assignment holds no key for it (issue 1126). func selfComposer(session *link.Session, node string, hooks composerHooks, say func(string)) *selfcompose.Composer { env := composerEnv(session, node, say, hooks) // And it keeps each placed module's data items in its own state data, which the node's disk-load watches (novox/hq @@ -1018,11 +1018,11 @@ func selfComposer(session *link.Session, node string, hooks composerHooks, say f } // composerEnv is what the node's composers read of the bus, the vault and the agent: its own composer and its answer -// to placement candidates (selfcompose.Shadow) alike. +// to assignment candidates (selfcompose.Shadow) alike. func composerEnv(session *link.Session, node string, say func(string), hooks composerHooks) selfcompose.Env { env := selfcompose.Env{Node: node, Watch: func(ctx context.Context, bucket, pattern string) (<-chan agent.Update, error) { - if bucket == buckets.Placement || bucket == buckets.PlacementCandidates { + if bucket == buckets.Assignments || bucket == buckets.ProposalCandidates { // The seat's states are made when their holder starts: a node-engine that starts first waits for the // bus's word that the bucket was created, and the shadow and the composer say they wait. return bucketWatch{session, bucket}.WatchAwait(ctx, pattern, func(err error) { diff --git a/cmd/mesh-host/nodeagent_integration_test.go b/cmd/mesh-host/nodeagent_integration_test.go index f8fcfbc..7a3551e 100644 --- a/cmd/mesh-host/nodeagent_integration_test.go +++ b/cmd/mesh-host/nodeagent_integration_test.go @@ -7,13 +7,13 @@ import ( "crypto/ed25519" "encoding/json" "errors" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" builderbuilds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "github.com/novox/mesh-host/internal/buckets" "github.com/novox/mesh-host/internal/buckets/bucketstest" @@ -230,16 +230,16 @@ func within(t *testing.T, d time.Duration, what string, ok func() bool) { } // Issue 1126 (ADR 0547 §2, the milestone's H8): the node agent started whole against a real bus, once the catalogue -// places its node (the placement written after it started), composes a module from its want, the indexer's manifest, its record and its mesh-config value, and applies it, +// places its node (the assignment written after it started), composes a module from its want, the indexer's manifest, its record and its mesh-config value, and applies it, // with no key in the controller's compositions; a changed config value is composed again on its event and applied as a -// revision of what runs. And a catalogue pull request's placement candidate equal to its placement is answered "same" +// revision of what runs. And a catalogue pull request's assignment candidate equal to its assignment is answered "same" // in the node's own key, and nothing is applied for it (ADR 0559 §4, subtask 10 of the milestone). // // And (issue 1126, ADR 0560) it reads, through its own session under the grant the grants part composes for it // (nodegrant.For with the served key, as the grants part grants it), the build of a placed module that declares one and the one config value // a provider on another node serves from (a mail domain); under a grant without that served key the bus refuses the // read and the node composes nothing. -func TestNatsANodeThePlacementNamesComposesAndAppliesItsOwn(t *testing.T) { +func TestNatsANodeTheAssignmentsNameComposesAndAppliesItsOwn(t *testing.T) { t.Run("without the served key the bus refuses the read", func(t *testing.T) { b := newItBed(t) stop, _ := h8(t, b, false) @@ -260,7 +260,7 @@ func TestNatsANodeThePlacementNamesComposesAndAppliesItsOwn(t *testing.T) { b := newItBedAs(t, nodegrant.For(itNode, "it2.mailer.domain")) stop, st := h8(t, b, true) defer stop() - placement, resolved, candidates := st.placement, st.resolved, st.candidates + assignment, resolved, candidates := st.assignment, st.resolved, st.candidates hkey, place, fp := st.hkey, st.place, st.fp within(t, 5*time.Second, "the node did not compose and apply its own module once placed", func() bool { return b.file("web") == "greeting=bonjour\n" }) // ADR 0565 §6 (1953): the node's engine writes its own key of the state packages, under the grant the grants part @@ -277,7 +277,7 @@ func TestNatsANodeThePlacementNamesComposesAndAppliesItsOwn(t *testing.T) { t.Errorf("the node's packages key was written again with no package changed: %v, was %v", e, written) } t.Run("one answer per candidate revision", func(t *testing.T) { h8once(t, b, resolved, candidates, place) }) - h8back(t, b, placement, resolved, candidates, hkey) + h8back(t, b, assignment, resolved, candidates, hkey) } // h8once is issue 1126, 2026-10-28 (shanks answered probe/1126-seed-7 at revision 84 every 13-17 s, its bus flooded): @@ -290,7 +290,7 @@ func h8once(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, place [] defer b.mu.Unlock() n := 0 for _, l := range b.said { - if strings.HasPrefix(l, "answering placement candidate feat-x ") { + if strings.HasPrefix(l, "answering assignment candidate feat-x ") { n++ } } @@ -320,8 +320,8 @@ func h8once(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, place [] if got := answering(); got != settled { t.Errorf("feat-x answered %d more time(s) at the same revision, with nothing it is composed from changed", got-settled) } - same, _ := meshmoduleassignerclient.DecodePlacement(place) - craw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "feat-x", Placement: &same}) + same, _ := meshmoduleassignerclient.DecodeAssignments(place) + craw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "feat-x", Assignments: &same}) if err != nil { t.Fatal(err) } @@ -336,11 +336,11 @@ func h8once(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, place [] } } -// h8back is A7 of issue 1126: no composition made elsewhere is applied. A candidate deleting the node's placement is -// refused, never answered as a return to the controller's compositions; the placement key deleted leaves the agent on +// h8back is A7 of issue 1126: no composition made elsewhere is applied. A candidate deleting the node's assignment is +// refused, never answered as a return to the controller's compositions; the assignment key deleted leaves the agent on // its own composer, composing nothing and keeping what runs, said in the journal, the node's have still saying it // composes its own. -func h8back(t *testing.T, b *itBed, placement, resolved, candidates nats.KeyValue, hkey string) { +func h8back(t *testing.T, b *itBed, assignment, resolved, candidates nats.KeyValue, hkey string) { t.Helper() node := func() nodeengine.HaveByNodeNodeValue { e, err := b.haves.Get(hkey) @@ -361,7 +361,7 @@ func h8back(t *testing.T, b *itBed, placement, resolved, candidates nats.KeyValu if _, err := candidates.Put(ckey, craw); err != nil { t.Fatal(err) } - within(t, 10*time.Second, "the node did not refuse a candidate taking its placement away", func() bool { + within(t, 10*time.Second, "the node did not refuse a candidate taking its assignment away", func() bool { a := node().Candidates["revert-x"] return a.Proposal == "revert-x" && a.Returns == "" && a.Verdict == nodeengine.CandidateAnswerVerdictRefused && strings.Contains(a.Refused, "composes nothing") @@ -372,30 +372,30 @@ func h8back(t *testing.T, b *itBed, placement, resolved, candidates nats.KeyValu if _, err := resolved.Put(itNode+".web.greeting", []byte(`{"value":"salut","source":"module"}`)); err != nil { t.Fatal(err) } - if err := placement.Delete(pkey); err != nil { + if err := assignment.Delete(pkey); err != nil { t.Fatal(err) } - within(t, 15*time.Second, "the node did not say it composes nothing once its placement went", func() bool { + within(t, 15*time.Second, "the node did not say it composes nothing once its assignment went", func() bool { b.mu.Lock() said := strings.Join(b.said, "\n") b.mu.Unlock() - return strings.Contains(said, "key in the seat's placement state is gone: it composes nothing of its own") + return strings.Contains(said, "key in the seat's assignment state is gone: it composes nothing of its own") }) time.Sleep(3 * time.Second) // what a pass in flight would mark lands within it b.mu.Lock() said := strings.Join(b.said, "\n") b.mu.Unlock() if n := node(); n.Composes != nec.ComposesOwn || strings.Contains(said, "applying the controller's compositions") { - t.Errorf("without its placement the node turned to the controller's compositions: %q", n.Composes) + t.Errorf("without its assignment the node turned to the controller's compositions: %q", n.Composes) } if h := b.have("web"); h.Composed != ran || h.Phase == nec.PhaseFailed || b.file("web") != "greeting=salut\n" { - t.Errorf("without its placement what runs moved: %+v, %q", h, b.file("web")) + t.Errorf("without its assignment what runs moved: %+v, %q", h, b.file("web")) } } // h8state is what h8 set up that the rest of H8 writes to. type h8state struct { - placement, resolved, candidates nats.KeyValue + assignment, resolved, candidates nats.KeyValue // packages is the engine's state packages (ADR 0565 §6), as the grants part creates it from the engine's manifest. packages nats.KeyValue hkey string @@ -414,10 +414,10 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) { } return kv } - placement, modules, manifests := kvOf(buckets.Placement), kvOf(buckets.Modules), kvOf(buckets.Manifests) + assignment, modules, manifests := kvOf(buckets.Assignments), kvOf(buckets.Modules), kvOf(buckets.Manifests) resolved := kvOf(selfcompose.ResolvedBucket) builds := kvOf(builderbuilds.BuilderBuildsBucket) - candidates := kvOf(buckets.PlacementCandidates) + candidates := kvOf(buckets.ProposalCandidates) packages := kvOf(link.PackagesBucket) path := filepath.Join(b.dir, "web.conf") man := `{"module":"web","version":"1","config":{"greeting":{"type":"string","description":"what the file says","default":"hello"}},` + @@ -429,11 +429,11 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) { mkey, _ := meshcatalogclient.ManifestsKey(digest) fp := fpIt("a") modv, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/web", Commit: strings.Repeat("1", 40)}) - place, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: itNode, Modules: []meshmoduleassigner.PlacedModule{{Module: "web"}}}) + place, _ := meshmoduleassignerclient.EncodeAssignments(meshmoduleassigner.Assignments{Node: itNode, Modules: []meshmoduleassigner.PlacedModule{{Module: "web"}}}) rec, _ := nec.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: itNode, Capabilities: []string{"service-manager", "package-manager"}}}) // it1 in one key per module and node (research 072 row 37), it2 in the form before: a node reads both. pkey, _ := meshmoduleassignerclient.NodeKey(itNode) - places, _ := meshmoduleassignerclient.PlacementValues(meshmoduleassigner.Placement{Node: itNode, Modules: []meshmoduleassigner.PlacedModule{{Module: "web"}}}) + places, _ := meshmoduleassignerclient.AssignmentsValues(meshmoduleassigner.Assignments{Node: itNode, Modules: []meshmoduleassigner.PlacedModule{{Module: "web"}}}) place = places[pkey] hkey, _ := nec.HaveNodeKey(itNode) // A provider on it2 serving from its own config value: a mail domain, which it1's composition settles. @@ -443,9 +443,9 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) { mlraw, _ := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "mailer", Manifest: mailer}) mlkey, _ := meshcatalogclient.ManifestsKey(mdigest) mlmod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpIt("b"), Manifest: mdigest, Repository: "novox/mailer", Commit: strings.Repeat("2", 40)}) - place2, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "it2", Modules: []meshmoduleassigner.PlacedModule{{Module: "mailer"}}}) + places2, _ := meshmoduleassignerclient.AssignmentsValues(meshmoduleassigner.Assignments{Node: "it2", Modules: []meshmoduleassigner.PlacedModule{{Module: "mailer"}}}) rec2, _ := nec.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "it2", Capabilities: []string{"service-manager"}}}) - pkey2, _ := meshmoduleassignerclient.LegacyKey("it2") + pkey2, _ := meshmoduleassignerclient.NodeKey("it2") hkey2, _ := nec.HaveNodeKey("it2") buildKey, _ := builderbuilds.BuilderBuildKey("web", fp) done, _ := builderbuilds.EncodeBuilderBuild(builderbuilds.BuilderBuild{State: builderbuilds.BuildDone, Manifest: digest, @@ -460,7 +460,7 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) { value []byte }{{manifests, mkey, mraw}, {modules, "web", modv}, {b.haves, hkey, rec}, {resolved, itNode + ".web.greeting", []byte(`{"value":"bonjour","source":"module"}`)}, - {manifests, mlkey, mlraw}, {modules, "mailer", mlmod}, {b.haves, hkey2, rec2}, {placement, pkey2, place2}, + {manifests, mlkey, mlraw}, {modules, "mailer", mlmod}, {b.haves, hkey2, rec2}, {assignment, pkey2, places2[pkey2]}, {assignment, "it2.mailer", places2["it2.mailer"]}, {resolved, "it2.mailer.domain", []byte(`{"value":"example.org","source":"module"}`)}, {builds, buildKey, firstBuild}} { if _, err := w.kv.Put(w.key, w.value); err != nil { t.Fatal(err) @@ -479,10 +479,10 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) { if b.file("web") != "" { t.Fatal("a node the catalogue does not place composed its own") } - if _, err := placement.Put(itNode+".web", places[itNode+".web"]); err != nil { + if _, err := assignment.Put(itNode+".web", places[itNode+".web"]); err != nil { t.Fatal(err) } - if _, err := placement.Put(pkey, place); err != nil { + if _, err := assignment.Put(pkey, place); err != nil { t.Fatal(err) } if checkBuild { @@ -503,7 +503,7 @@ func h8(t *testing.T, b *itBed, checkBuild bool) (func(), h8state) { t.Fatal(err) } } - return stop, h8state{placement: placement, resolved: resolved, candidates: candidates, packages: packages, hkey: hkey, place: place, fp: fp} + return stop, h8state{assignment: assignment, resolved: resolved, candidates: candidates, packages: packages, hkey: hkey, place: place, fp: fp} } // h8rest is H8 once the node composes its own: the have, a config revision, and the candidate answered "same". @@ -538,13 +538,13 @@ func h8rest(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, hkey str return h.Fp == fp && h.Composed != first && h.Phase == nec.PhaseRunning }) - // The node says its facts (its record, as the test wrote it), then a candidate equal to its placement is written: the + // The node says its facts (its record, as the test wrote it), then a candidate equal to its assignment is written: the // node answers "same" at the candidate's head and revision in its own key, and applies nothing. b.session.SayFacts(context.Background(), link.Report{Profile: map[string]any{"capabilities": []any{ map[string]any{"name": "service-manager", "present": true}}}}) second := b.have("web").Composed - same, _ := meshmoduleassignerclient.DecodePlacement(place) - craw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "feat-x", Placement: &same}) + same, _ := meshmoduleassignerclient.DecodeAssignments(place) + craw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "feat-x", Assignments: &same}) if err != nil { t.Fatal(err) } @@ -565,7 +565,7 @@ func h8rest(t *testing.T, b *itBed, resolved, candidates nats.KeyValue, hkey str return err == nil && answer.Proposal == "feat-x" && n.Record != nil }) if answer.Verdict != nodeengine.CandidateAnswerVerdictSame || answer.Revision != int64(crev) || len(answer.Same) != 1 || answer.Same[0] != "web" { - t.Errorf("the answer to a candidate equal to the placement: %+v", answer) + t.Errorf("the answer to a candidate equal to the assignment: %+v", answer) } if h := b.have("web"); h.Composed != second || b.file("web") != "greeting=salut\n" { t.Errorf("a candidate moved what runs: %+v, %q", h, b.file("web")) @@ -768,10 +768,10 @@ func TestNatsTheBusServerIsTakenOverAndReplacedOnlyWhenItsBuildChangesIt(t *test } return kv } - placement, modules, manifests := kvOf(buckets.Placement), kvOf(buckets.Modules), kvOf(buckets.Manifests) + assignment, modules, manifests := kvOf(buckets.Assignments), kvOf(buckets.Modules), kvOf(buckets.Manifests) kvOf(selfcompose.ResolvedBucket) builds := kvOf(builderbuilds.BuilderBuildsBucket) - kvOf(buckets.PlacementCandidates) + kvOf(buckets.ProposalCandidates) kvOf(link.PackagesBucket) // The bus module at a build, done: its manifest written and the indexer's module naming it at fp. publish := func(fp, env string) { @@ -798,7 +798,7 @@ func TestNatsTheBusServerIsTakenOverAndReplacedOnlyWhenItsBuildChangesIt(t *test rec, _ := nec.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: itNode, Capabilities: []string{"service-manager", "package-manager", "container-runtime"}}}) hkey, _ := nec.HaveNodeKey(itNode) - places, _ := meshmoduleassignerclient.PlacementValues(meshmoduleassigner.Placement{Node: itNode, Modules: []meshmoduleassigner.PlacedModule{{Module: "nats"}}}) + places, _ := meshmoduleassignerclient.AssignmentsValues(meshmoduleassigner.Assignments{Node: itNode, Modules: []meshmoduleassigner.PlacedModule{{Module: "nats"}}}) // The have the engine wrote of the folded bus: the old-form build, no fingerprint. natsHave, _ := nec.HaveKey(itNode, "nats") oldHave, _ := nec.EncodeHave(nodeengine.HaveByNodeModuleValue{Build: "build-1792674595610176744", Phase: nec.PhaseRunning, Health: nec.HealthOK}) @@ -848,7 +848,7 @@ func TestNatsTheBusServerIsTakenOverAndReplacedOnlyWhenItsBuildChangesIt(t *test stop := b.start() defer stop() for key, value := range places { - if _, err := placement.Put(key, value); err != nil { + if _, err := assignment.Put(key, value); err != nil { t.Fatal(err) } } diff --git a/cmd/mesh-host/trust.go b/cmd/mesh-host/trust.go index 405b3e7..65d3118 100644 --- a/cmd/mesh-host/trust.go +++ b/cmd/mesh-host/trust.go @@ -7,11 +7,11 @@ import ( "encoding/json" "errors" "fmt" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "github.com/novox/mesh-host/internal/buckets" "os" @@ -29,7 +29,7 @@ import ( // **What this engine trusts a mesh signature from** (novox/hq ADR 0550 §2; design 47's L3 item d; issue 1126): the // vault's key alone, read from the vault's state `signers`, which only the vault may write: the bus grants are its -// anchor, as the wants' and the placement's. The controller's key, which the node was enrolled with (its membership's +// anchor, as the wants' and the assignment's. The controller's key, which the node was enrolled with (its membership's // signer), is trusted never: the controller ended by its unassignment (the operator, 2026-11-02) and its lease is never // handed, so no gate waits on it. What it trusts now the node states in its record (NodeRecord.Trusts). type meshTrust struct { @@ -180,20 +180,17 @@ func followTrust(ctx context.Context, session *link.Session, trust *meshTrust, s } // placedAllHave says whether every module the seat mesh-module-assigner places on node has a have of its own, read from the -// bus: then the kept declaration is read no more (keptRetired). A node the seat does not place, or whose placement or haves +// bus: then the kept declaration is read no more (keptRetired). A node the seat does not place, or whose assignment or haves // cannot be read, keeps reading it. func placedAllHave(ctx context.Context, session *link.Session, node string) (bool, string) { key, err := meshmoduleassignerclient.NodeKey(node) if err != nil { return false, err.Error() } - legacy, _ := meshmoduleassignerclient.LegacyKey(node) - // Its node key names every module placed (research 072 row 37); before the holder converted it, its legacy key does. + // Its node key names every module placed (research 072 row 37). values := map[string][]byte{} - for _, k := range []string{key, legacy} { - if h, err := session.KVHistory(ctx, buckets.Placement, k); err == nil && len(h) > 0 && !h[len(h)-1].Deleted { - values[k] = h[len(h)-1].Value - } + if h, err := session.KVHistory(ctx, buckets.Assignments, key); err == nil && len(h) > 0 && !h[len(h)-1].Deleted { + values[key] = h[len(h)-1].Value } p, err := placedModules(node, values) if err != nil { @@ -204,27 +201,23 @@ func placedAllHave(ctx context.Context, session *link.Session, node string) (boo }) } -// placedModules is the modules node's node key names (a placement of their names alone), or its legacy key's placement -// where it has no node key; an error when neither reads. -func placedModules(node string, values map[string][]byte) (meshmoduleassigner.Placement, error) { +// placedModules is the modules node's node key names (an assignment of their names alone); an error when it does not +// read. +func placedModules(node string, values map[string][]byte) (meshmoduleassigner.Assignments, error) { key, _ := meshmoduleassignerclient.NodeKey(node) - if raw, ok := values[key]; ok { - n, err := meshmoduleassignerclient.DecodeNode(raw) - if err != nil { - return meshmoduleassigner.Placement{}, err - } - p := meshmoduleassigner.Placement{Node: node} - for _, r := range n.Modules { - p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: r.Module}) - } - return p, nil - } - legacy, _ := meshmoduleassignerclient.LegacyKey(node) - raw, ok := values[legacy] + raw, ok := values[key] if !ok { - return meshmoduleassigner.Placement{}, errors.New("its placement is not read") + return meshmoduleassigner.Assignments{}, errors.New("its assignment is not read") } - return meshmoduleassignerclient.DecodePlacement(raw) + n, err := meshmoduleassignerclient.DecodeNode(raw) + if err != nil { + return meshmoduleassigner.Assignments{}, err + } + p := meshmoduleassigner.Assignments{Node: node} + for _, r := range n.Modules { + p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: r.Module}) + } + return p, nil } // allHave says whether has finds, for every module p places, a have that names a fingerprint of the new form, and why @@ -232,7 +225,7 @@ func placedModules(node string, values map[string][]byte) (meshmoduleassigner.Pl // declaration is the one record of what runs of it: retired then, the node's own composed it as nothing and refused to // remove what runs (issue 774, 2026-10-30 19:43: systemd-resolved on shanks, nats and restic on novox, each node's own // apply refused from then on). -func allHave(p meshmoduleassigner.Placement, has func(module string) (nodeengine.HaveByNodeModuleValue, bool, error)) (bool, string) { +func allHave(p meshmoduleassigner.Assignments, has func(module string) (nodeengine.HaveByNodeModuleValue, bool, error)) (bool, string) { if len(p.Modules) == 0 { return false, "the seat mesh-module-assigner places no module here" } diff --git a/cmd/mesh-host/whatruns_test.go b/cmd/mesh-host/whatruns_test.go index 79bbda2..fbead93 100644 --- a/cmd/mesh-host/whatruns_test.go +++ b/cmd/mesh-host/whatruns_test.go @@ -6,8 +6,8 @@ import ( "encoding/json" "errors" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" engcomp "github.com/novox/mesh-host/internal/compositions" "path/filepath" @@ -120,7 +120,7 @@ func TestDeclaredNowReadsWhatRunsWhenTheAgentIsRunning(t *testing.T) { if d, err := keptDeclaration(state, nil); err != nil || len(d.Resources) != 1 { t.Fatalf("before the read is retired: %+v, %v", d, err) } - placed := meshmoduleassigner.Placement{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "zsh"}, {Module: "lemurs"}}} + placed := meshmoduleassigner.Assignments{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "zsh"}, {Module: "lemurs"}}} // Issue 774, 2026-10-30 19:43: a have naming only a build of the old form (systemd-resolved on shanks, folded in the // node's own) keeps the kept declaration read, since it is the one record of what runs of it. newForm := nodeengine.HaveByNodeModuleValue{Fp: meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64), Phase: nodeengineclient.PhaseRunning} @@ -139,7 +139,7 @@ func TestDeclaredNowReadsWhatRunsWhenTheAgentIsRunning(t *testing.T) { } } // The module the mesh provides never has a fingerprint, and keeps no read (review of issue 774). - withWireguard := meshmoduleassigner.Placement{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "zsh"}, {Module: overlay.Name}}} + withWireguard := meshmoduleassigner.Assignments{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "zsh"}, {Module: overlay.Name}}} if got, why := allHave(withWireguard, func(m string) (nodeengine.HaveByNodeModuleValue, bool, error) { if m == overlay.Name { return oldForm, true, nil @@ -148,7 +148,7 @@ func TestDeclaredNowReadsWhatRunsWhenTheAgentIsRunning(t *testing.T) { }); !got { t.Errorf("%s without a fingerprint beside zsh at the new form keeps the read: %s", overlay.Name, why) } - if got, _ := allHave(meshmoduleassigner.Placement{Node: "n1"}, nil); got { + if got, _ := allHave(meshmoduleassigner.Assignments{Node: "n1"}, nil); got { t.Error("a node with nothing placed retires the read") } keptRetired.Store(true) diff --git a/go.mod b/go.mod index 70226eb..295c246 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,9 @@ require ( git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000 - git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000 - git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 - git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 80c13cb..d1e8bb5 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -19,7 +19,7 @@ import ( "fmt" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "github.com/novox/mesh-host/internal/compositions" "slices" "sort" diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 36d826c..0b3738d 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -6,8 +6,8 @@ import ( "fmt" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "github.com/novox/mesh-host/internal/buckets/bucketstest" "github.com/novox/mesh-host/internal/compositions" "github.com/novox/mesh-host/internal/judge" diff --git a/internal/bootstrap/bootstrap.go b/internal/bootstrap/bootstrap.go index 5835bc5..16fd928 100644 --- a/internal/bootstrap/bootstrap.go +++ b/internal/bootstrap/bootstrap.go @@ -183,7 +183,7 @@ type Options struct { // SDKSource is where the mesh's shared library is built from. It is published to the package // registry before the base is built, because the base resolves it by version (novox/hq ADR 0076). SDKSource Source - // Site is where this machine sits, for the private network's placement. + // Site is where this machine sits, for the private network's assignment. Site string // Answers are the choices, answered by flag: name → answer. What a terminal would be asked. Answers map[string]string diff --git a/internal/bootstrap/phase2.go b/internal/bootstrap/phase2.go index e49eb1b..dba8989 100644 --- a/internal/bootstrap/phase2.go +++ b/internal/bootstrap/phase2.go @@ -146,7 +146,7 @@ func InstallFromCatalogue(ctx context.Context, o Options, control controlPlane, // // **Assigning is not being on the network** — a lesson paid for: the module installed, the names // file was written with no names in it, and everything reported success, because nobody had said -// where this machine IS. So placement is part of the step, not a separate act. +// where this machine IS. So assignment is part of the step, not a separate act. func PlaceOnTheNetwork(ctx context.Context, o Options, control controlPlane, brokerAddress string, say func(string)) error { diff --git a/internal/buckets/buckets.go b/internal/buckets/buckets.go index c83279d..d495046 100644 --- a/internal/buckets/buckets.go +++ b/internal/buckets/buckets.go @@ -8,9 +8,9 @@ import ( vault "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault" - seat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - wants "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + wants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "git.novox.be/novox/mesh-sdk/go/keys" ) @@ -27,13 +27,13 @@ const ( // HaveHistory is the number of revisions the have bucket keeps per key. const HaveHistory = 20 -// The states of the placement seat's holder and of the wants seat's holder that the engine reads, named by the holder's +// The states of the assigner seat's holder and of the wants seat's holder that the engine reads, named by the holder's // module as every state is (ADR 0634 §1, finding F4: the holder's name is kept here, in the engine's code). const ( - // Placement holds a node's placement, `.node`, and each of its modules, `.`. - Placement = AssignerHolder + "_" + seat.StatePlacement - // PlacementCandidates holds a node's candidate of a proposal, `.`. - PlacementCandidates = AssignerHolder + "_" + seat.StateCandidates + // Assignments holds a node's assignments, `.node`, and each of its modules, `.`. + Assignments = AssignerHolder + "_" + seat.StateAssignments + // ProposalCandidates holds a node's candidate of a proposal, `.`. + ProposalCandidates = AssignerHolder + "_" + seat.StateCandidates // Wants holds what each node should run, `want..` and `want..node`. Wants = AssignerHolder + "_" + wants.StateWants diff --git a/internal/buckets/buckets_test.go b/internal/buckets/buckets_test.go index 4700d39..0fd64c3 100644 --- a/internal/buckets/buckets_test.go +++ b/internal/buckets/buckets_test.go @@ -59,7 +59,7 @@ func TestACandidateKeyEscapesTheGroup(t *testing.T) { // holder (ADR 0625 §4), so these rows are the one place the node-engine's code says them. func TestTheHoldersBucketsAreTheirStatesBuckets(t *testing.T) { for name, c := range map[string][2]string{ - "Placement": {Placement, "mesh-delivery-assigner_placement"}, "PlacementCandidates": {PlacementCandidates, "mesh-delivery-assigner_candidates"}, + "Assignments": {Assignments, "mesh-delivery-assigner_assignments"}, "ProposalCandidates": {ProposalCandidates, "mesh-delivery-assigner_candidates"}, "Wants": {Wants, "mesh-delivery-assigner_wants"}, "Modules": {Modules, "mesh-delivery-indexer_modules"}, "Manifests": {Manifests, "mesh-delivery-indexer_manifests"}, "Verdicts": {Verdicts, "mesh-delivery-indexer_verdicts"}, "Candidates": {Candidates, "mesh-delivery-indexer_candidates"}, "Provisions": {Provisions, "mesh-provisioner_provisions"}, diff --git a/internal/buckets/bucketstest/bucketstest.go b/internal/buckets/bucketstest/bucketstest.go index 03912cc..b849932 100644 --- a/internal/buckets/bucketstest/bucketstest.go +++ b/internal/buckets/bucketstest/bucketstest.go @@ -1,4 +1,4 @@ -// Package bucketstest writes the values of the states the engine only reads (a want, a placement candidate) as their +// Package bucketstest writes the values of the states the engine only reads (a want, a assignment candidate) as their // holders do, for the tests that stand in for the holders. package bucketstest @@ -7,8 +7,8 @@ import ( "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" ) // Version is the value version the holders write. @@ -26,13 +26,13 @@ func NodeWant(w meshwants.WantsByWantNodeNodeValue) ([]byte, error) { return json.Marshal(w) } -// Candidate is a node's candidate of a proposal as the placement seat's holder writes it. +// Candidate is a node's candidate of a proposal as the assignment seat's holder writes it. func Candidate(c meshmoduleassigner.CandidatesByProposalNodeValue) ([]byte, error) { c.V = Version - if c.Placement != nil { - p := *c.Placement + if c.Assignments != nil { + p := *c.Assignments p.V = Version - c.Placement = &p + c.Assignments = &p } return json.Marshal(c) } @@ -48,7 +48,7 @@ func Pull(head meshdeliveryclient.Head, modules []string, fps map[string]meshcat return meshcatalog.CandidatesByGroupValuePullsItem{Repository: head.Repository, Number: int64(head.Number), Head: head.Head, Modules: modules, Fps: fps} } -// Raw is each value as JSON, as a composer case carries verdicts and placements. +// Raw is each value as JSON, as a composer case carries verdicts and assignments. func Raw[V any](in map[string]V) map[string]json.RawMessage { out := map[string]json.RawMessage{} for k, v := range in { diff --git a/internal/compose/composed.go b/internal/compose/composed.go index f7fe9ad..e976e04 100644 --- a/internal/compose/composed.go +++ b/internal/compose/composed.go @@ -50,7 +50,7 @@ func CompositionsApplied(in Input, node string, fps map[string]string) (map[stri } // CompositionsShadow is CompositionsApplied for a composition that is compared and never applied (a node's answer to a -// placement candidate, novox/hq ADR 0559 §4): composed in the Foreseeing mode, so an own secret never issued (a module +// assignment candidate, novox/hq ADR 0559 §4): composed in the Foreseeing mode, so an own secret never issued (a module // the candidate adds) is a named stand-in rather than an error, and what it stands in for is answered (stood, sorted, // the bus's user list left out) rather than refused, so the caller judges each stand-in: a value not yet issued for a // module the candidate adds, or one missing for a module the node runs. A module whose every value is held composes diff --git a/internal/compose/credentials.go b/internal/compose/credentials.go index e05339c..8c429ed 100644 --- a/internal/compose/credentials.go +++ b/internal/compose/credentials.go @@ -351,7 +351,7 @@ func (c *composer) ownSecretsFor(node Node, plan catalogue.Resolution, handing c if !held && mode == Foreseeing { // Asked ahead of the send: what the send would do about it, by the send's own rules. Made by it -- a stand-in, // named; refused by it -- refused here, the same words (novox/hq issue 275). A bus account not yet issued is - // foreseen, never refused: the vault mints it from the grants part's wanted list once the placement is written + // foreseen, never refused: the vault mints it from the grants part's wanted list once the assignment is written // on the operator's Approve (ADR 0561 §1, ADR 0567 §5), so a composition asked ahead of that cannot hold it // (issue 774, 2026-10-30: every proposal placing mesh-conditions, mesh-self-check or mesh-healers refused). if err := c.wouldMakeSecretForModule(node, m.Module, name); err != nil { diff --git a/internal/compose/credentials_test.go b/internal/compose/credentials_test.go index 50ddfc6..47b5671 100644 --- a/internal/compose/credentials_test.go +++ b/internal/compose/credentials_test.go @@ -118,7 +118,7 @@ func TestAnOutsideSecretIsPlacedOnlyOnceAPersonGaveIt(t *testing.T) { // 1364/AC3 (the send's refusals, novox/hq issue 275): in the Foreseeing mode a given secret under an old key is refused in the // controller's words. No secret value is in an error. A bus account not yet issued is foreseen, issued or not (issue 774: the vault -// mints it once the placement is written on the Approve, ADR 0561 §1, ADR 0567 §5; mesh-self-check's proposal on ace was refused +// mints it once the assignment is written on the Approve, ADR 0561 §1, ADR 0567 §5; mesh-self-check's proposal on ace was refused // "has no bus credential: nothing was issued for ace.mesh-self-check"). func TestTheSendsRefusalsAreRefusedInTheSendsWords(t *testing.T) { broker := catalogue.Manifest{Module: "mesh-self-check", Version: "1", RunsAs: "mesh-self-check", diff --git a/internal/compose/frombus/answer.go b/internal/compose/frombus/answer.go index a3b9093..702f1cb 100644 --- a/internal/compose/frombus/answer.go +++ b/internal/compose/frombus/answer.go @@ -18,8 +18,8 @@ import ( "github.com/novox/mesh-host/internal/compose/resolution" ) -// Answer is a node's answer to its proposal's placement candidates (novox/hq ADR 0567 §3, ADR 0559 §4): s.Node composed in shadow with -// s.Placements laid over the trunk's placement (compose.CompositionsShadow, its own sealed values and boxes through r as +// Answer is a node's answer to its proposal's assignment candidates (novox/hq ADR 0567 §3, ADR 0559 §4): s.Node composed in shadow with +// s.Assignments laid over the trunk's assignment (compose.CompositionsShadow, its own sealed values and boxes through r as // for its own composition), compared with what it runs (compositions.CompareComposed). It applies nothing and writes // nothing; the proposal and the revision are the candidate's, said back in the answer. // @@ -35,9 +35,9 @@ func Answer(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]st a.Verdict = nodeengine.CandidateAnswerVerdictRefused return a } - trunk, err := r.Placements(ctx) + trunk, err := r.Assignments(ctx) if err != nil { - return refuse(fmt.Errorf("frombus: placement: %w", err)) + return refuse(fmt.Errorf("frombus: assignment: %w", err)) } if s.Running == nil { s.Running = running @@ -144,7 +144,7 @@ func Answer(ctx context.Context, r Reader, mesh Mesh, s Scope, fps map[string]st // What the proposal adds, by node and module: placed by a candidate and not by the trunk on that node. added := map[string]bool{} - for node, p := range s.Placements { + for node, p := range s.Assignments { if p == nil { continue } @@ -430,7 +430,7 @@ const ( leaveOwn = "own-secrets" ) -// assignedOn is the modules placed on node, in placement order. +// assignedOn is the modules placed on node, in assignment order. func assignedOn(in compose.Input, node string) []string { for _, n := range in.Nodes { if n.Name == node { @@ -454,7 +454,7 @@ func manifestOn(in compose.Input, node, module string) (catalogue.Manifest, bool } // culprit is the module placed on node whose composition the error is of, and what of its offer to leave out: the -// first, in placement order, whose stub (compose.Stub) makes the composition in mode succeed or fail otherwise (leave +// first, in assignment order, whose stub (compose.Stub) makes the composition in mode succeed or fail otherwise (leave // ""); else the first whose stub without what it contributes to one provision does (leave that provision: de-spiegel's // routes, its names unset, and not its smtp account, issue 1960); else the first whose bare stub does (leaveAll: its // stub contributing nothing, its own secret never issued, issue 1126 seed-8). Empty when none does (the error is not diff --git a/internal/compose/frombus/answer_rows_test.go b/internal/compose/frombus/answer_rows_test.go index 261f490..6251e35 100644 --- a/internal/compose/frombus/answer_rows_test.go +++ b/internal/compose/frombus/answer_rows_test.go @@ -5,8 +5,8 @@ import ( "encoding/json" "fmt" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" @@ -41,11 +41,11 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { lemurs := []byte(`{"module":"lemurs","version":"1","resources":[{"id":"service","type":"service","unit":"lemurs.service","state":"running","boot":"enabled"}]}`) fp := func(c string) string { return "src2:" + strings.Repeat(c, 64) } mk := func(placed ...string) *bus { - p := meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}} + p := meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}} for _, m := range placed { p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m}) } - b := &bus{placements: map[string]meshmoduleassigner.Placement{"n1": p}, + b := &bus{assignments: map[string]meshmoduleassigner.Assignments{"n1": p}, records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t), SealingKey: "sk", Capabilities: []string{"systemd", "container-runtime"}}}, modules: map[string]meshcatalog.ModulesByModuleValue{"lemurs": {Manifest: meshcatalogclient.ManifestDigest(lemurs)}}, manifests: map[string][]byte{meshcatalogclient.ManifestDigest(lemurs): lemurs}, @@ -69,14 +69,14 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { } return b } - given := func(b *bus) map[string]*meshmoduleassigner.Placement { - p := b.placements["n1"] - return map[string]*meshmoduleassigner.Placement{"n1": &p} + given := func(b *bus) map[string]*meshmoduleassigner.Assignments { + p := b.assignments["n1"] + return map[string]*meshmoduleassigner.Assignments{"n1": &p} } // Every refusal: two builds taken in, and a module the composition refuses (its image never built), each named. b := mk("lemurs", "web", "api", "spiegel") - a := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given(b)}, + a := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given(b)}, map[string]string{"lemurs": fp("e"), "web": fp("1"), "api": fp("2"), "spiegel": fp("3")}, nil, "p-h1", 3) var refused []string for _, r := range a.Refusals { @@ -100,13 +100,13 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { } // Issue 1126: a module the node holds today that the candidate holds too is held as today, no refusal: with every // refused module held today the verdict is no refusal, and each is named under Held. - today := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given(b), HeldToday: map[string]string{"api": "held", "spiegel": "held", "web": "held"}}, + today := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given(b), HeldToday: map[string]string{"api": "held", "spiegel": "held", "web": "held"}}, map[string]string{"lemurs": fp("e"), "web": fp("1"), "api": fp("2"), "spiegel": fp("3")}, nil, "p-h1", 3) if today.Verdict == nodeengine.CandidateAnswerVerdictRefused || len(today.Refusals) != 0 || len(today.Held) != 3 || today.Held[0].Why == "" { t.Errorf("held as today: %+v", today) } // A refused module the node does not hold today stays a refusal: holding only api today leaves spiegel and web refused. - part := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given(b), HeldToday: map[string]string{"api": "held"}}, + part := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given(b), HeldToday: map[string]string{"api": "held"}}, map[string]string{"lemurs": fp("e"), "web": fp("1"), "api": fp("2"), "spiegel": fp("3")}, nil, "p-h1", 3) if part.Verdict != nodeengine.CandidateAnswerVerdictRefused || len(part.Refusals) != 2 || len(part.Held) != 1 || part.Held[0].Module != "api" { t.Errorf("held as today in part: %+v", part) @@ -123,9 +123,9 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { // The fold: spiegel runs inside the node's own composition at a build of the old form. Its resources are carried as // they run, each after the one it runs after: the node's own is the one it runs, and the node composes itself. b = mk("lemurs", overlay.Name, "spiegel") - p := b.placements["n1"] + p := b.assignments["n1"] p.Decided.Endpoint = "n1.example:51820" - b.placements["n1"] = p + b.assignments["n1"] = p fps := map[string]string{"lemurs": fp("e")} stub := Scope{Node: "n1"} in, err := Input(ctx, b, Mesh{}, stub) @@ -169,14 +169,14 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { if string(gotOwn) != string(wantOwn) { t.Errorf("the node's own composition is\n%s\nwant what runs\n%s", gotOwn, wantOwn) } - a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given(b), InOwn: []string{"spiegel"}}, fps, running, "p-h1", 3) + a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given(b), InOwn: []string{"spiegel"}}, fps, running, "p-h1", 3) if a.Verdict != nodeengine.CandidateAnswerVerdictSame { t.Errorf("the fold's answer: %+v, want the same", a) } // (A) The agent also says spiegel runs by module, from the declaration kept before the switch (agent.Everything): a // module the composition folds is judged inside the node's own, not as removed (issue 1126, seed #531's answer). keptToo := append(append([]engcomp.Composed{}, running...), engcomp.Composed{Node: "n1", Module: "spiegel", Resources: carriedRes}) - if a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given(b), InOwn: []string{"spiegel"}}, fps, keptToo, "p-h1", 3); a.Verdict != nodeengine.CandidateAnswerVerdictSame || len(a.Removed) != 0 { + if a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given(b), InOwn: []string{"spiegel"}}, fps, keptToo, "p-h1", 3); a.Verdict != nodeengine.CandidateAnswerVerdictSame || len(a.Removed) != 0 { t.Errorf("a folded module running from the kept declaration is answered %+v, want the same", a) } // (B) The node's own composition carries its bus membership as it runs it (the controller's, from its store): composed @@ -189,7 +189,7 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { } withMembership = append(withMembership, c) } - if a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given(b), InOwn: []string{"spiegel"}}, fps, withMembership, "p-h1", 3); a.Verdict != nodeengine.CandidateAnswerVerdictSame { + if a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given(b), InOwn: []string{"spiegel"}}, fps, withMembership, "p-h1", 3); a.Verdict != nodeengine.CandidateAnswerVerdictSame { t.Errorf("the node's own with its bus membership is answered %+v, want the same", a) } // Without what runs, the fold composes spiegel from its manifest, as before, and the composition refuses it. @@ -202,9 +202,9 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { // "not built for this mesh". { bb := mk("lemurs", overlay.Name, "spiegel") - bp := bb.placements["n1"] + bp := bb.assignments["n1"] bp.Decided.Endpoint = "n1.example:51820" - bb.placements["n1"] = bp + bb.assignments["n1"] = bp sp := bb.modules["spiegel"] sp.Fp2 = fp("3") bb.modules["spiegel"] = sp @@ -237,14 +237,14 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { b.manifests[meshcatalogclient.ManifestDigest(raw)] = raw b.modules[m] = meshcatalog.ModulesByModuleValue{Manifest: meshcatalogclient.ManifestDigest(raw)} } - p = b.placements["n1"] + p = b.assignments["n1"] p.Decided.Endpoint = "n1.example:51820" for i := range p.Modules { if p.Modules[i].Module == "spiegel" { p.Modules[i].Ports = map[string]int64{"8080": 20005} } } - b.placements["n1"] = p + b.assignments["n1"] = p rec := b.records["n1"] rec.OutwardLinks = []string{"eth0"} b.records["n1"] = rec @@ -375,11 +375,11 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { b.manifests[meshcatalogclient.ManifestDigest(raw)] = raw b.modules[m] = meshcatalog.ModulesByModuleValue{Manifest: meshcatalogclient.ManifestDigest(raw)} } - p = b.placements["n1"] + p = b.assignments["n1"] p.Decided.Endpoint = "n1.example:51820" - b.placements["n1"] = p + b.assignments["n1"] = p fps = map[string]string{"lemurs": fp("e"), "proxy": fp("f"), "mailu": fp("d")} - a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given(b), InOwn: []string{"de-spiegel"}}, fps, nil, "p-h1", 3) + a = Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given(b), InOwn: []string{"de-spiegel"}}, fps, nil, "p-h1", 3) refused = nil for _, r := range a.Refusals { refused = append(refused, r.Module) @@ -475,14 +475,14 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { b.manifests[meshcatalogclient.ManifestDigest(raw)] = raw b.modules[m] = meshcatalog.ModulesByModuleValue{Manifest: meshcatalogclient.ManifestDigest(raw)} } - p = b.placements["n1"] + p = b.assignments["n1"] p.Decided.Endpoint = "n1.example:51820" for i := range p.Modules { if p.Modules[i].Module == "site" { p.Modules[i].Ports = map[string]int64{"8080": 20005} } } - b.placements["n1"] = p + b.assignments["n1"] = p rec = b.records["n1"] rec.OutwardLinks = []string{"eth0"} b.records["n1"] = rec @@ -516,14 +516,14 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { b2.manifests[meshcatalogclient.ManifestDigest(raw)] = raw b2.modules[m] = meshcatalog.ModulesByModuleValue{Manifest: meshcatalogclient.ManifestDigest(raw)} } - p2 := b2.placements["n1"] + p2 := b2.assignments["n1"] p2.Decided.Endpoint = "n1.example:51820" for i := range p2.Modules { if p2.Modules[i].Module == "site" { p2.Modules[i].Ports = map[string]int64{"8080": 20005} } } - b2.placements["n1"] = p2 + b2.assignments["n1"] = p2 b2.records["n1"] = rec b2.held = true fps["app"] = fp("9") @@ -561,7 +561,7 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) { for i := range p.Modules { p.Modules[i].Ports = nil } - b.placements["n1"] = p + b.assignments["n1"] = p siteRuns.Resources[1] = json.RawMessage(`{"id":"site.server","module":"site","type":"container","name":"site","image":"registry.example/site@sha256:` + strings.Repeat("c", 64) + `","ports":["8080"]}`) siteRuns.FP = fp("7") b.manifests[meshcatalogclient.ManifestDigest(site)] = site @@ -591,18 +591,18 @@ func otherNodeNotResolvedIsNamed(t *testing.T) { prov := []byte(`{"module":"prov","version":"1","capabilities":["container-runtime"],"provides":[{"name":"thing","scope":"mesh"}]}`) cons := []byte(`{"module":"cons","version":"1","requires":["thing"]}`) // n2 runs prov, which needs a container runtime n2's record does not say it has: n2's set does not resolve here. - b := &bus{placements: map[string]meshmoduleassigner.Placement{ + b := &bus{assignments: map[string]meshmoduleassigner.Assignments{ "n1": {Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1", Endpoint: "n1.example:51820"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "cons"}, {Module: overlay.Name}}}, "n2": {Node: "n2", Decided: meshmoduleassigner.NodeDecided{Address: "10.77.0.2"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "prov"}, {Module: overlay.Name}}}}, records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t)}, "n2": {Node: "n2", TunnelKey: publicKey(t)}}, modules: map[string]meshcatalog.ModulesByModuleValue{"prov": {Manifest: meshcatalogclient.ManifestDigest(prov)}, "cons": {Manifest: meshcatalogclient.ManifestDigest(cons)}}, manifests: map[string][]byte{meshcatalogclient.ManifestDigest(prov): prov, meshcatalogclient.ManifestDigest(cons): cons}} - given := map[string]*meshmoduleassigner.Placement{} - for n, p := range b.placements { + given := map[string]*meshmoduleassigner.Assignments{} + for n, p := range b.assignments { p := p given[n] = &p } - a := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Placements: given}, map[string]string{"cons": "src2:" + strings.Repeat("1", 64)}, nil, "p-h1", 1) + a := Answer(ctx, b, Mesh{}, Scope{Node: "n1", Assignments: given}, map[string]string{"cons": "src2:" + strings.Repeat("1", 64)}, nil, "p-h1", 1) if a.Verdict != nodeengine.CandidateAnswerVerdictRefused || !strings.Contains(a.Refused, "prov on n2 lacks container-runtime") { t.Errorf("a refusal for want of n2's provider = %+v", a) } diff --git a/internal/compose/frombus/export.go b/internal/compose/frombus/export.go index 7d82bf1..bd0ed75 100644 --- a/internal/compose/frombus/export.go +++ b/internal/compose/frombus/export.go @@ -2,7 +2,7 @@ package frombus import ( "fmt" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" sdkkeys "git.novox.be/novox/mesh-sdk/go/keys" "sort" "strconv" @@ -11,12 +11,12 @@ import ( "github.com/novox/mesh-host/internal/compose" ) -// Export is one node's placement as the composition input the controller fills says it (novox/hq ADR 0555 §6, ADR 0567 -// §7): the inverse of Input for what a person decides, so the controller's one read-only verb `placement-export` +// Export is one node's assignment as the composition input the controller fills says it (novox/hq ADR 0555 §6, ADR 0567 +// §7): the inverse of Input for what a person decides, so the controller's one read-only export verb // answers, from its own store by its own code, exactly what the seat's adopt proposes as the seed. Its answer must stay // stable for the same store, since adopt checks its digest again at the warrant. // What the machine observes (its record) is not in it. -func Export(in compose.Input, node string) (meshmoduleassigner.Placement, error) { +func Export(in compose.Input, node string) (meshmoduleassigner.Assignments, error) { var n *compose.Node for i := range in.Nodes { if in.Nodes[i].Name == node { @@ -24,11 +24,11 @@ func Export(in compose.Input, node string) (meshmoduleassigner.Placement, error) } } if n == nil { - return meshmoduleassigner.Placement{}, fmt.Errorf("frombus: the input has no node %q", node) + return meshmoduleassigner.Assignments{}, fmt.Errorf("frombus: the input has no node %q", node) } - // At the version a placement key is read at, with a list of modules even when none is placed: the export is the + // At the version a assignment key is read at, with a list of modules even when none is placed: the export is the // seed's value, and the reader refuses a value of version 0 or without its modules. - p := meshmoduleassigner.Placement{V: sdkkeys.Version, Node: node, Modules: []meshmoduleassigner.PlacedModule{}, Decided: meshmoduleassigner.NodeDecided{ + p := meshmoduleassigner.Assignments{V: sdkkeys.Version, Node: node, Modules: []meshmoduleassigner.PlacedModule{}, Decided: meshmoduleassigner.NodeDecided{ Endpoint: n.Place.Endpoint, Site: n.Place.Site, Hub: n.Place.Hub, Address: n.Place.Address, PublicDomain: n.PublicDomain, Account: n.Account, AccountHome: n.AccountHome, AgentAccount: n.AgentAccount, AgentAccountHome: n.AgentAccountHome, Adopted: n.Adopted, Taken: n.Taken}} @@ -66,7 +66,7 @@ func Export(in compose.Input, node string) (meshmoduleassigner.Placement, error) m.Bindings[provision] = meshmoduleassigner.Provider{Node: to.Node, Module: to.Module} } // A module in the settings form keeps its config values in its layers, not in mesh-config: they are exported with - // the placement. A module in the config form has mesh-config's values laid into its layer, which are not. + // the assignment. A module in the config form has mesh-config's values laid into its layer, which are not. settingsForm := !in.Catalogue[module].DeclaresConfig() for _, layer := range n.Settings[module] { if layer.From == catalogue.MeshWideLayer { diff --git a/internal/compose/frombus/frombus.go b/internal/compose/frombus/frombus.go index a7cc735..f83a051 100644 --- a/internal/compose/frombus/frombus.go +++ b/internal/compose/frombus/frombus.go @@ -7,13 +7,13 @@ // through a Reader, so this package holds no bus client, no store and no timer: what it is given is all it reads. // // **What it reads, and from whom.** -// - which modules run on each node, with their pins, holdings, host ports, bindings and settings placement: the -// seat mesh-module-assigner's state `placement`, one key per node (ADR 0567 §1); +// - which modules run on each node, with their pins, holdings, host ports, bindings and settings assignment: the +// seat mesh-module-assigner's state `assignment`, one key per node (ADR 0567 §1); // - what a person decided of each node (its endpoint, site, hub, address on the private network, public domain and -// accounts): the same placement key, Placement.Decided; +// accounts): the same assignment key, Assignments.Decided; // - what each node observes of itself (its tunnel key, capabilities, keys, certificate, links and networks): the // node's own have key, `.node` (HaveNode.Record); the tunnel found at a node's adoption and the names of the hub's peers are decided, -// in its placement, and whether the hub took the tunnel over and who enrolled under a peer's key are read from both; +// in its assignment, and whether the hub took the tunnel over and who enrolled under a peer's key are read from both; // - every module's manifest: the indexer's `modules.` and its manifests by digest; for each module placed on // the composing node, its built manifest instead: the builder's done key at the trunk's FP2, or at the fingerprint // the node's want names where it runs another build (Scope.KeptAt), resolved by the controller's own take-in rule @@ -41,10 +41,10 @@ import ( "sort" "strings" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" @@ -62,8 +62,8 @@ import ( // Reader is the bus as a caller reads it with its own credential. Each method reads one part's state; an error is a // read that failed, never an absent key (an absent key is an empty answer). type Reader interface { - // Placements is every node's placement, by node (buckets.Placement). - Placements(ctx context.Context) (map[string]meshmoduleassigner.Placement, error) + // Assignments is every node's assignment, by node (buckets.Assignments). + Assignments(ctx context.Context) (map[string]meshmoduleassigner.Assignments, error) // Records is every node's record, by node: HaveNode.Record of each node's own have key, `.node` of // buckets.Have (ADR 0555 §3). Records(ctx context.Context) (map[string]nodeengine.NodeRecord, error) @@ -137,11 +137,11 @@ type Mesh struct { // controller composes from the manifest its take-in resolved: so a moved module is handed to the provisioner, a resource // naming an artifact names what was built, and a templated file has its text. A placed module the builder holds no done // build of at the trunk's FP2 composes from the indexer's manifest, as before. Every leaves the catalogue as the indexer -// read it: the check of every node judges placement, and reads no archive. +// read it: the check of every node judges assignment, and reads no archive. // -// Placements, when set, stands in for the placement of the nodes it names: a proposal's candidates +// Assignments, when set, stands in for the assignment of the nodes it names: a proposal's candidates // (meshmoduleassigner.CandidatesByProposalNodeValue), so a node composes in shadow with a proposal laid over and compares the result with -// what it runs (ADR 0567 §3); a nil value removes the node's placement. The rest is read as always. +// what it runs (ADR 0567 §3); a nil value removes the node's assignment. The rest is read as always. // // InOwn is the modules the composing node runs inside its own composition (InOwn of what it runs), composed there again // (compose.Node.InOwn) rather than cut out as modules no build is given for (issue 1126). @@ -150,12 +150,12 @@ type Mesh struct { // (compose.Node.RunsOwn), so a module folded there that names a build of the old form is left to the node, carried // as it runs (issue 1126). Nil composes every folded module from its manifest. type Scope struct { - Node string - Every bool - KeptAt map[string]string - Placements map[string]*meshmoduleassigner.Placement - InOwn []string - Running []engcomp.Composed + Node string + Every bool + KeptAt map[string]string + Assignments map[string]*meshmoduleassigner.Assignments + InOwn []string + Running []engcomp.Composed // HeldToday is, in an answer, the modules the node keeps today as they run ("node" for its own composition), each with // how ("held", "failed", "runs a build of the old form"): a candidate that cannot compose one, or holds one only // behind one of them, keeps it as today, which is said under CandidateAnswer.Held and is no refusal (issue 1126). @@ -174,7 +174,7 @@ type Scope struct { // controller's composed writer folded there because no fingerprint of the new form names them (mesh-wireguard, a module // run at a build of the old form; issue 1126). A node composing itself passes it as Scope.InOwn, so its own // composition keeps them as it runs them until a want names a build of the new form for one. -func InOwn(running []engcomp.Composed, p meshmoduleassigner.Placement) []string { +func InOwn(running []engcomp.Composed, p meshmoduleassigner.Assignments) []string { placed := map[string]bool{} for _, m := range p.Modules { placed[m.Module] = true @@ -258,10 +258,10 @@ func input(ctx context.Context, r Reader, mesh Mesh, s Scope, refuse func(module // base is the part of a node's input that the fingerprints it composes its modules at leave alone (issue 1954): read once // for its plans at the wanted builds and at the newest, whose builds alone differ (PlansHeldTwice). type base struct { - in compose.Input - placements map[string]meshmoduleassigner.Placement - records map[string]nodeengine.NodeRecord - modules map[string]meshcatalog.ModulesByModuleValue + in compose.Input + assignments map[string]meshmoduleassigner.Assignments + records map[string]nodeengine.NodeRecord + modules map[string]meshcatalog.ModulesByModuleValue // ownRefused is each module placed on the composing node whose manifest the indexer refused, with why, in the order // read: every scope holds it. ownRefused [][2]string @@ -291,29 +291,29 @@ func (b base) cloned() compose.Input { return in } -// readBase is input up to the builds: placement, records, the indexer's modules and manifests, each node's values. +// readBase is input up to the builds: assignment, records, the indexer's modules and manifests, each node's values. func readBase(ctx context.Context, r Reader, mesh Mesh, s Scope) (base, error) { var b base - placements, err := r.Placements(ctx) + assignments, err := r.Assignments(ctx) if err != nil { - return b, fmt.Errorf("frombus: placement: %w", err) + return b, fmt.Errorf("frombus: assignment: %w", err) } - if len(s.Placements) > 0 { - given := make(map[string]meshmoduleassigner.Placement, len(placements)+len(s.Placements)) - for node, p := range placements { + if len(s.Assignments) > 0 { + given := make(map[string]meshmoduleassigner.Assignments, len(assignments)+len(s.Assignments)) + for node, p := range assignments { given[node] = p } - for node, p := range s.Placements { + for node, p := range s.Assignments { if p == nil { delete(given, node) continue } if p.Node != node { - return b, fmt.Errorf("frombus: the placement given for %s names the node %q", node, p.Node) + return b, fmt.Errorf("frombus: the assignment given for %s names the node %q", node, p.Node) } given[node] = *p } - placements = given + assignments = given } records, err := r.Records(ctx) if err != nil { @@ -323,8 +323,8 @@ func readBase(ctx context.Context, r Reader, mesh Mesh, s Scope) (base, error) { if err != nil { return b, fmt.Errorf("frombus: the indexer's modules: %w", err) } - if _, ok := placements[s.Node]; !ok && !s.Every { - return b, fmt.Errorf("frombus: %s has no placement in the catalogue", s.Node) + if _, ok := assignments[s.Node]; !ok && !s.Every { + return b, fmt.Errorf("frombus: %s has no assignment in the catalogue", s.Node) } in := compose.Input{OverlayCIDR: mesh.OverlayCIDR, InternalSuffix: mesh.InternalSuffix, Authority: mesh.Authority, @@ -357,7 +357,7 @@ func readBase(ctx context.Context, r Reader, mesh Mesh, s Scope) (base, error) { (*field)[module] = why } ownPlaced := map[string]bool{} - for _, pm := range placements[s.Node].Modules { + for _, pm := range assignments[s.Node].Modules { ownPlaced[pm.Module] = true } for _, name := range sortedKeys(modules) { @@ -401,9 +401,9 @@ func readBase(ctx context.Context, r Reader, mesh Mesh, s Scope) (base, error) { } var missing []string - for _, node := range nodesOf(placements, records) { + for _, node := range nodesOf(assignments, records) { rec, hasRecord := records[node] - p := placements[node] + p := assignments[node] if !hasRecord { if len(p.Modules) > 0 { missing = append(missing, node) @@ -492,13 +492,13 @@ func readBase(ctx context.Context, r Reader, mesh Mesh, s Scope) (base, error) { } } if in.Adopted != nil && in.Adopted.Adopted { - in.CarriedPeers = carriedPeers(in.Adopted.Tunnel, placements[in.Adopted.Hub].Decided.PeerNames, records) + in.CarriedPeers = carriedPeers(in.Adopted.Tunnel, assignments[in.Adopted.Hub].Decided.PeerNames, records) } if len(missing) > 0 { return b, fmt.Errorf("frombus: modules are placed on %s, whose agent has written no record yet", strings.Join(missing, ", ")) } - b.in, b.placements, b.records, b.modules = in, placements, records, modules + b.in, b.assignments, b.records, b.modules = in, assignments, records, modules return b, nil } @@ -523,7 +523,7 @@ func (b base) finish(ctx context.Context, r Reader, build func(ctx context.Conte if _, ok := b.records[s.Node]; !ok { return compose.Input{}, fmt.Errorf("frombus: %s has written no record of itself yet", s.Node) } - if err := fromBuilds(ctx, r, build, &in, s, b.placements[s.Node], b.modules, read, parse, refuse, wanted); err != nil { + if err := fromBuilds(ctx, r, build, &in, s, b.assignments[s.Node], b.modules, read, parse, refuse, wanted); err != nil { return compose.Input{}, err } // Its own modules' secrets, sealed to its own key by the vault: a composition carries them sealed (ADR 0550 §5). @@ -799,7 +799,7 @@ func ranOf(ctx context.Context, r Reader, mesh Mesh, running []engcomp.Composed, // by the one take-in rule (takein.Manifest), a templates archive read in the artifact store as the node reaches it // (issue 1126). func fromBuilds(ctx context.Context, r Reader, build func(ctx context.Context, module, fp string) (*builds.BuilderBuild, error), - in *compose.Input, s Scope, p meshmoduleassigner.Placement, modules map[string]meshcatalog.ModulesByModuleValue, + in *compose.Input, s Scope, p meshmoduleassigner.Assignments, modules map[string]meshcatalog.ModulesByModuleValue, read func(module, digest string) (catalogue.Manifest, error), parse func([]byte) (catalogue.Manifest, error), refuse func(module string, err error), wanted map[string]string) error { // stub keeps a refused module in the composition as a stub for this node alone (compose.Stub): what it provides @@ -1004,9 +1004,9 @@ func attachedOf(in []nodeengine.AttachedNetwork) []catalogue.AttachedNetwork { return out } -// nodeOf is the composition's node from its record (what the machine observes) and its placement (what a person +// nodeOf is the composition's node from its record (what the machine observes) and its assignment (what a person // decided of it, and where its modules run). -func nodeOf(rec nodeengine.NodeRecord, p meshmoduleassigner.Placement) compose.Node { +func nodeOf(rec nodeengine.NodeRecord, p meshmoduleassigner.Assignments) compose.Node { n := compose.Node{ Name: rec.Node, Place: compose.Place{Key: rec.TunnelKey}, @@ -1024,9 +1024,9 @@ func nodeOf(rec nodeengine.NodeRecord, p meshmoduleassigner.Placement) compose.N return n } -// placeNode sets what a person decided of n and where its modules run, from its placement; what the machine observes +// placeNode sets what a person decided of n and where its modules run, from its assignment; what the machine observes // (its keys, links, networks, certificate) and its settings layers are left as they are. -func placeNode(n *compose.Node, p meshmoduleassigner.Placement) { +func placeNode(n *compose.Node, p meshmoduleassigner.Assignments) { d := p.Decided n.Place = compose.Place{Key: n.Place.Key, Endpoint: d.Endpoint, Site: d.Site, Hub: d.Hub, Address: d.Address} n.Assigned, n.PublicDomain = meshmoduleassignerclient.Assigned(p), d.PublicDomain @@ -1035,15 +1035,15 @@ func placeNode(n *compose.Node, p meshmoduleassigner.Placement) { n.Pins, n.Bound, n.Ports = meshmoduleassignerclient.ChosenPins(p), meshmoduleassignerclient.Bound(p), meshmoduleassignerclient.HostPorts(p) } -// Placed is in with the placements given standing in for those of the nodes they name (issue 1126): each such node's -// decided parts, modules, pins, bindings, host ports, settings placement and holdings are the given placement's, its +// Placed is in with the assignments given standing in for those of the nodes they name (issue 1126): each such node's +// decided parts, modules, pins, bindings, host ports, settings assignment and holdings are the given assignment's, its // config values those in already carries for it (the layer of the node's own name), and what the machine observes is -// kept. A nil placement leaves the node in the mesh with nothing placed on it. A node in does not hold is refused: it has +// kept. A nil assignment leaves the node in the mesh with nothing placed on it. A node in does not hold is refused: it has // no record to compose it from. The found tunnel and the hub's peers are read from in as they are, not from the given -// placement. It is how a check composes a change's own placement over an input already filled (composer.Case's -// Placements). -func Placed(in compose.Input, placements map[string]*meshmoduleassigner.Placement) (compose.Input, error) { - if len(placements) == 0 { +// assignment. It is how a check composes a change's own assignment over an input already filled (composer.Case's +// Assignments). +func Placed(in compose.Input, assignments map[string]*meshmoduleassigner.Assignments) (compose.Input, error) { + if len(assignments) == 0 { return in, nil } out := in @@ -1052,17 +1052,17 @@ func Placed(in compose.Input, placements map[string]*meshmoduleassigner.Placemen for i, n := range out.Nodes { index[n.Name] = i } - for _, node := range sortedKeys(placements) { - p := placements[node] + for _, node := range sortedKeys(assignments) { + p := assignments[node] i, ok := index[node] if !ok { - return compose.Input{}, fmt.Errorf("frombus: a placement is given for %s, which the input holds no node for", node) + return compose.Input{}, fmt.Errorf("frombus: a assignment is given for %s, which the input holds no node for", node) } if p == nil { - p = &meshmoduleassigner.Placement{Node: node} + p = &meshmoduleassigner.Assignments{Node: node} } if p.Node != node { - return compose.Input{}, fmt.Errorf("frombus: the placement given for %s names the node %q", node, p.Node) + return compose.Input{}, fmt.Errorf("frombus: the assignment given for %s names the node %q", node, p.Node) } n := out.Nodes[i] placeNode(&n, *p) @@ -1086,12 +1086,12 @@ func Placed(in compose.Input, placements map[string]*meshmoduleassigner.Placemen } out.Holdings = nil for _, h := range in.Holdings { - if _, given := placements[h.Node]; !given { + if _, given := assignments[h.Node]; !given { out.Holdings = append(out.Holdings, h) } } - for _, node := range sortedKeys(placements) { - if p := placements[node]; p != nil { + for _, node := range sortedKeys(assignments) { + if p := assignments[node]; p != nil { out.Holdings = append(out.Holdings, meshmoduleassignerclient.Held(*p)...) } } @@ -1132,10 +1132,10 @@ func carriedPeers(t compose.Tunnel, names map[string]string, records map[string] return out } -// nodesOf is every node named by a placement or a record, by name: the order the controller reads them in. -func nodesOf(placements map[string]meshmoduleassigner.Placement, records map[string]nodeengine.NodeRecord) []string { +// nodesOf is every node named by a assignment or a record, by name: the order the controller reads them in. +func nodesOf(assignments map[string]meshmoduleassigner.Assignments, records map[string]nodeengine.NodeRecord) []string { seen := map[string]bool{} - for n := range placements { + for n := range assignments { seen[n] = true } for n := range records { diff --git a/internal/compose/frombus/frombus_test.go b/internal/compose/frombus/frombus_test.go index c0a4cae..6ae853e 100644 --- a/internal/compose/frombus/frombus_test.go +++ b/internal/compose/frombus/frombus_test.go @@ -11,10 +11,10 @@ import ( "encoding/json" "errors" "fmt" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" @@ -35,7 +35,7 @@ import ( type bus struct { vaultAuthority string handed map[string]bool - placements map[string]meshmoduleassigner.Placement + assignments map[string]meshmoduleassigner.Assignments records map[string]nodeengine.NodeRecord modules map[string]meshcatalog.ModulesByModuleValue manifests map[string][]byte @@ -44,7 +44,7 @@ type bus struct { own []compose.OwnSecretRow ownErr error // asked is every consumer credential asked; the fake refuses the whole ask, as the vault's own does, for a consuming - // module its placement does not put on the node. + // module its assignment does not put on the node. asked []compose.CredentialKey // builds are the builder's keys, by " "; archives the artifact store's blobs, by digest, and fetched // every URL asked; served another node's values by ".", and servedRead every ask with its keys. @@ -83,8 +83,8 @@ func (b *bus) Served(_ context.Context, node, module string, keys []string) (map return out, nil } -func (b *bus) Placements(context.Context) (map[string]meshmoduleassigner.Placement, error) { - return b.placements, nil +func (b *bus) Assignments(context.Context) (map[string]meshmoduleassigner.Assignments, error) { + return b.assignments, nil } func (b *bus) VaultAuthority(context.Context) string { return b.vaultAuthority } func (b *bus) Handed(context.Context, string) map[string]bool { return b.handed } @@ -109,7 +109,7 @@ func (b *bus) Credentials(_ context.Context, node string, asConsumer, asProvider b.asked = append(b.asked, asConsumer...) for _, k := range asConsumer { placed := false - for _, m := range b.placements[node].Modules { + for _, m := range b.assignments[node].Modules { placed = placed || m.Module == k.ConsumerModule } if !placed { @@ -143,7 +143,7 @@ func publicKey(t *testing.T) string { return base64.StdEncoding.EncodeToString(k.PublicKey().Bytes()) } -// Issue 1126 (S3, S4, S5): the bus's placement, node records, manifests and config values fill the same compose.Input the +// Issue 1126 (S3, S4, S5): the bus's assignment, node records, manifests and config values fill the same compose.Input the // controller's store fills for compose's own two-machine mesh (compose's aMesh: anchor the hub, laptop), and that Input // composes; a node reads only its own config values; what cannot be composed is refused, naming it. func TestInput(t *testing.T) { @@ -167,7 +167,7 @@ func TestInput(t *testing.T) { } digest := meshcatalogclient.ManifestDigest(xterm) // A module in the settings form (plex and the media modules on one node, 2026-10-26): its config values are in its - // layers, not mesh-config, so the placement carries them (MeshValues, Values) and mesh-config's are not read for it. + // layers, not mesh-config, so the assignment carries them (MeshValues, Values) and mesh-config's are not read for it. media := []byte(`{"module":"media","version":"1","settings":{"lines":{"kind":"preference","default":0,"why":"kept"},` + `"name":{"kind":"preference","default":"x","why":"shown"}},"own-secrets":{"token":"/srv/media/token"},"resources":[{"id":"conf","type":"file",` + `"path":"/srv/media/conf","mode":"0644","content":"uid=${setting:lines} name=${setting:name}\n"}]}`) @@ -178,7 +178,7 @@ func TestInput(t *testing.T) { Peers: []meshmoduleassigner.FoundTunnelPeer{{PublicKey: "peer", Address: "10.77.0.9/32"}, {PublicKey: keys["laptop"], Address: "10.77.0.2/32"}}} mk := func() *bus { return &bus{ - placements: map[string]meshmoduleassigner.Placement{ + assignments: map[string]meshmoduleassigner.Assignments{ "anchor": {Node: "anchor", Decided: meshmoduleassigner.NodeDecided{Endpoint: "anchor.example:51820", Site: "here", Hub: true, Address: "10.77.0.1", Tunnel: tunnel, PeerNames: map[string]string{"peer": "phone"}}, Modules: []meshmoduleassigner.PlacedModule{{Module: overlay.Name}}, @@ -289,15 +289,15 @@ func TestInput(t *testing.T) { t.Fatalf("compositions differ or are empty: %d from the bus, %d from the store", len(fromBus), len(fromStore)) } - // Issue 1126: a candidate placement equal to the node's composes what it runs, and CompareComposed says so; one + // Issue 1126: a candidate assignment equal to the node's composes what it runs, and CompareComposed says so; one // thing changed in the group's candidate of another node (the hub's endpoint) names exactly the module it moves - // here. Placed puts the same placement over the store's input unchanged. + // here. Placed puts the same assignment over the store's input unchanged. running := make([]compositions.Composed, 0, len(fromBus)) for _, c := range fromBus { running = append(running, c) } - laptop := mk().placements["laptop"] - asIs, _, held, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Placements: map[string]*meshmoduleassigner.Placement{"laptop": &laptop}}, fps) + laptop := mk().assignments["laptop"] + asIs, _, held, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Assignments: map[string]*meshmoduleassigner.Assignments{"laptop": &laptop}}, fps) if err == nil && len(held) > 0 { err = fmt.Errorf("modules held: %v", held) } @@ -305,11 +305,11 @@ func TestInput(t *testing.T) { t.Fatal(err) } if c := compositions.CompareComposed(asIs, running); len(c.Differs)+len(c.Added)+len(c.Removed) != 0 || len(c.Same) != len(running) { - t.Errorf("the candidate equal to the placement: %+v", c) + t.Errorf("the candidate equal to the assignment: %+v", c) } - moved := mk().placements["anchor"] + moved := mk().assignments["anchor"] moved.Decided.Endpoint = "anchor.example:51821" - changed, _, held, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Placements: map[string]*meshmoduleassigner.Placement{"laptop": &laptop, "anchor": &moved}}, fps) + changed, _, held, err := Compose(ctx, mk(), Mesh{}, Scope{Node: "laptop", Assignments: map[string]*meshmoduleassigner.Assignments{"laptop": &laptop, "anchor": &moved}}, fps) if err == nil && len(held) > 0 { err = fmt.Errorf("modules held: %v", held) } @@ -335,11 +335,11 @@ func TestInput(t *testing.T) { files, _ := json.Marshal(catalogue.Manifest{Module: "files", Version: "1", Requires: []string{"s3-bucket"}, Resources: []map[string]any{{"id": "config", "type": "file", "path": "/etc/files/config.json", "mode": "0600", "content": "{}", "merge": "json"}}}) one := func(placed ...string) *bus { - p := meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}} + p := meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}} for _, m := range placed { p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m}) } - return &bus{placements: map[string]meshmoduleassigner.Placement{"n1": p}, + return &bus{assignments: map[string]meshmoduleassigner.Assignments{"n1": p}, records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: keys["anchor"], SealingKey: "sk", Capabilities: []string{"systemd"}}}, modules: map[string]meshcatalog.ModulesByModuleValue{"keeper": {Manifest: meshcatalogclient.ManifestDigest(keeper)}, "lemurs": {Manifest: meshcatalogclient.ManifestDigest(lemurs)}, "objects": {Manifest: meshcatalogclient.ManifestDigest(objects)}, "files": {Manifest: meshcatalogclient.ManifestDigest(files)}}, @@ -385,14 +385,14 @@ func TestInput(t *testing.T) { // node's own, each in its place, its module taken off; the rest split by module. tunnelled := func() *bus { b := one("keeper", "lemurs", overlay.Name) - p := b.placements["n1"] + p := b.assignments["n1"] p.Decided.Endpoint = "n1.example:51820" - b.placements["n1"] = p + b.assignments["n1"] = p return b } - tunnelledAs := func() map[string]*meshmoduleassigner.Placement { - p := tunnelled().placements["n1"] - return map[string]*meshmoduleassigner.Placement{"n1": &p} + tunnelledAs := func() map[string]*meshmoduleassigner.Assignments { + p := tunnelled().assignments["n1"] + return map[string]*meshmoduleassigner.Assignments{"n1": &p} } folded := func() []compositions.Composed { b := tunnelled() @@ -437,16 +437,16 @@ func TestInput(t *testing.T) { } return out }() - if got := InOwn(folded, tunnelled().placements["n1"]); !reflect.DeepEqual(got, []string{"lemurs", overlay.Name}) { + if got := InOwn(folded, tunnelled().assignments["n1"]); !reflect.DeepEqual(got, []string{"lemurs", overlay.Name}) { t.Errorf("InOwn of the controller's composition = %v, want lemurs and %s", got, overlay.Name) } - placedAs := func(modules ...string) map[string]*meshmoduleassigner.Placement { - p := one(modules...).placements["n1"] - return map[string]*meshmoduleassigner.Placement{"n1": &p} + placedAs := func(modules ...string) map[string]*meshmoduleassigner.Assignments { + p := one(modules...).assignments["n1"] + return map[string]*meshmoduleassigner.Assignments{"n1": &p} } for name, c := range map[string]struct { b *bus - given map[string]*meshmoduleassigner.Placement + given map[string]*meshmoduleassigner.Assignments running []compositions.Composed verdict string check func(nodeengine.CandidateAnswer) bool @@ -483,7 +483,7 @@ func TestInput(t *testing.T) { runsNow, nodeengine.CandidateAnswerVerdictRefused, func(a nodeengine.CandidateAnswer) bool { return len(a.Refusals) == 1 && a.Refusals[0].Module == "keeper" && len(a.Added) == 0 }}, - "cannot compose": {one("keeper"), map[string]*meshmoduleassigner.Placement{"n1": {Node: "n2"}}, runsNow, nodeengine.CandidateAnswerVerdictRefused, + "cannot compose": {one("keeper"), map[string]*meshmoduleassigner.Assignments{"n1": {Node: "n2"}}, runsNow, nodeengine.CandidateAnswerVerdictRefused, func(a nodeengine.CandidateAnswer) bool { return a.Refused != "" }}, // Issue 1126 (the seed's review): the private network's module and one at a build of the old form, which no want // names and the controller folds into the node's own, are folded there again: the node's own is the same, and @@ -495,7 +495,7 @@ func TestInput(t *testing.T) { if name == "mesh-wireguard and an old-form module, folded" { fps = map[string]string{"keeper": oneFps["keeper"]} // the node's wants: no build of lemurs or the private network } - a := Answer(ctx, c.b, Mesh{}, Scope{Node: "n1", Placements: c.given, InOwn: InOwn(c.running, *c.given["n1"])}, fps, c.running, "p-h1", 7) + a := Answer(ctx, c.b, Mesh{}, Scope{Node: "n1", Assignments: c.given, InOwn: InOwn(c.running, *c.given["n1"])}, fps, c.running, "p-h1", 7) if a.Verdict != c.verdict || !c.check(a) { t.Errorf("%s: %+v", name, a) } @@ -503,28 +503,28 @@ func TestInput(t *testing.T) { t.Errorf("%s: the answer is not one a have holds: %v (%s)", name, err, raw) } } - if got, err := Placed(want, map[string]*meshmoduleassigner.Placement{"laptop": &laptop}); err != nil || !reflect.DeepEqual(got, want) { - t.Errorf("Placed with the node's own placement changes the input: %v", err) + if got, err := Placed(want, map[string]*meshmoduleassigner.Assignments{"laptop": &laptop}); err != nil || !reflect.DeepEqual(got, want) { + t.Errorf("Placed with the node's own assignment changes the input: %v", err) } - if got, err := Placed(want, map[string]*meshmoduleassigner.Placement{"laptop": nil}); err != nil || len(got.Nodes[1].Assigned) != 0 || got.Nodes[1].Place.Key != keys["laptop"] { - t.Errorf("Placed with no placement: %+v, %v", got.Nodes[1], err) + if got, err := Placed(want, map[string]*meshmoduleassigner.Assignments{"laptop": nil}); err != nil || len(got.Nodes[1].Assigned) != 0 || got.Nodes[1].Place.Key != keys["laptop"] { + t.Errorf("Placed with no assignment: %+v, %v", got.Nodes[1], err) } - if _, err := Placed(want, map[string]*meshmoduleassigner.Placement{"stranger": {Node: "stranger"}}); err == nil { + if _, err := Placed(want, map[string]*meshmoduleassigner.Assignments{"stranger": {Node: "stranger"}}); err == nil { t.Error("Placed a node the input holds no node for") } - if _, err := Input(ctx, mk(), Mesh{}, Scope{Node: "laptop", Placements: map[string]*meshmoduleassigner.Placement{"laptop": {Node: "anchor"}}}); err == nil { - t.Error("a given placement naming another node was taken") + if _, err := Input(ctx, mk(), Mesh{}, Scope{Node: "laptop", Assignments: map[string]*meshmoduleassigner.Assignments{"laptop": {Node: "anchor"}}}); err == nil { + t.Error("a given assignment naming another node was taken") } // ADR 0555 §6: Export is Input's inverse for what a person decides: the store's input exported, node by node, is the - // placement the bus holds, and read back it is the store's input again. - for node, placed := range mk().placements { + // assignment the bus holds, and read back it is the store's input again. + for node, placed := range mk().assignments { got, err := Export(want, node) if err != nil { t.Fatal(err) } - if gj, _ := meshmoduleassignerclient.EncodePlacement(got); !meshmoduleassignerclient.SamePlacement(got, placed) { - pj, _ := meshmoduleassignerclient.EncodePlacement(placed) + if gj, _ := meshmoduleassignerclient.EncodeAssignments(got); !meshmoduleassignerclient.SameAssignments(got, placed) { + pj, _ := meshmoduleassignerclient.EncodeAssignments(placed) t.Errorf("%s exported as %s, want %s", node, gj, pj) } } @@ -622,10 +622,10 @@ func TestInput(t *testing.T) { clientArtifacts := []builds.BuiltArtifact{{Name: "client", Kind: "bundle", Reference: "artifact-store://mesh-provisioner-client/client/blobs/" + digestOf("7"), Digest: digestOf("7")}} movedMesh := func(built bool) *bus { b := one() - p := b.placements["n1"] + p := b.assignments["n1"] p.Modules = []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "mesh-provisioner-client"}, {Module: "restic"}} p.Decided.Account, p.Decided.AccountHome = "operator", "/home/operator" - b.placements["n1"] = p + b.assignments["n1"] = p rec := b.records["n1"] rec.Contract, rec.Capabilities = compose.HandedContract, []string{"systemd", "package-manager"} b.records["n1"] = rec @@ -771,9 +771,9 @@ func TestInput(t *testing.T) { `"provides":[{"name":"smtp","scope":"mesh"}],"serves":{"smtp":{"port":587,"domain":"${config:domain}"}}}`) withMailer := func(set bool) *bus { b := mk() - p := b.placements["anchor"] + p := b.assignments["anchor"] p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: "mailer"}) - b.placements["anchor"] = p + b.assignments["anchor"] = p b.modules["mailer"] = meshcatalog.ModulesByModuleValue{Manifest: meshcatalogclient.ManifestDigest(mailer)} b.manifests[meshcatalogclient.ManifestDigest(mailer)] = mailer if set { @@ -798,7 +798,7 @@ func TestInput(t *testing.T) { says string }{ "no record": {func(b *bus) { delete(b.records, "laptop") }, Scope{Node: "anchor"}, "laptop, whose agent has written no record"}, - "no placement": {func(b *bus) {}, Scope{Node: "stranger"}, "stranger has no placement"}, + "no assignment": {func(b *bus) {}, Scope{Node: "stranger"}, "stranger has no assignment"}, "a manifest unread": {func(b *bus) { delete(b.manifests, digest) }, Scope{Node: "laptop"}, "the manifest of xterm"}, "a kept one unbuilt": {func(b *bus) {}, Scope{Node: "laptop", KeptAt: map[string]string{"xterm": "src2:gone"}}, "holds no done build"}, } { @@ -840,11 +840,11 @@ func statesOf(t *testing.T, ctx context.Context, b *bus) mapStates { } out[state][key] = raw } - placements, _ := b.Placements(ctx) - for _, p := range placements { - values, err := meshmoduleassignerclient.PlacementValues(p) + assignments, _ := b.Assignments(ctx) + for _, p := range assignments { + values, err := meshmoduleassignerclient.AssignmentsValues(p) for k, raw := range values { - put(PlacementRead, k, raw, err) + put(AssignmentsRead, k, raw, err) } } records, _ := b.Records(ctx) diff --git a/internal/compose/frombus/frombustest/frombustest.go b/internal/compose/frombus/frombustest/frombustest.go index 70a7a56..b052bad 100644 --- a/internal/compose/frombus/frombustest/frombustest.go +++ b/internal/compose/frombus/frombustest/frombustest.go @@ -12,8 +12,8 @@ import ( "embed" "encoding/json" "errors" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" @@ -27,7 +27,7 @@ var testdata embed.FS // Bus is a frombus.Reader over values held in memory. A credential asked is answered held when Held is set. type Bus struct { - Placement map[string]meshmoduleassigner.Placement + ByNode map[string]meshmoduleassigner.Assignments Record map[string]nodeengine.NodeRecord Module map[string]meshcatalog.ModulesByModuleValue Manifests map[string][]byte @@ -41,8 +41,8 @@ type Bus struct { var _ frombus.Reader = (*Bus)(nil) -func (b *Bus) Placements(context.Context) (map[string]meshmoduleassigner.Placement, error) { - return b.Placement, nil +func (b *Bus) Assignments(context.Context) (map[string]meshmoduleassigner.Assignments, error) { + return b.ByNode, nil } func (b *Bus) Records(context.Context) (map[string]nodeengine.NodeRecord, error) { return b.Record, nil @@ -156,7 +156,7 @@ func Provisioned() (*Bus, error) { digestOf := func(c string) string { return "sha256:" + strings.Repeat(c, 64) } fps := FPs() b := &Bus{ - Placement: map[string]meshmoduleassigner.Placement{Node: {Node: Node, + ByNode: map[string]meshmoduleassigner.Assignments{Node: {Node: Node, Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1", Account: "operator", AccountHome: "/home/operator"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "postgres"}, {Module: "mesh-provisioner-client"}, {Module: "restic"}}}}, Record: map[string]nodeengine.NodeRecord{Node: {Node: Node, TunnelKey: "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=", diff --git a/internal/compose/frombus/frombustest/frombustest_test.go b/internal/compose/frombus/frombustest/frombustest_test.go index 3593057..3c2b190 100644 --- a/internal/compose/frombus/frombustest/frombustest_test.go +++ b/internal/compose/frombus/frombustest/frombustest_test.go @@ -3,10 +3,10 @@ package frombustest import ( "context" "encoding/json" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" @@ -70,9 +70,9 @@ func TestTheProvisionedMeshHandsAPlanWithItsProvision(t *testing.T) { } if row.bad != "" { if !row.elsewhere { - p := b.Placement[Node] + p := b.ByNode[Node] p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: "backup"}) - b.Placement[Node] = p + b.ByNode[Node] = p } digest := meshcatalogclient.ManifestDigest([]byte(row.bad)) b.Manifests[digest] = []byte(row.bad) @@ -166,19 +166,19 @@ func TestTheProvisionedMeshHandsAPlanWithItsProvision(t *testing.T) { byKey := map[string]int{} ws := stdio.WatchedStatesOver(func(state string, handle func(stdio.StateChange) error) error { byKey[state+" watched"]++ - return handle(stdio.StateChange{Key: "placement." + Node, Op: stdio.OpPut, Value: []byte(`{}`), Current: true}) + return handle(stdio.StateChange{Key: Node + ".node", Op: stdio.OpPut, Value: []byte(`{}`), Current: true}) }, func(state, key string) ([]byte, bool, error) { byKey[state]++; return []byte(key), true, nil }) w := fromruntime.Watched{WatchedStates: ws} for range 2 { _, _, _ = w.Get(frombus.BuildsRead, "restic."+fp) _, _, _ = w.Get(frombus.ManifestsRead, "sha256-d") - if _, found, _ := w.Get(frombus.PlacementRead, "placement."+Node); !found { - t.Error("the watched reader does not answer the placement from its copy") + if _, found, _ := w.Get(frombus.AssignmentsRead, Node+".node"); !found { + t.Error("the watched reader does not answer the assignment from its copy") } } - if byKey[frombus.BuildsRead] != 2 || byKey[frombus.ManifestsRead] != 1 || byKey[frombus.PlacementRead] != 0 || - byKey[frombus.PlacementRead+" watched"] != 1 { - t.Errorf("the watched reader read %v, want a build by key each time, a manifest once, the placement watched once", byKey) + if byKey[frombus.BuildsRead] != 2 || byKey[frombus.ManifestsRead] != 1 || byKey[frombus.AssignmentsRead] != 0 || + byKey[frombus.AssignmentsRead+" watched"] != 1 { + t.Errorf("the watched reader read %v, want a build by key each time, a manifest once, the assignment watched once", byKey) } fetched := 0 keep := fromruntime.KeptFetch(func(context.Context, string) ([]byte, error) { fetched++; return []byte("tar"), nil }) @@ -279,10 +279,10 @@ func TestTheProvisionedMeshHandsAPlanWithItsProvision(t *testing.T) { // held: its build not done while it runs bb.Builds["mesh-provisioner-client "+fps["mesh-provisioner-client"]] = &builds.BuilderBuild{State: builds.BuildBuilding} } else { - // taken out of the node's placement while what runs still names it: the account goes with it, never kept stale - p := bb.Placement[Node] + // taken out of the node's assignment while what runs still names it: the account goes with it, never kept stale + p := bb.ByNode[Node] p.Modules = slices.DeleteFunc(slices.Clone(p.Modules), func(m meshmoduleassigner.PlacedModule) bool { return m.Module == "mesh-provisioner-client" }) - bb.Placement[Node] = p + bb.ByNode[Node] = p want = "" } after, _, held, err := frombus.Compose(context.Background(), bb, frombus.Mesh{}, frombus.Scope{Node: Node, Running: runs}, fps) @@ -314,9 +314,14 @@ func statesOf(t *testing.T, b *Bus) frombus.States { } s[state][key] = raw } - for node, p := range b.Placement { - raw, err := meshmoduleassignerclient.EncodePlacement(p) - put(frombus.PlacementRead, "placement."+node, raw, err) + for _, p := range b.ByNode { + vals, err := meshmoduleassignerclient.AssignmentsValues(p) + if err != nil { + put(frombus.AssignmentsRead, p.Node+".node", nil, err) + } + for key, raw := range vals { + put(frombus.AssignmentsRead, key, raw, nil) + } } for node, rec := range b.Record { raw, err := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &rec}) diff --git a/internal/compose/frombus/held_rows_test.go b/internal/compose/frombus/held_rows_test.go index 4936bb1..e4d5673 100644 --- a/internal/compose/frombus/held_rows_test.go +++ b/internal/compose/frombus/held_rows_test.go @@ -5,8 +5,8 @@ import ( "encoding/json" "errors" "fmt" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" @@ -20,7 +20,7 @@ import ( // aNode is a bus with n1 the hub, each of manifests placed on it and held at the indexer, in the order given. func aNode(t *testing.T, manifests ...string) (*bus, map[string]string) { t.Helper() - p := meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}} + p := meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}} b := &bus{records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: publicKey(t), SealingKey: "sk", Capabilities: []string{"systemd"}}}, modules: map[string]meshcatalog.ModulesByModuleValue{}, manifests: map[string][]byte{}, builds: map[string]*builds.BuilderBuild{}} fps := map[string]string{} @@ -31,7 +31,7 @@ func aNode(t *testing.T, manifests ...string) (*bus, map[string]string) { b.manifests[meshcatalogclient.ManifestDigest([]byte(raw))] = []byte(raw) fps[module] = "src2:" + strings.Repeat(string(rune('1'+i)), 64) } - b.placements = map[string]meshmoduleassigner.Placement{"n1": p} + b.assignments = map[string]meshmoduleassigner.Assignments{"n1": p} return b, fps } diff --git a/internal/compose/frombus/states.go b/internal/compose/frombus/states.go index 0eb785d..7e9fbd6 100644 --- a/internal/compose/frombus/states.go +++ b/internal/compose/frombus/states.go @@ -2,7 +2,7 @@ package frombus // **The bus read through a module's declared `reads`** (the lead, 2026-10-29: one reader in place of the copies in // mesh-provisioner and disk-load): a Reader over the states a module reads, by a runtime's Get and Keys (a bundle's -// stdio.State, a process's own), for a holder that composes plans or data items and reads no value: placement, each +// stdio.State, a process's own), for a holder that composes plans or data items and reads no value: assignment, each // node's record in its own have key, the indexer's modules and manifests, the builder's builds and mesh-config's resolved // values. No own secret and no credential is read (compose.Planning). Pure as the rest of this package: a bundle's // runtime states and an HTTP fetch are compose/fromruntime's. @@ -15,10 +15,10 @@ import ( "strings" "time" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" @@ -31,16 +31,16 @@ import ( const ( // The holders' names are kept here, in the engine's own code, as the names of the states a manifest's reads use // (ADR 0634 §1, finding F4); a seat's contract names a state locally, not the module that holds it. - PlacementRead = buckets.AssignerHolder + ".placement" - ModulesRead = buckets.CatalogHolder + ".modules" - ManifestsRead = buckets.CatalogHolder + ".manifests" - BuildsRead = builds.BuilderModule + ".builds" - HaveRead = "node-engine.have" - ResolvedRead = "mesh-config.resolved" + AssignmentsRead = buckets.AssignerHolder + ".assignment" + ModulesRead = buckets.CatalogHolder + ".modules" + ManifestsRead = buckets.CatalogHolder + ".manifests" + BuildsRead = builds.BuilderModule + ".builds" + HaveRead = "node-engine.have" + ResolvedRead = "mesh-config.resolved" ) // StatesReads are those reads, in a manifest's order. -var StatesReads = []string{PlacementRead, ModulesRead, ManifestsRead, BuildsRead, HaveRead, ResolvedRead} +var StatesReads = []string{AssignmentsRead, ModulesRead, ManifestsRead, BuildsRead, HaveRead, ResolvedRead} // States is a module's read states as its runtime gives them: a key's value, false when it has none. type States interface { @@ -85,9 +85,9 @@ func (r statesReader) each(read string, visit func(key string, value []byte)) er return nil } -func (r statesReader) Placements(context.Context) (map[string]meshmoduleassigner.Placement, error) { +func (r statesReader) Assignments(context.Context) (map[string]meshmoduleassigner.Assignments, error) { keys := meshmoduleassignerclient.NewKeys() - err := r.each(PlacementRead, func(k string, v []byte) { + err := r.each(AssignmentsRead, func(k string, v []byte) { _, _ = keys.Put(k, v, 0, false) // a key that does not read places nothing, as before }) return keys.All(), err diff --git a/internal/compose/frombus/twice.go b/internal/compose/frombus/twice.go index 4411c51..8b0ebf2 100644 --- a/internal/compose/frombus/twice.go +++ b/internal/compose/frombus/twice.go @@ -2,7 +2,7 @@ package frombus // **A node's plans at two sets of builds, over one read** (issue 1954; novox/hq ADR 0570 clause 2, ADR 0580 clause 2): // the seat mesh-provisioner's holder composes each node at the builds its wants name and at the newest build of each -// placed module. Both are composed from one read of the bus (placement, records, the indexer's modules and manifests, +// placed module. Both are composed from one read of the bus (assignment, records, the indexer's modules and manifests, // values: base); only the builds the node's modules are composed from differ, each build read once for both, and a // build taken in once per content by a reader that keeps it (StatesReader). Where the time went is said by phase. diff --git a/internal/compose/fromruntime/fromruntime.go b/internal/compose/fromruntime/fromruntime.go index b9fab99..f6e72ad 100644 --- a/internal/compose/fromruntime/fromruntime.go +++ b/internal/compose/fromruntime/fromruntime.go @@ -34,7 +34,7 @@ func (States) Keys(state string) ([]string, error) { return stdio.State(state).K func Reader() frombus.Reader { return frombus.StatesReader(States{}, HTTPFetch) } // Watched is frombus's States over a watched copy (novox/hq ADR 0580 clause 1): the states a composition reads whole -// (placement, haves, modules, resolved values) from the copy, a build by key when asked, a manifest once per digest. +// (assignment, haves, modules, resolved values) from the copy, a build by key when asked, a manifest once per digest. type Watched struct{ *stdio.WatchedStates } // Get is a key's value: a build read now, a manifest kept once read, any other state's from the copy. diff --git a/internal/compose/input.go b/internal/compose/input.go index c33657e..e1eaa9b 100644 --- a/internal/compose/input.go +++ b/internal/compose/input.go @@ -260,7 +260,7 @@ type ComposedHow struct { HeldBefore map[string]string } -// Place is where a node is, as its placement says (inv.Overlay). +// Place is where a node is, as its assignment says (inv.Overlay). type Place struct { Key string Endpoint string diff --git a/internal/compose/network.go b/internal/compose/network.go index 1ba2dfd..09ab10d 100644 --- a/internal/compose/network.go +++ b/internal/compose/network.go @@ -123,10 +123,10 @@ func (c *composer) network(on map[string]bool, refused map[string]string) (*over Site: p.Site, Hub: p.Hub, Address: p.Address, } if t, takes := tunnels[node.Name]; takes && t.NodeAdopted { - // Only an adopted node is told to take the found unit over. A hub whose placement disagrees with the tunnel is + // Only an adopted node is told to take the found unit over. A hub whose assignment disagrees with the tunnel is // refused, not composed. if wrong := disagrees(p, t.Tunnel); wrong != "" { - return nil, fmt.Errorf("%s takes over the tunnel on %s and its placement disagrees with it: %s. "+ + return nil, fmt.Errorf("%s takes over the tunnel on %s and its assignment disagrees with it: %s. "+ "Re-place it — `overlay place %s --hub --endpoint :%d …` — and push again; "+ "nothing was composed", node.Name, t.Interface, wrong, node.Name, t.Port) } @@ -217,7 +217,7 @@ func (c *composer) generators() (map[string]catalogue.Generator, error) { return map[string]catalogue.Generator{overlay.Name: net}, nil } -// disagrees says how a node's placement differs from the tunnel it takes over — its address not the tunnel's, its +// disagrees says how a node's assignment differs from the tunnel it takes over — its address not the tunnel's, its // endpoint not on the tunnel's port — or nothing when both agree. func disagrees(p Place, t Tunnel) string { var wrong []string diff --git a/internal/compose/network_test.go b/internal/compose/network_test.go index 3b1ca59..2cf4657 100644 --- a/internal/compose/network_test.go +++ b/internal/compose/network_test.go @@ -80,7 +80,7 @@ func TestTheOverlaysRangeIsTheAdoptedTunnels(t *testing.T) { } } -// A takeover is composed only for a hub whose placement agrees with the tunnel (mesh-controller network_test.go +// A takeover is composed only for a hub whose assignment agrees with the tunnel (mesh-controller network_test.go // TestATakeoverIsNotComposedForAHubPlacedOffItsTunnel): an address or an endpoint port that differs would have the host stop // the found interface and raise the mesh's where no peer is listening. func TestATakeoverIsNotComposedForAHubPlacedOffItsTunnel(t *testing.T) { diff --git a/internal/compose/render.go b/internal/compose/render.go index 3354fad..125569d 100644 --- a/internal/compose/render.go +++ b/internal/compose/render.go @@ -78,7 +78,7 @@ func (c *composer) render(nodeName string, mode Mode) (Rendered, error) { } r := placed.Rendering out.ForeseenPorts = placed.ForeseenPorts - // A plan carries its contributions' machine ports, refused where the placement assigned none (issue 774). + // A plan carries its contributions' machine ports, refused where the assignment assigned none (issue 774). r.Planned = mode == Planning r.Grants, r.Withheld, r.Unbound = grants, withheld, unbound out.Withheld, out.Unbound = withheld, unbound diff --git a/internal/compose/rendering.go b/internal/compose/rendering.go index d0b2afe..4bdd41c 100644 --- a/internal/compose/rendering.go +++ b/internal/compose/rendering.go @@ -328,7 +328,7 @@ func (c *composer) namesInTheMesh() map[string]string { } // StoreSeat is the seat whose holder is the artifact store (novox/hq ADR 0632 §1): the store's address is that holder's -// listen open to the mesh, on the machine placement puts it on. The provision artifact-store it was found by before is +// listen open to the mesh, on the machine assignment puts it on. The provision artifact-store it was found by before is // retired; no module serves it. const StoreSeat = "container-image-registry" diff --git a/internal/compose/resolution/declaration.go b/internal/compose/resolution/declaration.go index 2eaae4c..4f4e73a 100644 --- a/internal/compose/resolution/declaration.go +++ b/internal/compose/resolution/declaration.go @@ -1520,7 +1520,7 @@ func resolutionContributedOf(r mcat.Resolution, m mcat.Manifest, to string, with } // atAssignedPort is a contribution of m with its port where this machine publishes it for m: the number -// `${port:}` gives m itself and its declaration publishes (machinePort, port_into.go), the placement's +// `${port:}` gives m itself and its declaration publishes (machinePort, port_into.go), the assigner's // assignment where it holds one and the software's own number where it holds none (a bare port not yet moved, a fixed // mapping, the host's network). What its provider dials, on this machine over loopback and from another at m's address. // diff --git a/internal/compose/resolution/enrolment_seat.go b/internal/compose/resolution/enrolment_seat.go index 1d30161..68ffb9f 100644 --- a/internal/compose/resolution/enrolment_seat.go +++ b/internal/compose/resolution/enrolment_seat.go @@ -15,22 +15,22 @@ func enrolmentVerbs() []mcat.Verb { } return []mcat.Verb{ {Name: mcat.EnrolmentVerbIssue, Description: "Issue a one-time token for a new machine, on the operator's Approve: " + - "refused at once, naming every finding, for a name that is not a node name or that a node record, a placement " + + "refused at once, naming every finding, for a name that is not a node name or that a node record, an assignments " + "key or an open enrolment holds, and for every call before the controller's lease is handed away. Otherwise " + "reserves the lowest free private-network address, asks the operator under the seat's own name and, on the " + - "warrant, adds the node to placement with its tunnel key, asks the vault for the token and says enrolment-issued. " + + "warrant, adds the node to the assignments with its tunnel key, asks the vault for the token and says enrolment-issued. " + "The token itself is read only through tokens.", Input: schema(map[string]string{ "node": "the new machine's name in the mesh", "why": "why it joins, shown to the operator with the ask", "expires": "how long the token lasts, a duration such as 1h or 24h: one hour when not given, at most 24 hours", // The operator's decision of 2026-11-02 01:05: the tunnel key is given at issue, so on the Approve the node - // enters placement with it and the hub peers it before redeem. + // enters the assignments with it and the hub peers it before redeem. "overlay_key": "the new machine's private network (tunnel) public key, as `nox-mesh-host key` prints it", }, []string{"node", "why", "overlay_key"})}, {Name: mcat.EnrolmentVerbRedeem, Description: "Spend a token: asked by a joining node-engine dialled as enrol. " + "alone, with the public halves of its keys and its identity key's proof over them. Spends the token, asks the " + - "vault for the node's account and certificate, writes the node into placement, and answers the account sealed " + + "vault for the node's account and certificate, writes the node into the assignments, and answers the account sealed " + "to the node, its certificate and the vault's public half, signed by the vault. Asked again with the same keys " + "it finishes what is not done and answers the same; with other keys it is refused.", Input: map[string]any{"type": "object", "additionalProperties": false, diff --git a/internal/compose/resolution/keyed_test.go b/internal/compose/resolution/keyed_test.go index 2ca665c..7846f10 100644 --- a/internal/compose/resolution/keyed_test.go +++ b/internal/compose/resolution/keyed_test.go @@ -54,7 +54,7 @@ func TestAModuleNotHandedHasAPlanForWhatAHelperAnswers(t *testing.T) { t.Fatalf("the plan gives %+v (%v)", given, err) } // Its port is where the machine publishes it, the number ${port:9000} gives the module and its declaration publishes, - // never the software's own where the placement moved it (2026-10-31: route-proxy dialled gitea's 3000, published at + // never the software's own where the assigner moved it (2026-10-31: route-proxy dialled gitea's 3000, published at // 20000): its provider dials what the plan says. Where a plan carries it, a port the module does not listen on is // refused by module, node and port. for _, p := range []struct { @@ -65,10 +65,10 @@ func TestAModuleNotHandedHasAPlanForWhatAHelperAnswers(t *testing.T) { port float64 refused string }{ - {"assigned by the placement (gitea 3000 at 20000)", map[string]map[int]int{"notify": {9000: 20009}}, nil, true, 20009, ""}, - {"given by the node over the placement", map[string]map[int]int{"notify": {9000: 20009}}, + {"assigned by the assigner (gitea 3000 at 20000)", map[string]map[int]int{"notify": {9000: 20009}}, nil, true, 20009, ""}, + {"given by the node over the assigner", map[string]map[int]int{"notify": {9000: 20009}}, map[string]map[int]int{"notify": {9000: 9900}}, true, 9900, ""}, - {"a bare published port the placement holds none for keeps its number (influxdb 8086)", + {"a bare published port the assigner holds none for keeps its number (influxdb 8086)", map[string]map[int]int{"notify": {9000: 9000}}, nil, true, 9000, ""}, {"a mapping the module fixed (as mailu 7080:80)", map[string]map[int]int{"notify": {9000: 7080}}, nil, true, 7080, ""}, {"a port the module does not listen on", nil, nil, true, 0, diff --git a/internal/compose/resolution/placement_seat.go b/internal/compose/resolution/placement_seat.go deleted file mode 100644 index e2915ec..0000000 --- a/internal/compose/resolution/placement_seat.go +++ /dev/null @@ -1,59 +0,0 @@ -package resolution - -import ( - mcat "git.novox.be/novox/mesh-sdk/go/catalogue" -) - -// placementVerbs are the seat's verbs: what reads all it keeps and does by hand all it does by itself (ADR 0567 §10). -// Optional while the holder that serves them is not yet on every node (design 33 §7): the seat is new in the same -// delivery as its holder. -func placementVerbs() []mcat.Verb { - changes := map[string]any{"type": "array", "minItems": 1, "description": "the changes, each an object with `op`: " + - "place (node, module, ports, bindings), take-off (node, module), pin (node, provision, provider {node, module}), " + - "unpin (node, provision), hold (node, module, claim, scope, kind, site), unhold (node, module, claim), decide " + - "(node, field, value: a decided field of the node, its address among them), config (node, module, mesh-wide, " + - "layer, mesh-wide-values, values), add-node (node, decided, keys: a joining node, mesh-node-enroller's alone, the " + - "proposal's one change, with the enrolment's warrant, its tunnel key alone at the operator's yes; ADR 0587 §7), " + - "remove-node (node: a joining node never confirmed, taken off when its enrolment ends; mesh-node-enroller's alone, " + - "on the same warrant)", - "items": map[string]any{"type": "object", "required": []string{"op", "node"}}} - return []mcat.Verb{ - {Name: mcat.PlacementVerbPlacement, Optional: true, Description: "Read placement: one node's key, the nodes one " + - "module is placed on, or every key, each with its revision, and how full the state is against its cap. With " + - "history, one node's or one module's changes in order: a revision the state keeps whole, an older one as the " + - "proposal's changes from its placement-changed event.", - Input: schema(map[string]string{"node": "one node", "module": "one module: the nodes it is placed on", - "history": "\"true\": the changes in order, from the stream and the state's revisions", - // ADR 0571 clause 4: who holds which seat on which node, from the state seat-holders. - "seats": "\"true\": who holds each seat, and on which node (the state seat-holders); with node, that node's"}, - nil, "history", "seats")}, - {Name: mcat.PlacementVerbProposals, Optional: true, Description: "Every open proposal of placement: its changes, " + - "the revision of each key it was laid over, its candidates, every node's answer, the differences computed and " + - "answered, its verdict and its ask. Narrowed to one by id.", - Input: schema(map[string]string{"id": "one proposal's id"}, nil)}, - {Name: mcat.PlacementVerbPropose, Optional: true, Description: "Propose a change of placement, with why: laid over " + - "the current keys and refused at once, naming every finding, when the result cannot be placed (two modules " + - "on one host port, a provider not placed, a seat held once held twice, two nodes with one address, two " + - "hubs, a module the indexer does not hold, a node with no record). Otherwise the nodes it touches compose " + - "it in shadow and answer, and on a passing verdict the operator is asked to approve it. Who proposes is the " + - "caller, as the runtime proves it. An add-node from mesh-node-enroller, whose warrant names its node and address, " + - "is written with no ask of its own: its Approve is the enrolment's (ADR 0587 §7).", - Input: map[string]any{"type": "object", "additionalProperties": false, "required": []string{"why", "changes"}, - "properties": map[string]any{ - "why": map[string]any{"type": "string", "description": "why, shown to the operator with the proposal"}, - "changes": changes, - // ADR 0587 §7: an add-node carries the warrant of the enrolment that issued the node's token, whose - // Approve is the proposal's; the holder writes it with no ask of its own. - "warrant": map[string]any{"type": "object", "description": "for an add-node alone: the enrolment's " + - "warrant {node, address, ask, warrant}, naming the change's node and address", - "required": []string{"node", "address", "ask", "warrant"}}, - }}}, - {Name: mcat.PlacementVerbWithdraw, Optional: true, Description: "Withdraw an open proposal: its key and its " + - "candidates are deleted and placement-withdrawn carries it whole. With why.", - Input: schema(map[string]string{"id": "the proposal's id", "why": "why, kept in the event"}, []string{"id", "why"})}, - {Name: mcat.PlacementVerbAgain, Optional: true, Description: "Judge an open proposal now with the answers that " + - "stand, and raise its ask again when its verdict passes and the operator's channel does not hold the ask. " + - "Nothing more: the recovery of a proposal whose judgement or ask was lost.", - Input: schema(map[string]string{"id": "the proposal's id"}, []string{"id"})}, - } -} diff --git a/internal/compose/resolution/proofs_test.go b/internal/compose/resolution/proofs_test.go index 76d3686..cd174e4 100644 --- a/internal/compose/resolution/proofs_test.go +++ b/internal/compose/resolution/proofs_test.go @@ -408,11 +408,11 @@ func TestRouteProxysProofsOfAComposition(t *testing.T) { placed map[int]int at int }{ - {"beside the proxy, moved by the placement (gitea)", "anchor", "gitea", 3000, []any{"3000"}, map[int]int{3000: 20000}, 20000}, - {"on another machine, moved by the placement (grafana)", "home", "grafana", 3000, []any{"3000"}, map[int]int{3000: 20012}, 20012}, - {"a bare port the placement holds none for (influxdb)", "home", "influxdb", 8086, []any{"8086"}, nil, 8086}, + {"beside the proxy, moved by the assigner (gitea)", "anchor", "gitea", 3000, []any{"3000"}, map[int]int{3000: 20000}, 20000}, + {"on another machine, moved by the assigner (grafana)", "home", "grafana", 3000, []any{"3000"}, map[int]int{3000: 20012}, 20012}, + {"a bare port the assigner holds none for (influxdb)", "home", "influxdb", 8086, []any{"8086"}, nil, 8086}, {"a fixed mapping (mailu 7080:80)", "anchor", "mailu", 7080, []any{"25", "7080:80"}, nil, 7080}, - {"a fixed mapping the placement names another port of (photos 9102:9000)", "anchor", "photos", 9102, + {"a fixed mapping the assigner names another port of (photos 9102:9000)", "anchor", "photos", 9102, []any{"9102:9000"}, map[int]int{9000: 20011}, 9102}, {"the host's network (home-assistant)", "anchor", "home-assistant", 8123, nil, nil, 8123}, } { diff --git a/internal/composer/composer.go b/internal/composer/composer.go index 3e5f3f2..efd4688 100644 --- a/internal/composer/composer.go +++ b/internal/composer/composer.go @@ -10,8 +10,8 @@ package composer import ( "encoding/json" "fmt" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay" "slices" @@ -138,19 +138,19 @@ func runCase(r Request, c Case, depth string) (CaseVerdict, error) { if err != nil { return CaseVerdict{}, err } - placements := map[string]*meshmoduleassigner.Placement{} - for node, raw := range c.Placements { + assignments := map[string]*meshmoduleassigner.Assignments{} + for node, raw := range c.Assignments { if string(raw) == "null" { - placements[node] = nil + assignments[node] = nil continue } - p, perr := meshmoduleassignerclient.DecodePlacement(raw) + p, perr := meshmoduleassignerclient.DecodeAssignments(raw) if perr != nil { - return CaseVerdict{}, fmt.Errorf("composer: the case %q: the placement of %s is not one: %w", c.Name, node, perr) + return CaseVerdict{}, fmt.Errorf("composer: the case %q: the assignment of %s is not one: %w", c.Name, node, perr) } - placements[node] = &p + assignments[node] = &p } - in, err := frombus.Placed(base, placements) + in, err := frombus.Placed(base, assignments) if err != nil { return CaseVerdict{}, fmt.Errorf("composer: the case %q: %w", c.Name, err) } diff --git a/internal/composer/composer_test.go b/internal/composer/composer_test.go index c9ff7e1..bd2e9d6 100644 --- a/internal/composer/composer_test.go +++ b/internal/composer/composer_test.go @@ -3,7 +3,7 @@ package composer import ( "context" "encoding/json" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay" @@ -55,17 +55,17 @@ func TestARequestItCannotRunIsRefused(t *testing.T) { if _, err := Run(Request{V: Version, Depth: "deep"}); err == nil { t.Error("an unknown depth runs") } - // A case's own placement (issue 1126) composes over a node the input holds, and is refused for one it does not. + // A case's own assignment (issue 1126) composes over a node the input holds, and is refused for one it does not. placed := withInput(t, Request{V: Version, Machines: []string{"a"}, Depth: DepthResolve, Cases: []Case{{Name: "trunk", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}}, {Name: "change", Manifests: map[string]Manifest{"demo": {Raw: json.RawMessage(demo)}}, - Placements: bucketstest.Raw(map[string]meshmoduleassigner.Placement{"a": {V: 1, Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "ghost"}}}})}}}, + Assignments: bucketstest.Raw(map[string]meshmoduleassigner.Assignments{"a": {V: 1, Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "ghost"}}}})}}}, compose.Input{Nodes: []compose.Node{{Name: "a"}}}) if v, err := Run(placed); err != nil || !v.Cases[0].Machines["a"].Composes || v.Cases[1].Machines["a"].Composes || !strings.Contains(v.Cases[1].Machines["a"].Reason, "ghost") { - t.Errorf("a case composes its own placement: %+v, %v", v, err) + t.Errorf("a case composes its own assignment: %+v, %v", v, err) } - placed.Cases[1].Placements = bucketstest.Raw(map[string]meshmoduleassigner.Placement{"z": {V: 1, Node: "z"}}) + placed.Cases[1].Assignments = bucketstest.Raw(map[string]meshmoduleassigner.Assignments{"z": {V: 1, Node: "z"}}) if _, err := Run(placed); err == nil || !strings.Contains(err.Error(), "holds no node for") { t.Errorf("a case placing a node the input lacks: %v", err) } diff --git a/internal/compositions/candidate.go b/internal/compositions/candidate.go index 0bed69d..b80a54d 100644 --- a/internal/compositions/candidate.go +++ b/internal/compositions/candidate.go @@ -15,8 +15,8 @@ const ( MaxAnswerBytes = 160 << 10 ) -// ReturnsToNothing is what a node answers a proposal that takes its placement key away. -const ReturnsToNothing = "the proposal takes this node's placement away: it composes nothing then" +// ReturnsToNothing is what a node answers a proposal that takes its assignment key away. +const ReturnsToNothing = "the proposal takes this node's assignment away: it composes nothing then" // NodeWord is the last part of a node's own key, and the name of its own composition in an answer. const NodeWord = "node" diff --git a/internal/compositions/composed_compare.go b/internal/compositions/composed_compare.go index 446021b..ec3606e 100644 --- a/internal/compositions/composed_compare.go +++ b/internal/compositions/composed_compare.go @@ -24,7 +24,7 @@ type Comparison struct { } // CompareComposed compares the compositions a node composed (by key, as compose.Compositions answers them) with the ones -// it runs, as its agent would (ADR 0559 §4: a node's answer to a placement candidate). A module is the same when its +// it runs, as its agent would (ADR 0559 §4: a node's answer to a assignment candidate). A module is the same when its // composition's digest is the one it runs (ComposedDigestOf, the signature left out), so the agent would apply nothing // of it; it differs when the digests differ (both named); it is added when composed and not run, removed when run and // not composed. The node's own composition is named HaveNodeWord. The bus's user list (catalogue.BusUsersID) is left out diff --git a/internal/inventory/inventory_test.go b/internal/inventory/inventory_test.go index a3b0dce..60bc2b1 100644 --- a/internal/inventory/inventory_test.go +++ b/internal/inventory/inventory_test.go @@ -54,7 +54,7 @@ func TestMemoryIsReadOrReportedMissing(t *testing.T) { t.Errorf("memoryFrom returned %d, %v", kb, ok) } // A meminfo without MemTotal must not read as zero memory — a machine reporting no memory - // would be excluded from placement for a parsing failure. + // would be excluded from assignment for a parsing failure. if _, ok := memoryFrom("MemFree: 1 kB\n"); ok { t.Error("a meminfo with no MemTotal was read as a successful measurement") } diff --git a/internal/judge/aliases.go b/internal/judge/aliases.go index 2ad8d2e..a8ed3f9 100644 --- a/internal/judge/aliases.go +++ b/internal/judge/aliases.go @@ -1,5 +1,5 @@ // Package judge is the node-engine's judgement of a move (novox/hq ADR 0377 §3, ADR 0547 §3): what moving a module from -// the composition it runs to a new one does, and how a placement candidate's answer is fitted to the node's key. It +// the composition it runs to a new one does, and how a assignment candidate's answer is fitted to the node's key. It // moved here from mesh-sdk's delivery package (novox/hq ADR 0593 §1, §7), since only the node-engine judges a move; // the shapes it judges (a composition, a judgement's words, a candidate's answer) stay in mesh-sdk. package judge diff --git a/internal/judge/answer_test.go b/internal/judge/answer_test.go index 70e0cc7..eb4c9ee 100644 --- a/internal/judge/answer_test.go +++ b/internal/judge/answer_test.go @@ -8,7 +8,7 @@ import ( ) // An answer too large for the node's key is fitted: the texts go first, the verdict and the paths stay, and the answer -// given is not changed (moved from mesh-sdk's placement test with FitAnswer, novox/hq ADR 0593 §7). +// given is not changed (moved from mesh-sdk's assignment test with FitAnswer, novox/hq ADR 0593 §7). func TestFitAnswer(t *testing.T) { long := strings.Repeat("x", compositions.MaxBody) big := CandidateAnswer{Proposal: "p-x1", Revision: 1, Verdict: nodeengine.CandidateAnswerVerdictDiffers, Differs: []ModuleDiffers{{ diff --git a/internal/link/enrol_seat.go b/internal/link/enrol_seat.go index f75cbdd..3d11c38 100644 --- a/internal/link/enrol_seat.go +++ b/internal/link/enrol_seat.go @@ -159,7 +159,7 @@ type Joining struct { // Run joins: the answer is verified before anything is kept (an answer not signed by the key the token carries keeps nothing), // kept, and then the machine dials as the login its key of node-logins names and confirms. Nothing is applied here: the engine -// applies once its placement names a module. +// applies once its assignment names a module. func (j Joining) Run(ctx context.Context) (meshnodeenrollerclient.RedeemAnswer, error) { answer, err := j.Redeem(ctx) if err != nil { diff --git a/internal/link/have.go b/internal/link/have.go index 0047e6f..063ac25 100644 --- a/internal/link/have.go +++ b/internal/link/have.go @@ -52,7 +52,7 @@ type haveState struct { // nodeSealed is, on a node composing its own, each value its own composition carries sealed to its key (ADR 0547 §3, // issue 1126): kept in its key so a restart reuses them and applies nothing again. nodeSealed compositions.Seals - // nodeCandidates is the node's answer to each placement candidate proposal (novox/hq ADR 0559 §4), written into its key. + // nodeCandidates is the node's answer to each assignment candidate proposal (novox/hq ADR 0559 §4), written into its key. nodeCandidates map[string]nodeengine.CandidateAnswer // nodeComposes and nodeComposesWhy are whose compositions the node applies and why (HaveNode.Composes), written into // its key. @@ -748,7 +748,7 @@ func (q *Session) NoteNode(ctx context.Context, composed string, sealed composit q.writeHaves(ctx, bus) } -// NoteCandidates takes the node's answers to the placement candidates, by proposal (its shadow composer's, novox/hq ADR +// NoteCandidates takes the node's answers to the assignment candidates, by proposal (its shadow composer's, novox/hq ADR // 0559 §4), and writes the node's key where it differs; a proposal left out is no longer answered. func (q *Session) NoteCandidates(ctx context.Context, answers map[string]nodeengine.CandidateAnswer) { q.init() diff --git a/internal/link/have_test.go b/internal/link/have_test.go index 79a8b94..71c841b 100644 --- a/internal/link/have_test.go +++ b/internal/link/have_test.go @@ -617,7 +617,7 @@ func TestAnAppliedModulesHaveNamesTheFingerprintAndTheDigestAndFollowsItsHealth( if n, ok := q.ReadNodeHave(context.Background()); !ok || n.Composed != dig || len(n.SealedAt) != 1 { t.Fatalf("the node key read back: %+v %v", n, ok) } - // The node's answers to placement candidates ride in the same key (ADR 0559 §4), and go when no group is answered. + // The node's answers to assignment candidates ride in the same key (ADR 0559 §4), and go when no group is answered. answers := map[string]nodeengine.CandidateAnswer{"p-1": {Proposal: "p-1", Revision: 1, Verdict: nodeengine.CandidateAnswerVerdictSame, Same: []string{"web"}}} q.NoteCandidates(context.Background(), answers) if n, err := nodeengineclient.DecodeHaveNode(l.kv["n1.node"].value); err != nil || n.Candidates["p-1"].Proposal != "p-1" || n.Composed != dig { diff --git a/internal/link/history_integration_test.go b/internal/link/history_integration_test.go index 38e1617..61dbc42 100644 --- a/internal/link/history_integration_test.go +++ b/internal/link/history_integration_test.go @@ -71,7 +71,7 @@ func TestNatsAKeysRevisionsAreReadBack(t *testing.T) { } // A bucket that is not there, and one the account may not read, are errors at once, never a wait: the node agent - // follows its placement key by one watch at its start (endWhenPlaced) and, refused, stays on the controller's compositions and says why. + // follows its assignment key by one watch at its start (endWhenPlaced) and, refused, stays on the controller's compositions and says why. if _, err := bus.KVWatch(ctx, "never-it", ">"); err == nil { t.Error("a watch of a bucket that is not there gave no error") } @@ -112,13 +112,13 @@ func TestNatsAKeysRevisionsAreReadBack(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err := later.PutString("placement.n1", "v"); err != nil { + if _, err := later.PutString("n1.node", "v"); err != nil { t.Fatal(err) } for seen := false; !seen; { select { case u := <-ch: - seen = u.Key == "placement.n1" && string(u.Value) == "v" + seen = u.Key == "n1.node" && string(u.Value) == "v" case <-actx.Done(): t.Fatal("the bucket made after the watch was never watched") } diff --git a/internal/link/watch.go b/internal/link/watch.go index 9f03840..948a675 100644 --- a/internal/link/watch.go +++ b/internal/link/watch.go @@ -25,7 +25,7 @@ type KVUpdate struct { type KVBus interface { // KVWatch yields the keys of the bucket matching the pattern now, a Caught update, then every change, until ctx ends // or the link does (the channel is then closed). A bucket the node's account may not read is an error. The pattern - // may be several, separated by spaces (WatchPatterns), watched as one: a node's placement in either form of its + // may be several, separated by spaces (WatchPatterns), watched as one: a node's assignment in either form of its // keys (research 072 row 37). KVWatch(ctx context.Context, bucket, pattern string) (<-chan KVUpdate, error) } @@ -269,7 +269,7 @@ func CreatedAdvisory(bucket string) string { return "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_" + bucket } -// KVWatchAwait is KVWatch for a bucket that may not be on the bus yet (the placement seat's states, made when their +// KVWatchAwait is KVWatch for a bucket that may not be on the bus yet (the assignment seat's states, made when their // holder starts): the channel is returned at once, and while the bucket is missing it yields nothing and waiting is told // why; when the bus says the bucket's stream was created the watch is made and its updates follow. No timer: the // advisory is heard before the first try, so a creation between the try and the wait is not missed. Any other error of diff --git a/internal/nodegrant/testdata/node.json b/internal/nodegrant/testdata/node.json index bfb8c60..788e0cb 100644 --- a/internal/nodegrant/testdata/node.json +++ b/internal/nodegrant/testdata/node.json @@ -4,7 +4,7 @@ "$JS.API.CONSUMER.CREATE.KV_composed-NODE.>", "$JS.API.CONSUMER.CREATE.KV_mesh-config_resolved.*.$KV.mesh-config_resolved.NODE.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_candidates.>", - "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_placement.>", + "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_assignments.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_wants.*.$KV.mesh-delivery-assigner_wants.want.NODE.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-builder_builds.>", "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_candidates.>", @@ -22,7 +22,7 @@ "$JS.API.CONSUMER.DELETE.KV_composed-NODE.>", "$JS.API.CONSUMER.DELETE.KV_mesh-config_resolved.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_candidates.>", - "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_placement.>", + "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_assignments.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-assigner_wants.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-builder_builds.>", "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-indexer_candidates.>", @@ -40,7 +40,7 @@ "$JS.API.DIRECT.GET.KV_composed-NODE.>", "$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.NODE.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_candidates.>", - "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_placement.>", + "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_assignments.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_wants.$KV.mesh-delivery-assigner_wants.want.NODE.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-builder_builds.>", "$JS.API.DIRECT.GET.KV_mesh-delivery-indexer_candidates.>", @@ -58,7 +58,7 @@ "$JS.API.STREAM.INFO.KV_composed-NODE", "$JS.API.STREAM.INFO.KV_mesh-config_resolved", "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_candidates", - "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_placement", + "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_assignments", "$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_wants", "$JS.API.STREAM.INFO.KV_mesh-delivery-builder_builds", "$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_candidates", @@ -75,7 +75,7 @@ "$JS.FC.KV_composed-NODE.>", "$JS.FC.KV_mesh-config_resolved.>", "$JS.FC.KV_mesh-delivery-assigner_candidates.>", - "$JS.FC.KV_mesh-delivery-assigner_placement.>", + "$JS.FC.KV_mesh-delivery-assigner_assignments.>", "$JS.FC.KV_mesh-delivery-assigner_wants.>", "$JS.FC.KV_mesh-delivery-builder_builds.>", "$JS.FC.KV_mesh-delivery-indexer_candidates.>", @@ -112,7 +112,7 @@ "PublishDeny": null, "Subscribe": [ "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_candidates", - "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_placement", + "$JS.EVENT.ADVISORY.STREAM.CREATED.KV_mesh-delivery-assigner_assignments", "$SRV.INFO", "$SRV.INFO.node-engine", "$SRV.INFO.node-engine.>", diff --git a/internal/selfcompose/candidate.go b/internal/selfcompose/candidate.go index 023db96..3791f1e 100644 --- a/internal/selfcompose/candidate.go +++ b/internal/selfcompose/candidate.go @@ -6,9 +6,9 @@ import ( "encoding/json" "errors" "fmt" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "github.com/novox/mesh-host/internal/buckets" engcomp "github.com/novox/mesh-host/internal/compositions" "maps" @@ -28,7 +28,7 @@ import ( // **A check's candidate, composed in shadow on a call** (novox/hq ADR 0593 §7): the node seat's verb compose-candidate. // Given the check's composition key (`.`), it reads the group's candidate from the indexer // (buckets.Candidates, keyed by the group alone, buckets.CandidateKey) at those heads, and composes itself -// twice from what its shadow holds (placement, records, the indexer's modules, its config values), with its own sealed +// twice from what its shadow holds (assignment, records, the indexer's modules, its config values), with its own sealed // values: once as it is, and once with the candidate's modules laid over the indexer's (each at the candidate's // fingerprint, its manifest by digest and the indexer's verdict on it, ADR 0578 §3; a module the candidate removes left // out). It answers per module placed here composed (its digest, and whether the candidate changes it), refused by the @@ -315,7 +315,7 @@ func (c *Composer) scratch() *Composer { out := New(c.env) c.mu.Lock() defer c.mu.Unlock() - out.placements = maps.Clone(c.placements) + out.assignments = maps.Clone(c.assignments) out.records = maps.Clone(c.records) out.inOwn = maps.Clone(c.inOwn) out.modules = maps.Clone(c.modules) @@ -326,7 +326,7 @@ func (c *Composer) scratch() *Composer { out.sealedOwn = maps.Clone(c.sealedOwn) out.ownHanded = maps.Clone(c.ownHanded) out.certified = c.certified - out.placementKeys = c.placementKeys + out.assignmentKeys = c.assignmentKeys out.origin = c return out } @@ -433,14 +433,14 @@ func (c *Composer) layCandidate(ctx context.Context, cand meshcatalog.Candidates func (c *Composer) composeShadow(ctx context.Context, running []engcomp.Composed, laid map[string]bool, input func(frombus.Scope, map[string]string)) (map[string]engcomp.Composed, map[string]string, error) { node := c.env.Node c.mu.Lock() - placement, placed := c.placements[node] + assignment, placed := c.assignments[node] modules := maps.Clone(c.modules) - inOwn := c.inOwnOf(placement) + inOwn := c.inOwnOf(assignment) c.mu.Unlock() if !placed { - return nil, nil, errors.New("this node's key in the seat's placement state is gone: it composes nothing of its own") + return nil, nil, errors.New("this node's key in the seat's assignment state is gone: it composes nothing of its own") } - fps := shadowBuilds(running, placement, modules, inOwn) + fps := shadowBuilds(running, assignment, modules, inOwn) // A module the laid candidate changes is composed at the candidate's build, though the node runs another. for module := range fps { if mod, ok := modules[module]; ok && laid[module] { diff --git a/internal/selfcompose/candidate_test.go b/internal/selfcompose/candidate_test.go index e659478..b1513be 100644 --- a/internal/selfcompose/candidate_test.go +++ b/internal/selfcompose/candidate_test.go @@ -3,8 +3,7 @@ package selfcompose import ( "context" "encoding/json" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" @@ -25,7 +24,7 @@ import ( // novox/hq ADR 0593 §7: the verb compose-candidate composes a check's candidate on this node in shadow. A candidate that // changes a placed module composes it at the candidate's build, changed; one whose manifest the indexer refused is // answered refused, naming the module and the judge's words, and the verdict is refused; a candidate the indexer does not -// hold is refused in words. Nothing is applied, nothing is written, no placement answer is handed, and the answer comes +// hold is refused in words. Nothing is applied, nothing is written, no assignment answer is handed, and the answer comes // within the bound; a shadow not ready and an ended bound answer unready at once, which the checker takes as no answer. func TestTheNodeComposesACandidateOnAsk(t *testing.T) { b := newFakeBus() @@ -87,7 +86,7 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) { if a := s.ComposeCandidate(ctx, nodeengine.ComposeCandidateInput{Candidate: accepted}); a.Verdict != nec.Unready || a.Refused == "" { t.Errorf("a shadow not ready answered %+v", a) } - place, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}}) have, _ := nec.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) @@ -96,10 +95,10 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) { for watching() < 6 && time.Now().Before(deadline) { time.Sleep(5 * time.Millisecond) } - b.put(t, buckets.Placement, "placement.n1", place) + b.putAssigned(t, place) b.put(t, buckets.Have, "n1.node", have) b.put(t, buckets.Modules, "lemurs", mod) - for _, bucket := range []string{buckets.PlacementCandidates, buckets.Placement, buckets.Have, buckets.Modules, ResolvedBucket} { + for _, bucket := range []string{buckets.ProposalCandidates, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket} { b.send(bucket, agent.Update{Caught: true}) } for !s.ready.Load() && time.Now().Before(deadline) { @@ -185,7 +184,7 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) { var tick atomic.Int64 env.Now = func() time.Time { return time.Unix(0, 0).Add(time.Duration(tick.Add(1)) * time.Second) } s := NewShadow(env, nil) - s.c.placements["n1"] = meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + s.c.assignments["n1"] = meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}} s.c.records["n1"] = nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}} s.c.modules["lemurs"] = meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"} diff --git a/internal/selfcompose/composedstate.go b/internal/selfcompose/composedstate.go index 5c4dab7..6793a85 100644 --- a/internal/selfcompose/composedstate.go +++ b/internal/selfcompose/composedstate.go @@ -54,7 +54,7 @@ type published struct { fps map[string]string // by module, the wanted fingerprint comps map[string]engcomp.Composed held map[string]string - // placed is the modules the node's placement puts here, ran what runs of each module (by its composition's FP). + // placed is the modules the node's assignment puts here, ran what runs of each module (by its composition's FP). placed []string ran map[string]string } diff --git a/internal/selfcompose/composedstate_test.go b/internal/selfcompose/composedstate_test.go index 9f57406..a1a45f8 100644 --- a/internal/selfcompose/composedstate_test.go +++ b/internal/selfcompose/composedstate_test.go @@ -3,12 +3,11 @@ package selfcompose import ( "context" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "github.com/novox/mesh-host/internal/buckets" "github.com/novox/mesh-host/internal/buckets/bucketstest" @@ -91,20 +90,20 @@ func TestTheNodePublishesItsCompositions(t *testing.T) { if err != nil { t.Fatal(err) } - place, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "oldie"}}}) have, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) omod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: ofp, Manifest: odigest, Repository: "novox/o", Commit: "c"}) want, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true}) nodeWant, _ := bucketstest.NodeWant(meshwants.WantsByWantNodeNodeValue{Self: true}) - b.put(t, buckets.Placement, "placement.n1", place) + b.putAssigned(t, place) b.put(t, buckets.Have, "n1.node", have) b.put(t, buckets.Modules, "lemurs", mod) b.put(t, buckets.Modules, "oldie", omod) b.put(t, buckets.Wants, "want.n1.lemurs", want) b.put(t, buckets.Wants, "want.n1.node", nodeWant) - for _, bucket := range []string{buckets.Wants, buckets.Placement, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { + for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { b.send(bucket, agent.Update{Caught: true}) } for u := next(t, out); !u.Caught; u = next(t, out) { diff --git a/internal/selfcompose/selfcompose.go b/internal/selfcompose/selfcompose.go index 5519e1d..080dca2 100644 --- a/internal/selfcompose/selfcompose.go +++ b/internal/selfcompose/selfcompose.go @@ -2,7 +2,7 @@ // modules and its own composition with mesh-sdk's composer, from what is on the bus, and hands the compositions to the // agent as the agent once read them from the controller's bucket `composed-`. // -// **It watches, it never polls** (ADR 0547 §2): its inputs are watches of the node's wants, the indexer's placement and +// **It watches, it never polls** (ADR 0547 §2): its inputs are watches of the node's wants, the indexer's assignment and // modules, every node's own have key (where a node's record rides, ADR 0555 §3) and mesh-config's resolved values of this // node. A change of any of them marks the node to compose again; one loop drains the mark, so a burst of changes composes // the node once (composing one node takes 0.8 to 4.4 s, measured 2026-10-26). It hands the agent only the compositions that @@ -19,14 +19,14 @@ import ( "encoding/json" "errors" "fmt" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-sdk/go/declaration" "github.com/novox/mesh-host/internal/buckets" @@ -135,13 +135,13 @@ const VaultSecretChanged = "mesh.mod.mesh-vault.event.secret-changed" type Composer struct { env Env - mu sync.Mutex - wants map[string]meshwants.WantsByWantNodeModuleValue // by module, the node's own under agent.NodeModule - placements map[string]meshmoduleassigner.Placement - // placementKeys is the seat's state placement key by key in either form, each node's placement assembled from it + mu sync.Mutex + wants map[string]meshwants.WantsByWantNodeModuleValue // by module, the node's own under agent.NodeModule + assignments map[string]meshmoduleassigner.Assignments + // assignmentKeys is the seat's state assignment key by key in either form, each node's assignment assembled from it // (meshmoduleassignerclient.Keys; research 072 row 37): a write of the holder's under way is never composed half. - placementKeys *meshmoduleassignerclient.Keys - records map[string]nodeengine.NodeRecord + assignmentKeys *meshmoduleassignerclient.Keys + records map[string]nodeengine.NodeRecord // inOwn is, by module, whether this node's have of it names no fingerprint of the new form (mesh-wireguard, a module // at a build of the old form): the node runs it inside its own composition, and composes it there again (issue 1126). inOwn map[string]bool @@ -307,7 +307,7 @@ func New(env Env) *Composer { if env.Say == nil { env.Say = func(string) {} } - return &Composer{env: env, wants: map[string]meshwants.WantsByWantNodeModuleValue{}, placements: map[string]meshmoduleassigner.Placement{}, placementKeys: meshmoduleassignerclient.NewKeys(), + return &Composer{env: env, wants: map[string]meshwants.WantsByWantNodeModuleValue{}, assignments: map[string]meshmoduleassigner.Assignments{}, assignmentKeys: meshmoduleassignerclient.NewKeys(), records: map[string]nodeengine.NodeRecord{}, inOwn: map[string]bool{}, modules: map[string]meshcatalog.ModulesByModuleValue{}, values: map[string]map[string]any{}, manifests: map[string][]byte{}, archives: map[string][]byte{}, builds: map[string]builds.BuilderBuild{}, handed: map[string]string{}, sealedOwn: map[string]ownSealed{}, pkgs: map[string]compositions.Composed{}, asked: make(chan chan Pass, 8)} @@ -363,7 +363,7 @@ func (c *Composer) inputs() []input { node := c.env.Node return []input{ {buckets.Wants, "want." + node + ".>", (*Composer).takeWant, nil}, - {buckets.Placement, ">", (*Composer).takePlacement, nil}, + {buckets.Assignments, ">", (*Composer).takeAssignments, nil}, // Every node's record, and this node's own haves (what it runs inside its own composition): two watches, each // within the node's grant (buscompose: readGrants of the have bucket at `*.node`, NodeKeysGrants at `.>`). // A watch of every key is refused by the bus (issue 1126, 2026-10-27 09:21: the fold's `>` stopped every agent). @@ -396,7 +396,7 @@ func (c *Composer) takeBuildDone(u agent.Update) bool { w, wanted := c.wants[module] m := c.modules[module] placedHere := false - for _, pm := range c.placements[c.env.Node].Modules { + for _, pm := range c.assignments[c.env.Node].Modules { placedHere = placedHere || pm.Module == module } c.mu.Unlock() @@ -593,7 +593,7 @@ func (c *Composer) caughtSent() bool { return sent } -// takeChanged takes u through in.take and says whether the entry it names changed: a want, a placement, a module or a +// takeChanged takes u through in.take and says whether the entry it names changed: a want, a assignment, a module or a // config value written again with the value it held changes nothing composed, so it makes no pass (issue 774, // 2026-10-31: every put under want..> made a pass, and each pass asked the vault for the node's own secrets). func (c *Composer) takeChanged(in input, u agent.Update) bool { @@ -623,14 +623,14 @@ func (c *Composer) entry(bucket, key string) (v any, has, known bool) { } w, ok := c.wants[module] return w.Fp, ok, true - case buckets.Placement: - // Any key of a node, its node key, a module key or its legacy key, stands for the node's whole placement: a + case buckets.Assignments: + // Any key of a node, its node key, a module key or its legacy key, stands for the node's whole assignment: a // module key written before its node key names it changes nothing composed. node, _, _, err := meshmoduleassignerclient.ParseKey(key) if err != nil { return nil, false, true } - p, ok := c.placements[node] + p, ok := c.assignments[node] return p, ok, true case buckets.Modules: name, err := meshcatalogclient.ParseModulesKey(key) @@ -677,25 +677,25 @@ func (c *Composer) takeWant(u agent.Update) { c.wants[module] = w } -func (c *Composer) takePlacement(u agent.Update) { +func (c *Composer) takeAssignments(u agent.Update) { if u.Caught { return } c.mu.Lock() - if c.placementKeys == nil { - c.placementKeys = meshmoduleassignerclient.NewKeys() + if c.assignmentKeys == nil { + c.assignmentKeys = meshmoduleassignerclient.NewKeys() } - node, err := c.placementKeys.Put(u.Key, u.Value, u.Revision, u.Deleted || len(u.Value) == 0) + node, err := c.assignmentKeys.Put(u.Key, u.Value, u.Revision, u.Deleted || len(u.Value) == 0) if node != "" { - if p, _, ok := c.placementKeys.Of(node); ok { - c.placements[node] = p + if p, _, ok := c.assignmentKeys.Of(node); ok { + c.assignments[node] = p } else { - delete(c.placements, node) + delete(c.assignments, node) } } c.mu.Unlock() if node != "" && err != nil && c.env.Say != nil { - c.env.Say("the seat's placement of " + node + ": " + err.Error()) + c.env.Say("the seat's assignment of " + node + ": " + err.Error()) } } @@ -758,14 +758,14 @@ func (c *Composer) takeModuleHave(u agent.Update) bool { } // inOwnOf is, of the modules p places, those the node runs inside its own composition (c.inOwn), sorted; c.mu held. -// Machines is, from the placements this composer holds, each node by its address on the private network, and the hub's +// Machines is, from the assignments this composer holds, each node by its address on the private network, and the hub's // node: what names the machine a failing network part points at (nodeengine.NetworkPart.Targets, issue 774). Empty -// before any placement is read. +// before any assignment is read. func (c *Composer) Machines() (byAddress map[string]string, hub string) { c.mu.Lock() defer c.mu.Unlock() byAddress = map[string]string{} - for node, p := range c.placements { + for node, p := range c.assignments { if a, _, _ := strings.Cut(p.Decided.Address, "/"); a != "" { byAddress[a] = node } @@ -776,7 +776,7 @@ func (c *Composer) Machines() (byAddress map[string]string, hub string) { return byAddress, hub } -func (c *Composer) inOwnOf(p meshmoduleassigner.Placement) []string { +func (c *Composer) inOwnOf(p meshmoduleassigner.Assignments) []string { var out []string for _, m := range p.Modules { if c.inOwn[m.Module] { @@ -874,14 +874,14 @@ func (c *Composer) compose(ctx context.Context) []agent.Update { _, nodeWanted := c.wants[agent.NodeModule] // What it runs inside its own composition and no want names at a build of the new form stays there, as the // controller's composed writer folds it (issue 1126); compose folds none a want names. - own, placed := c.placements[c.env.Node] + own, placed := c.assignments[c.env.Node] inOwn := c.inOwnOf(own) r := reader{c: c} c.mu.Unlock() if !placed { // The catalogue does not place it: it composes nothing, applies no composition made elsewhere, and this is no // refusal of what it runs (issue 1126). - c.sayOnce("this node's key in the seat's placement state is gone: it composes nothing of its own, and keeps running what it runs") + c.sayOnce("this node's key in the seat's assignment state is gone: it composes nothing of its own, and keeps running what it runs") c.paid() // nothing of its own to compose, so no pass is owed and no timer armed again return nil } @@ -1317,10 +1317,10 @@ func (r reader) VaultAuthority(context.Context) string { var _ frombus.SignersReader = reader{} -// certificateWanted says whether a module the placement p puts on this node asks for a certificate for the machine's +// certificateWanted says whether a module the assignment p puts on this node asks for a certificate for the machine's // internal name (its manifest's `certificate`), read from the indexer's manifests; a manifest that cannot be read is // taken as asking none, and the composition says what it misses. -func (c *Composer) certificateWanted(ctx context.Context, r reader, p meshmoduleassigner.Placement) bool { +func (c *Composer) certificateWanted(ctx context.Context, r reader, p meshmoduleassigner.Assignments) bool { for _, m := range p.Modules { c.mu.Lock() digest := c.modules[m.Module].Manifest @@ -1348,7 +1348,7 @@ func (c *Composer) certificateWanted(ctx context.Context, r reader, p meshmodule // certifyIfWanted asks Certify once per serving key when a module placed here asks for a certificate and this node's // record holds none for its internal name and serving key from an authority it trusts (the one it holds, or the vault's). -func (c *Composer) certifyIfWanted(ctx context.Context, r reader, p meshmoduleassigner.Placement) { +func (c *Composer) certifyIfWanted(ctx context.Context, r reader, p meshmoduleassigner.Assignments) { if c.env.Certify == nil || !c.certificateWanted(ctx, r, p) { return } @@ -1377,11 +1377,11 @@ func (c *Composer) certifyIfWanted(ctx context.Context, r reader, p meshmoduleas c.mu.Unlock() } -func (r reader) Placements(context.Context) (map[string]meshmoduleassigner.Placement, error) { +func (r reader) Assignments(context.Context) (map[string]meshmoduleassigner.Assignments, error) { r.c.mu.Lock() defer r.c.mu.Unlock() - out := make(map[string]meshmoduleassigner.Placement, len(r.c.placements)) - for k, v := range r.c.placements { + out := make(map[string]meshmoduleassigner.Assignments, len(r.c.assignments)) + for k, v := range r.c.assignments { out[k] = v } return out, nil @@ -1721,7 +1721,7 @@ func Transient(err error) bool { // ComposedToday is the digest of module composed at the build fp with today's settings and secrets, as this node would // compose it now beside its other modules at their wanted builds, the boxes it runs kept as compose keeps them, and whether // it composed (novox/hq ADR 0601 §1): the node agent compares it with what runs, to say whether a move to another build -// changes the build alone. A module held, a node refused, or no placement: false, and the move is asked of a person. +// changes the build alone. A module held, a node refused, or no assignment: false, and the move is asked of a person. func (c *Composer) ComposedToday(ctx context.Context, module, fp string) (string, bool) { c.mu.Lock() fps, kept := map[string]string{}, map[string]string{} @@ -1739,7 +1739,7 @@ func (c *Composer) ComposedToday(ctx context.Context, module, fp string) (string if mm, ok := c.modules[module]; !ok || mm.Fp2 != fp { kept[module] = fp } - own, placed := c.placements[c.env.Node] + own, placed := c.assignments[c.env.Node] inOwn := c.inOwnOf(own) c.mu.Unlock() if !placed { diff --git a/internal/selfcompose/selfcompose_test.go b/internal/selfcompose/selfcompose_test.go index 0a4dcee..fc714ca 100644 --- a/internal/selfcompose/selfcompose_test.go +++ b/internal/selfcompose/selfcompose_test.go @@ -8,13 +8,13 @@ import ( "encoding/json" "errors" "fmt" + "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-sdk/go/declaration" "github.com/novox/mesh-host/internal/buckets" @@ -197,13 +197,13 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { okey, _ := meshcatalogclient.ManifestsKey(odigest) b.kv[buckets.Manifests+"/"+okey] = oman omod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: meshcatalogclient.SourcePrefix2 + strings.Repeat("c", 64), Manifest: odigest, Repository: "novox/o", Commit: "c"}) - place, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "oldie"}}}) have, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) want, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true}) nodeWant, _ := bucketstest.NodeWant(meshwants.WantsByWantNodeNodeValue{Self: true}) - b.put(t, buckets.Placement, "placement.n1", place) + b.putAssigned(t, place) b.put(t, buckets.Have, "n1.node", have) oldHave, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Build: "build-1", Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK}) b.put(t, buckets.Have, "n1.oldie", oldHave) @@ -211,7 +211,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { b.put(t, buckets.Modules, "oldie", omod) b.put(t, buckets.Wants, "want.n1.lemurs", want) b.put(t, buckets.Wants, "want.n1.node", nodeWant) - for _, bucket := range []string{buckets.Wants, buckets.Placement, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { + for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { b.send(bucket, agent.Update{Caught: true}) } got := map[string]bool{} @@ -324,8 +324,8 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { for k, v := range c.wants { c2.wants[k] = v } - for k, v := range c.placements { - c2.placements[k] = v + for k, v := range c.assignments { + c2.assignments[k] = v } for k, v := range c.records { c2.records[k] = v @@ -441,9 +441,9 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { c3.modules[m] = meshcatalog.ModulesByModuleValue{Fp2: mfp, Manifest: dig, Repository: "novox/" + m, Commit: "c"} c3.wants[m] = meshwants.WantsByWantNodeModuleValue{Fp: mfp, Self: true} } - p3 := c3.placements["n1"] + p3 := c3.assignments["n1"] p3.Modules = append(append([]meshmoduleassigner.PlacedModule{}, p3.Modules...), meshmoduleassigner.PlacedModule{Module: "proxy"}, meshmoduleassigner.PlacedModule{Module: "de-spiegel"}) - c3.placements["n1"] = p3 + c3.assignments["n1"] = p3 r3 := c3.records["n1"] r3.Capabilities = []string{"systemd", "container-runtime"} c3.records["n1"] = r3 @@ -495,9 +495,9 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { // The indexer's verdict on the captured manifest (ADR 0578): the engine judges nothing, it takes this. Verdict: meshcatalogclient.VerdictRefused, Problems: []string{`bar item "shows" is none of them`}} c4.wants["power"] = meshwants.WantsByWantNodeModuleValue{Fp: powerFP, Self: true} - p4 := c4.placements["n1"] + p4 := c4.assignments["n1"] p4.Modules = append(append([]meshmoduleassigner.PlacedModule{}, p4.Modules...), meshmoduleassigner.PlacedModule{Module: "power"}) - c4.placements["n1"] = p4 + c4.assignments["n1"] = p4 handed4 := map[string]agent.Update{} for _, u := range c4.compose(ctx) { handed4[u.Key] = u @@ -513,10 +513,10 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { // what runs nor the kept declaration: with nothing running (the kept read retired, Running empty) the node's own // still carries its resources, so retiring the read removes nothing of it. c5, told5 := fresh(env.Get) - p5 := c5.placements["n1"] + p5 := c5.assignments["n1"] p5.Modules = append(append([]meshmoduleassigner.PlacedModule{}, p5.Modules...), meshmoduleassigner.PlacedModule{Module: overlay.Name}) p5.Decided.Endpoint = "n1.example:51820" - c5.placements["n1"] = p5 + c5.assignments["n1"] = p5 c5.inOwn[overlay.Name] = true nodeKey5, _ := compositions.NodeKey("n1") var own5 string @@ -597,12 +597,12 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { // secrets. A key written again with what it held, or a want whose words moved and not its build, makes none. worded, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true, Words: "lemurs is held at aaaaaaaaaaaa"}) elsewhere, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: meshcatalogclient.SourcePrefix2 + strings.Repeat("e", 64), Self: true}) - // Research 072 row 37: the placement converted to one key per module and node moves nothing composed; a write in - // keys makes its pass at its node key, the commit, and not at a module key written before it nor at the mirror. - placeBase := meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + // Research 072 row 37: the assignments kept as one key per module and node move nothing composed when written again; a + // write in keys makes its pass at its node key, the commit, and not at a module key written before it. + placeBase := meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "oldie"}}} placeKeys := meshmoduleassignerclient.NewKeys() - keyRows := func(after meshmoduleassigner.Placement, what string) []struct { + keyRows := func(after meshmoduleassigner.Assignments, what string) []struct { name, bucket, key string value []byte pass bool @@ -622,23 +622,25 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { name, bucket, key string value []byte pass bool - }{what + ": " + w.Key, buckets.Placement, w.Key, w.Value, w.Commit && what != "converted"}) + }{what + ": " + w.Key, buckets.Assignments, w.Key, w.Value, w.Commit}) } return rows } - _, _ = placeKeys.Put("placement.n1", place, 1, false) + for k, v := range place { + _, _ = placeKeys.Put(k, v, 1, false) + } lemursMoved := placeBase lemursMoved.Modules = []meshmoduleassigner.PlacedModule{{Module: "lemurs", Values: map[string]any{"greeting": "hoi"}}, {Module: "oldie"}} - var placementRows []struct { + var assignmentRows []struct { name, bucket, key string value []byte pass bool } for _, step := range []struct { what string - p meshmoduleassigner.Placement - }{{"converted", placeBase}, {"lemurs moved", lemursMoved}, {"lemurs back", placeBase}} { - placementRows = append(placementRows, keyRows(step.p, step.what)...) + p meshmoduleassigner.Assignments + }{{"lemurs moved", lemursMoved}, {"lemurs back", placeBase}} { + assignmentRows = append(assignmentRows, keyRows(step.p, step.what)...) } for _, row := range append([]struct { name, bucket, key string @@ -648,11 +650,11 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { {"lemurs's want again", buckets.Wants, "want.n1.lemurs", want, false}, {"the node's own want again", buckets.Wants, "want.n1.node", nodeWant, false}, {"lemurs's want with other words", buckets.Wants, "want.n1.lemurs", worded, false}, - {"the placement again", buckets.Placement, "placement.n1", place, false}, + {"the assignment again", buckets.Assignments, "n1.node", place["n1.node"], false}, {"lemurs's module again", buckets.Modules, "lemurs", mod, false}, {"lemurs's want at another build", buckets.Wants, "want.n1.lemurs", elsewhere, true}, {"lemurs's want back at its build", buckets.Wants, "want.n1.lemurs", want, true}, - }, placementRows...) { + }, assignmentRows...) { before := passes() b.put(t, row.bucket, row.key, row.value) if !row.pass { @@ -698,18 +700,18 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { if err != nil { t.Fatal(err) } - tplace, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + tplace := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "tool"}}}) tmod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: next2, Manifest: tdigest, Repository: "novox/t", Commit: "c"}) twant, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: next2, Self: true}) building, _ := builds.EncodeBuilderBuild(builds.BuilderBuild{State: "building"}) bkey, _ := builds.BuilderBuildKey("tool", next2) tb.kv[builds.BuilderBuildsBucket+"/"+bkey] = building - tb.put(t, buckets.Placement, "placement.n1", tplace) + tb.putAssigned(t, tplace) tb.put(t, buckets.Have, "n1.node", have) tb.put(t, buckets.Modules, "tool", tmod) tb.put(t, buckets.Wants, "want.n1.tool", twant) - for _, bucket := range []string{buckets.Wants, buckets.Placement, buckets.Have, buckets.Modules, + for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { tb.send(bucket, agent.Update{Caught: true}) } @@ -777,18 +779,19 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { b.kv[buckets.Manifests+"/"+kkey] = kman b.mu.Unlock() kmod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: kdigest, Repository: "novox/k", Commit: "c"}) - both, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + both := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "oldie"}, {Module: "keeper"}}}) b.put(t, buckets.Modules, "keeper", kmod) - // n1 is held in keys per module and node since the rows above, which a legacy key no longer moves. - bothPlaced, _ := meshmoduleassignerclient.DecodePlacement(both) - bothWrites, err := placeKeys.Writes("n1", &bothPlaced) + // n1 is held in keys per module and node, which the writes below move. + bothPlaced, _ := meshmoduleassignerclient.AssignmentsOf(both) + bothNode := bothPlaced["n1"] + bothWrites, err := placeKeys.Writes("n1", &bothNode) if err != nil { t.Fatal(err) } for _, w := range bothWrites { if w.Value != nil { - b.put(t, buckets.Placement, w.Key, w.Value) + b.put(t, buckets.Assignments, w.Key, w.Value) } } b.put(t, buckets.Wants, "want.n1.keeper", want) @@ -889,8 +892,8 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { for k, v := range c.wants { again.wants[k] = v } - for k, v := range c.placements { - again.placements[k] = v + for k, v := range c.assignments { + again.assignments[k] = v } for k, v := range c.records { again.records[k] = v @@ -912,7 +915,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { // Then the catalogue no longer places the node: it composes nothing of its own, and owes no pass either, so // no timer is armed again (rev-seed-same-3, round 1). again.mu.Lock() - clear(again.placements) + clear(again.assignments) again.mu.Unlock() if again.compose(ctx); again.owed.Load() { t.Errorf("%s: an unplaced node still owes a pass", row.name) @@ -1034,17 +1037,17 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { if asked, _ := json.Marshal(sent["credentials"]); !strings.Contains(string(asked), `"role":"consumer"`) || !strings.Contains(string(asked), `"role":"provider"`) { t.Errorf("the vault was asked %s", asked) } - // ADR 0559 §4: the node answers placement candidates in shadow, placed or not, applying nothing and handing no - // composition: the same; a module the group adds, its secret not yet issued; a module removed; a placement it cannot + // ADR 0559 §4: the node answers assignment candidates in shadow, placed or not, applying nothing and handing no + // composition: the same; a module the group adds, its secret not yet issued; a module removed; a assignment it cannot // compose; a group whose keys went is no longer answered. What runs is never composed from a candidate: the // Composer does not read the state. for _, in := range New(env).inputs() { - if in.bucket == buckets.PlacementCandidates { - t.Fatal("the node's own composer reads placement candidates") + if in.bucket == buckets.ProposalCandidates { + t.Fatal("the node's own composer reads assignment candidates") } } sb := newPatternBus() - trunk := meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}} + trunk := meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}} mods := map[string]meshcatalog.ModulesByModuleValue{"lemurs": {Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}, "keeper": {Fp2: fp, Manifest: kdigest, Repository: "novox/k", Commit: "c"}} readOnly := &fakeBus{kv: map[string][]byte{buckets.Manifests + "/" + key: man, buckets.Manifests + "/" + kkey: kman}} @@ -1058,7 +1061,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { return json.RawMessage(`{"node":"n1","key":"sk","own":[]}`), nil // nothing issued for keeper: it is not placed here } // What n1 runs: lemurs and its own composition, as its composer composes them from the trunk. - running, _, _, err := frombus.Compose(ctx, fixedReader{placements: map[string]meshmoduleassigner.Placement{"n1": trunk}, + running, _, _, err := frombus.Compose(ctx, fixedReader{assignments: map[string]meshmoduleassigner.Assignments{"n1": trunk}, records: map[string]nodeengine.NodeRecord{"n1": {Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}, modules: mods, manifests: map[string][]byte{digest: []byte(lemurs), kdigest: []byte(keeper)}}, frombus.Mesh{}, frombus.Scope{Node: "n1"}, map[string]string{"lemurs": fp}) if err != nil { @@ -1071,7 +1074,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { } return out } - // The controller's compositions at n1's wants, in n1's composition bucket: what a head deleting its placement + // The controller's compositions at n1's wants, in n1's composition bucket: what a head deleting its assignment // returns it to (the seed's revert); here the ones it runs. composedBucket, _ := compositions.BucketFor("n1") for key, c := range running { @@ -1089,15 +1092,15 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { defer scancel() // Review of L2b, round 1, finding 2: the candidates' state is not there when the node starts (a delivery before the // indexer made it): the shadow tries again, and answers once it appears. - sb.missing(buckets.PlacementCandidates, true) + sb.missing(buckets.ProposalCandidates, true) kept := make(chan struct{}) go func() { sh.Keep(sctx, func(int) time.Duration { return 5 * time.Millisecond }); close(kept) }() time.Sleep(30 * time.Millisecond) - sb.missing(buckets.PlacementCandidates, false) + sb.missing(buckets.ProposalCandidates, false) sb.made(6) rec, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) - tp, _ := meshmoduleassignerclient.EncodePlacement(trunk) - sb.put(buckets.Placement, "placement.n1", tp, 1) + tp := assignedValues(t, trunk) + sb.putAssigned(tp, 1) sb.put(buckets.Have, "n1.node", rec, 1) for name, m := range mods { raw, _ := meshcatalogclient.EncodeModule(m) @@ -1124,33 +1127,33 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { for _, m := range modules { p.Modules = append(p.Modules, meshmoduleassigner.PlacedModule{Module: m}) } - raw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: group, Placement: &p}) + raw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: group, Assignments: &p}) if err != nil { t.Fatal(err) } k := meshmoduleassigner.CandidatesByProposalNodeKey(group, "n1") - sb.put(buckets.PlacementCandidates, k, raw, rev) + sb.put(buckets.ProposalCandidates, k, raw, rev) } candidate("same", 3, "lemurs") candidate("adds", 4, "lemurs", "keeper") candidate("drops", 5) candidate("ghost", 6, "lemurs", "ghost") - // A head deleting n1's placement file (the seed's revert, mesh-catalog #526): n1 returns to the controller's + // A head deleting n1's assignment file (the seed's revert, mesh-catalog #526): n1 returns to the controller's // compositions, answered as such, not as every module removed. if raw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "deletes"}); err != nil { t.Fatal(err) } else { k := meshmoduleassigner.CandidatesByProposalNodeKey("deletes", "n1") - sb.put(buckets.PlacementCandidates, k, raw, 7) + sb.put(buckets.ProposalCandidates, k, raw, 7) } // A proposal that changes another node's key and reaches n1's composition (ADR 0567 §3): n1's candidate holds no - // placement of its own change (Reached, nil), and n1 answers against its current placement, never as one whose key + // assignment of its own change (Reached, nil), and n1 answers against its current assignment, never as one whose key // the proposal takes away. if raw, err := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "reach", Reached: true}); err != nil { t.Fatal(err) } else { k := meshmoduleassigner.CandidatesByProposalNodeKey("reach", "n1") - sb.put(buckets.PlacementCandidates, k, raw, 8) + sb.put(buckets.ProposalCandidates, k, raw, 8) } sb.caught() var answers map[string]nodeengine.CandidateAnswer @@ -1199,7 +1202,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { return a.Verdict == nodeengine.CandidateAnswerVerdictSame && a.Returns == "" && a.Revision == 8 && len(a.Removed) == 0 && fmt.Sprint(a.Same) == "[lemurs node]" }, - // Issue 1126, A7: a candidate deleting the node's placement is refused, never answered as a return to the + // Issue 1126, A7: a candidate deleting the node's assignment is refused, never answered as a return to the // controller's compositions: no composition made elsewhere is applied, so the node would compose nothing. "deletes": func(a nodeengine.CandidateAnswer) bool { return a.Verdict == nodeengine.CandidateAnswerVerdictRefused && a.Returns == "" && a.Revision == 7 && @@ -1218,7 +1221,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { case <-time.After(100 * time.Millisecond): } // Issue 1126, 2026-10-28 (shanks answered probe/1126-seed-7 at revision 84 every 13-17 s): a candidate revision is - // answered once. A module's have whose health or words alone move (each look rewrites them), and a placement, a + // answered once. A module's have whose health or words alone move (each look rewrites them), and a assignment, a // module or a config value written again with the value it held, answer nothing again; a new revision answers its // group alone, and what runs moving answers every group once. answering := func() int { @@ -1226,7 +1229,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { defer readOnly.mu.Unlock() n := 0 for _, l := range readOnly.said { - if strings.HasPrefix(l, "answering placement candidate ") { + if strings.HasPrefix(l, "answering assignment candidate ") { n++ } } @@ -1255,7 +1258,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { settled := answering() lemursHave("its tool lemurs_health: no answer within 10s", 11) lemursHave("its tool lemurs_health: no answer within 20s", 12) - sb.put(buckets.Placement, "placement.n1", tp, 2) + sb.putAssigned(tp, 2) lraw, _ := meshcatalogclient.EncodeModule(mods["lemurs"]) sb.put(buckets.Modules, "lemurs", lraw, 2) drain() @@ -1279,7 +1282,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { } return "" } - if got := lastSaid("answering placement candidate same "); got != "answering placement candidate same (revision 8; woken by the candidate)" { + if got := lastSaid("answering assignment candidate same "); got != "answering assignment candidate same (revision 8; woken by the candidate)" { t.Errorf("a new revision's answer said %q", got) } settled = answering() @@ -1290,7 +1293,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { if got := answering(); got != settled+len(answers) { t.Errorf("what runs moved, and %d answer(s) were composed again for %d group(s)", got-settled, len(answers)) } - if got := lastSaid("answering placement candidate drops "); got != "answering placement candidate drops (revision 5; woken by haves)" { + if got := lastSaid("answering assignment candidate drops "); got != "answering assignment candidate drops (revision 5; woken by haves)" { t.Errorf("an answer woken by what runs moving said %q", got) } // A read the bus did not answer is no answer to keep: the group is answered again after a bound, though nothing moved. @@ -1305,41 +1308,41 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { return a["adds"].Revision == 9 && a["adds"].Verdict == nodeengine.CandidateAnswerVerdictDiffers }) drain() - if got := lastSaid("answering placement candidate adds "); got != "answering placement candidate adds (revision 9; woken by a retry after a read the bus did not answer)" { + if got := lastSaid("answering assignment candidate adds "); got != "answering assignment candidate adds (revision 9; woken by a retry after a read the bus did not answer)" { t.Errorf("an answer composed again after the bus did not answer said %q", got) } k := meshmoduleassigner.CandidatesByProposalNodeKey("same", "n1") - sb.del(buckets.PlacementCandidates, k) + sb.del(buckets.ProposalCandidates, k) waitFor("the deleted group's answer stays", func(a map[string]nodeengine.CandidateAnswer) bool { _, ok := a["same"]; return !ok && len(a) == 5 }) // Review of L2b, round 2: a watch ends while the link stays; a group's key is deleted before the shadow watches // again. The new run reads afresh, so that group is no longer answered. - sb.missing(buckets.PlacementCandidates, true) + sb.missing(buckets.ProposalCandidates, true) sb.end(buckets.Modules) dropsKey := meshmoduleassigner.CandidatesByProposalNodeKey("drops", "n1") - sb.del(buckets.PlacementCandidates, dropsKey) + sb.del(buckets.ProposalCandidates, dropsKey) time.Sleep(30 * time.Millisecond) - sb.missing(buckets.PlacementCandidates, false) + sb.missing(buckets.ProposalCandidates, false) waitFor("a group deleted while the shadow was down is still answered", func(a map[string]nodeengine.CandidateAnswer) bool { _, ok := a["drops"] return !ok && len(a) == 4 }) - // Issue 1126, A7 AC1: a placement that cannot be read composes nothing and says why; the node never applies a - // composition made elsewhere instead. (A placement read without the node's key composes nothing either: above.) + // Issue 1126, A7 AC1: a assignment that cannot be read composes nothing and says why; the node never applies a + // composition made elsewhere instead. (A assignment read without the node's key composes nothing either: above.) { refused := newFakeBus() env := refused.env() watch := env.Watch env.Watch = func(ctx context.Context, bucket, pattern string) (<-chan agent.Update, error) { - if bucket == buckets.Placement { + if bucket == buckets.Assignments { return nil, fmt.Errorf("nats: Permissions Violation for Subscription to %s", bucket) } return watch(ctx, bucket, pattern) } out, err := New(env).Watch(ctx, "") - if out != nil || err == nil || !strings.Contains(err.Error(), buckets.Placement) || !strings.Contains(err.Error(), "cannot be watched") { - t.Errorf("an unreadable placement: composed %v, said %v", out != nil, err) + if out != nil || err == nil || !strings.Contains(err.Error(), buckets.Assignments) || !strings.Contains(err.Error(), "cannot be watched") { + t.Errorf("an unreadable assignment: composed %v, said %v", out != nil, err) } } @@ -1359,20 +1362,20 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { defer ncancel() go func() { _ = nsh.Run(nctx) }() nb.made(6) - nb.put(buckets.Placement, "placement.n1", tp, 1) + nb.putAssigned(tp, 1) nb.put(buckets.Have, "n1.node", rec, 1) for name, m := range mods { raw, _ := meshcatalogclient.EncodeModule(m) nb.put(buckets.Modules, name, raw, 1) } - sameRaw, _ := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "same", Placement: &trunk}) + sameRaw, _ := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "same", Assignments: &trunk}) sameKey := meshmoduleassigner.CandidatesByProposalNodeKey("same", "n1") - nb.put(buckets.PlacementCandidates, sameKey, sameRaw, 3) + nb.put(buckets.ProposalCandidates, sameKey, sameRaw, 3) // Issue 1126, T3: an older group (a closed pull request's candidate, rev 2) is answered after the newest. - oldRaw, _ := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "older", Placement: &trunk}) + oldRaw, _ := bucketstest.Candidate(meshmoduleassigner.CandidatesByProposalNodeValue{Proposal: "older", Assignments: &trunk}) oldKey := meshmoduleassigner.CandidatesByProposalNodeKey("older", "n1") - nb.put(buckets.PlacementCandidates, oldKey, oldRaw, 2) - for _, bucket := range []string{buckets.PlacementCandidates, buckets.Placement, buckets.Have, buckets.Modules, builds.BuilderBuildsBucket} { + nb.put(buckets.ProposalCandidates, oldKey, oldRaw, 2) + for _, bucket := range []string{buckets.ProposalCandidates, buckets.Assignments, buckets.Have, buckets.Modules, builds.BuilderBuildsBucket} { nb.send(bucket, agent.Update{Caught: true}) } select { @@ -1384,7 +1387,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { t.Fatal("a shadow not ready after its wait answered nothing") } nmu.Lock() - if !strings.Contains(strings.Join(nsaid, "\n"), "this node answers no placement candidate yet: the node has not read "+ResolvedBucket) { + if !strings.Contains(strings.Join(nsaid, "\n"), "this node answers no assignment candidate yet: the node has not read "+ResolvedBucket) { t.Errorf("a shadow not ready said %q", nsaid) } nmu.Unlock() @@ -1408,13 +1411,13 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { // Issue 1126, T2: an answer says when it starts and when it ends (and, every minute between, what it waits on). nmu.Lock() said := strings.Join(nsaid, "\n") - for _, line := range []string{"the answer to same took", "answering placement candidate same (revision 3; woken by its first answer since the shadow began)", - "answered placement candidate same in", "answering placement candidate older (revision 2;"} { + for _, line := range []string{"the answer to same took", "answering assignment candidate same (revision 3; woken by its first answer since the shadow began)", + "answered assignment candidate same in", "answering assignment candidate older (revision 2;"} { if !strings.Contains(said, line) { t.Errorf("%q was not said: %q", line, nsaid) } } - if strings.Index(said, "answering placement candidate same") > strings.Index(said, "answering placement candidate older") { + if strings.Index(said, "answering assignment candidate same") > strings.Index(said, "answering assignment candidate older") { t.Errorf("the older group was answered first: %q", nsaid) } nmu.Unlock() @@ -1454,7 +1457,7 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { // trunk's. inOwnRunning := []compositions.Composed{{Node: "n1", Module: "lemurs", FP: fp}} other := "src2:" + strings.Repeat("9", 64) - gotFps := shadowBuilds(inOwnRunning, meshmoduleassigner.Placement{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "nats"}, {Module: "keeper"}}}, + gotFps := shadowBuilds(inOwnRunning, meshmoduleassigner.Assignments{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "nats"}, {Module: "keeper"}}}, map[string]meshcatalog.ModulesByModuleValue{"lemurs": {Fp2: other}, "nats": {Fp2: other}, "keeper": {Fp2: other}}, []string{"nats"}) if fmt.Sprint(gotFps) != fmt.Sprint(map[string]string{"lemurs": fp, "keeper": other}) { t.Errorf("shadow builds %v; want lemurs at what runs, keeper at the trunk's, nats at none (folded)", gotFps) @@ -1519,12 +1522,12 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { cc.manifests[cdigest] = []byte(withCert) cc.modules = map[string]meshcatalog.ModulesByModuleValue{"lemurs": {Manifest: digest}, "web": {Manifest: cdigest}} cc.records["n1"] = nodeengine.NodeRecord{Node: "n1", ServingKey: base64.StdEncoding.EncodeToString(make([]byte, 32))} - onlyLemurs := meshmoduleassigner.Placement{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}} + onlyLemurs := meshmoduleassigner.Assignments{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}} cc.certifyIfWanted(context.Background(), reader{c: cc}, onlyLemurs) if certAsks != 0 { t.Errorf("no module asks for a certificate, and the vault was asked %d times", certAsks) } - withWeb := meshmoduleassigner.Placement{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "web"}}} + withWeb := meshmoduleassigner.Assignments{Node: "n1", Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}, {Module: "web"}}} cc.certifyIfWanted(context.Background(), reader{c: cc}, withWeb) cc.certifyIfWanted(context.Background(), reader{c: cc}, withWeb) if certAsks != 1 { @@ -1665,14 +1668,14 @@ func (b *patternBus) caught() { // fixedReader is frombus's Reader over values held, for composing what a node runs. type fixedReader struct { - placements map[string]meshmoduleassigner.Placement - records map[string]nodeengine.NodeRecord - modules map[string]meshcatalog.ModulesByModuleValue - manifests map[string][]byte + assignments map[string]meshmoduleassigner.Assignments + records map[string]nodeengine.NodeRecord + modules map[string]meshcatalog.ModulesByModuleValue + manifests map[string][]byte } -func (r fixedReader) Placements(context.Context) (map[string]meshmoduleassigner.Placement, error) { - return r.placements, nil +func (r fixedReader) Assignments(context.Context) (map[string]meshmoduleassigner.Assignments, error) { + return r.assignments, nil } func (r fixedReader) Records(context.Context) (map[string]nodeengine.NodeRecord, error) { return r.records, nil @@ -1791,18 +1794,18 @@ func TestAnUnreadSeatListHoldsWhatIsNewAndHandsItAtTheRead(t *testing.T) { if err != nil { t.Fatal(err) } - place, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}}) have, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) want, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true}) nodeWant, _ := bucketstest.NodeWant(meshwants.WantsByWantNodeNodeValue{Self: true}) - b.put(t, buckets.Placement, "placement.n1", place) + b.putAssigned(t, place) b.put(t, buckets.Have, "n1.node", have) b.put(t, buckets.Modules, "lemurs", mod) b.put(t, buckets.Wants, "want.n1.lemurs", want) b.put(t, buckets.Wants, "want.n1.node", nodeWant) - for _, bucket := range []string{buckets.Wants, buckets.Placement, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { + for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { b.send(bucket, agent.Update{Caught: true}) } handed := map[string]bool{} @@ -1872,18 +1875,18 @@ func TestAHeldOwnCompositionThatWasHandedIsNotDropped(t *testing.T) { if err != nil { t.Fatal(err) } - place, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, + place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: []meshmoduleassigner.PlacedModule{{Module: "lemurs"}}}) have, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) want, _ := bucketstest.Want(meshwants.WantsByWantNodeModuleValue{Fp: fp, Self: true}) nodeWant, _ := bucketstest.NodeWant(meshwants.WantsByWantNodeNodeValue{Self: true}) - b.put(t, buckets.Placement, "placement.n1", place) + b.putAssigned(t, place) b.put(t, buckets.Have, "n1.node", have) b.put(t, buckets.Modules, "lemurs", mod) b.put(t, buckets.Wants, "want.n1.lemurs", want) b.put(t, buckets.Wants, "want.n1.node", nodeWant) - for _, bucket := range []string{buckets.Wants, buckets.Placement, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { + for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { b.send(bucket, agent.Update{Caught: true}) } // The list is read: the node's own composition is handed. Then the reader cannot act again (it restarted and has @@ -1999,13 +2002,13 @@ func TestTheHolderOfTheCatalogIsStartedFromTheOwnCompositionBeforeTheListIsReadA b.put(t, buckets.Have, hk, hv) placed = append(placed, meshmoduleassigner.PlacedModule{Module: m}) } - place, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: placed}) + place := assignedValues(t, meshmoduleassigner.Assignments{Node: "n1", Decided: meshmoduleassigner.NodeDecided{Hub: true, Address: "10.77.0.1"}, Modules: placed}) have, _ := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: "n1", TunnelKey: "k", SealingKey: "sk", Capabilities: []string{"systemd"}}}) nodeWant, _ := bucketstest.NodeWant(meshwants.WantsByWantNodeNodeValue{Self: true}) - b.put(t, buckets.Placement, "placement.n1", place) + b.putAssigned(t, place) b.put(t, buckets.Have, "n1.node", have) b.put(t, buckets.Wants, "want.n1.node", nodeWant) - for _, bucket := range []string{buckets.Wants, buckets.Placement, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { + for _, bucket := range []string{buckets.Wants, buckets.Assignments, buckets.Have, buckets.Modules, ResolvedBucket, builds.BuilderBuildsBucket} { b.send(bucket, agent.Update{Caught: true}) } servicesIn := func(u agent.Update) []string { @@ -2056,3 +2059,28 @@ func TestACompositionHoldsWhenTheSeatListReaderCouldNotStart(t *testing.T) { t.Errorf("held %v, comps %v: a reader that did not start must hold the node's own composition", held, comps) } } + +// assignedValues is every key the assigner writes for p: the node's, `.node`, and each module's, `.`. +func assignedValues(t *testing.T, p meshmoduleassigner.Assignments) map[string][]byte { + t.Helper() + vals, err := meshmoduleassignerclient.AssignmentsValues(p) + if err != nil { + t.Fatal(err) + } + return vals +} + +// putAssigned writes the keys of one node's assignments into the assignments state. +func (b *fakeBus) putAssigned(t *testing.T, vals map[string][]byte) { + t.Helper() + for _, k := range slices.Sorted(maps.Keys(vals)) { + b.put(t, buckets.Assignments, k, vals[k]) + } +} + +// putAssigned sends the keys of one node's assignments, each at the revision. +func (b *patternBus) putAssigned(vals map[string][]byte, rev uint64) { + for _, k := range slices.Sorted(maps.Keys(vals)) { + b.put(buckets.Assignments, k, vals[k], rev) + } +} diff --git a/internal/selfcompose/shadow.go b/internal/selfcompose/shadow.go index abc73a1..4e8e466 100644 --- a/internal/selfcompose/shadow.go +++ b/internal/selfcompose/shadow.go @@ -6,10 +6,10 @@ import ( "encoding/json" "fmt" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "github.com/novox/mesh-host/internal/buckets" "github.com/novox/mesh-host/internal/compose/frombus" engcomp "github.com/novox/mesh-host/internal/compositions" @@ -23,13 +23,13 @@ import ( "github.com/novox/mesh-host/internal/agent" ) -// Shadow is the node's answer to placement candidates (novox/hq ADR 0559 §4, issue 1126): for every proposal with a key in -// the placement seat's state `candidates`, it composes this node in shadow from the trunk's placement with the proposal's +// Shadow is the node's answer to assignment candidates (novox/hq ADR 0559 §4, issue 1126): for every proposal with a key in +// the assignment seat's state `candidates`, it composes this node in shadow from the trunk's assignment with the proposal's // keys laid over it and its own inputs (the sealed values and boxes it holds among them), compares the result with what // the node runs, and hands the answers to be written into the node's own have key, `.node`, under `candidates`. -// It applies nothing and hands no composition: what runs is still composed from `placement` alone, by the Composer. +// It applies nothing and hands no composition: what runs is still composed from `assignment` alone, by the Composer. // -// **Event-driven, like the Composer.** It watches every candidate key, the trunk's placement, every node's record, the +// **Event-driven, like the Composer.** It watches every candidate key, the trunk's assignment, every node's record, the // indexer's modules, this node's config values, and this node's module haves (what it runs moved); a burst of changes // answers once. It runs whether or not the node is placed yet, against what it runs. An answer is handed only when it changes, and a proposal's answer is dropped when its // keys go. @@ -72,7 +72,7 @@ type answeredAt struct { } // inputWords name the shadow's inputs, in Run's order, as an answer says which of them woke it. -var inputWords = [...]string{"the candidate", "placement", "records", "modules", "values", "haves"} +var inputWords = [...]string{"the candidate", "assignment", "records", "modules", "values", "haves"} // wokenBy says what moved between the answer a proposal stands at (had, when done) and the one about to be composed (at): // its first answer, a retry, its candidate's revisions, or the inputs that changed, by their words. @@ -130,7 +130,7 @@ func (s *Shadow) Keep(ctx context.Context, backoff func(try int) time.Duration) why = err.Error() } if why != said { - s.c.env.Say("this node answers no placement candidate for now, and tries again: " + why) + s.c.env.Say("this node answers no assignment candidate for now, and tries again: " + why) said = why } select { @@ -161,8 +161,8 @@ func (s *Shadow) Run(ctx context.Context) error { node := c.env.Node // In inputWords' order. ins := []input{ - {buckets.PlacementCandidates, ">", nil, nil}, - {buckets.Placement, ">", (*Composer).takePlacement, nil}, + {buckets.ProposalCandidates, ">", nil, nil}, + {buckets.Assignments, ">", (*Composer).takeAssignments, nil}, {buckets.Have, "*." + nodeengineclient.NodeWord, nil, nil}, {buckets.Modules, ">", (*Composer).takeModule, nil}, {ResolvedBucket, node + ".>", (*Composer).takeValue, nil}, @@ -178,7 +178,7 @@ func (s *Shadow) Run(ctx context.Context) error { for i, in := range ins { ch, err := c.env.Watch(ctx, in.bucket, in.pattern) if err != nil { - return fmt.Errorf("the node answers placement candidates from %s, which cannot be watched: %w", in.bucket, err) + return fmt.Errorf("the node answers assignment candidates from %s, which cannot be watched: %w", in.bucket, err) } go func(i int, ch <-chan agent.Update) { for u := range ch { @@ -216,7 +216,7 @@ func (s *Shadow) Run(ctx context.Context) error { continue case t := <-merged: if !t.ok { - return fmt.Errorf("the watch of %s ended; the node answers no placement candidate until its agent starts again", ins[t.in].bucket) + return fmt.Errorf("the watch of %s ended; the node answers no assignment candidate until its agent starts again", ins[t.in].bucket) } // Only a change of what an answer is composed from answers again: a key written again with the value it held, // this node's own key (its answers), or a module's have whose health or words moved but not what it runs, is @@ -258,7 +258,7 @@ func (s *Shadow) Run(ctx context.Context) error { if s.owed { wait := LaterWait(tries) tries++ - c.env.Say(fmt.Sprintf("this node answers again in %s the placement candidates whose answer met a bus that did not answer", wait)) + c.env.Say(fmt.Sprintf("this node answers again in %s the assignment candidates whose answer met a bus that did not answer", wait)) again = time.After(wait) } else { tries, again = 0, nil @@ -300,7 +300,7 @@ func (s *Shadow) takeCandidate(u agent.Update) bool { return true } c, err := meshmoduleassigner.DecodeCandidatesByProposalNodeValue(u.Value) - if err != nil || c.Proposal != proposal || (c.Placement != nil && c.Placement.Node != node) { + if err != nil || c.Proposal != proposal || (c.Assignments != nil && c.Assignments.Node != node) { // Not one: read as gone. if _, ok := s.candidates[proposal][node]; ok { delete(s.candidates[proposal], node) @@ -345,7 +345,7 @@ func (s *Shadow) notReady(ctx context.Context, ins []input, caught []bool) { } } why := fmt.Sprintf("the node has not read %s after %s; it answers once it has", strings.Join(waits, ", "), notReadyAfter) - s.c.env.Say("this node answers no placement candidate yet: " + why) + s.c.env.Say("this node answers no assignment candidate yet: " + why) s.mu.Lock() next := map[string]nodeengine.CandidateAnswer{} for proposal, keys := range s.candidates { @@ -428,7 +428,7 @@ func (s *Shadow) answerAll(ctx context.Context) { given[g] = a } s.answer(ctx, given) - c.env.Say(fmt.Sprintf("%s answered %d placement candidate proposal(s)", node, len(given))) + c.env.Say(fmt.Sprintf("%s answered %d assignment candidate proposal(s)", node, len(given))) } } for _, l := range proposals { @@ -444,7 +444,7 @@ func (s *Shadow) answerAll(ctx context.Context) { continue } began := time.Now() - c.env.Say(fmt.Sprintf("answering placement candidate %s (revision %d; woken by %s)", proposal, l.rev, wokenBy(at, done, retry, l.at))) + c.env.Say(fmt.Sprintf("answering assignment candidate %s (revision %d; woken by %s)", proposal, l.rev, wokenBy(at, done, retry, l.at))) stop := make(chan struct{}) go func() { tick := time.NewTicker(progressEvery) @@ -474,10 +474,10 @@ func (s *Shadow) answerAll(ctx context.Context) { if ok { a = judge.FitAnswer(a, engcomp.MaxAnswerBytes) next[proposal] = a - c.env.Say(fmt.Sprintf("answered placement candidate %s in %s: %s", proposal, took.Round(time.Millisecond), a.Verdict)) + c.env.Say(fmt.Sprintf("answered assignment candidate %s in %s: %s", proposal, took.Round(time.Millisecond), a.Verdict)) } else { delete(next, proposal) - c.env.Say(fmt.Sprintf("placement candidate %s does not place this node: nothing to answer", proposal)) + c.env.Say(fmt.Sprintf("assignment candidate %s does not place this node: nothing to answer", proposal)) } s.mu.Lock() if provisional { @@ -507,12 +507,12 @@ func (s *Shadow) answerProposal(ctx context.Context, proposal string) (nodeengin defer c.waiting("composing")() s.mu.Lock() keys := s.candidates[proposal] - given := make(map[string]*meshmoduleassigner.Placement, len(keys)) + given := make(map[string]*meshmoduleassigner.Assignments, len(keys)) for n, k := range keys { - if k.c.Reached && k.c.Placement == nil { + if k.c.Reached && k.c.Assignments == nil { continue // reached, with no key of its own: nothing laid over it } - given[n] = k.c.Placement + given[n] = k.c.Assignments } // The revision answered is the node's own candidate key's; a node with none is not asked. own, mine := keys[node] @@ -534,26 +534,26 @@ func (s *Shadow) answerProposal(ctx context.Context, proposal string) (nodeengin running = c.env.Running() } if p, ok := given[node]; ok && p == nil && !own.c.Reached { - // The proposal deletes this node's placement key: the node would compose nothing and keep running what it runs, + // The proposal deletes this node's assignment key: the node would compose nothing and keep running what it runs, // since no composition made elsewhere is applied (issue 1126: the controller ended by its unassignment). Refused, - // so the catalogue does not take a node's placement away by a merge. + // so the catalogue does not take a node's assignment away by a merge. return nodeengine.CandidateAnswer{Proposal: proposal, Revision: int64(rev), Verdict: nodeengine.CandidateAnswerVerdictRefused, - Refused: "the proposal takes this node's placement away: it then composes nothing, and no composition made " + + Refused: "the proposal takes this node's assignment away: it then composes nothing, and no composition made " + "elsewhere is applied, so what runs here would be followed by nothing"}, true } - placement := given[node] - if placement == nil { + assignment := given[node] + if assignment == nil { c.mu.Lock() - p := c.placements[node] + p := c.assignments[node] c.mu.Unlock() - placement = &p + assignment = &p } // What it runs inside its own composition, of what the candidate places (the seed's trunk places nothing). c.mu.Lock() - inOwn := c.inOwnOf(*placement) + inOwn := c.inOwnOf(*assignment) c.mu.Unlock() - fps := shadowBuilds(running, *placement, modules, inOwn) + fps := shadowBuilds(running, *assignment, modules, inOwn) kept := map[string]string{} for module, fp := range fps { if mod, ok := modules[module]; ok && mod.Fp2 == fp { @@ -570,7 +570,7 @@ func (s *Shadow) answerProposal(ctx context.Context, proposal string) (nodeengin c.mu.Lock() c.wants = held c.mu.Unlock() - a := frombus.Answer(ctx, reader{c: c}, c.env.Mesh, frombus.Scope{Node: node, KeptAt: kept, Placements: given, InOwn: inOwn, + a := frombus.Answer(ctx, reader{c: c}, c.env.Mesh, frombus.Scope{Node: node, KeptAt: kept, Assignments: given, InOwn: inOwn, SameValue: c.sameValue, HeldToday: s.heldToday()}, fps, running, proposal, rev) s.mu.Lock() ran := make(map[string]string, len(s.ran)) @@ -664,7 +664,7 @@ func asHavesSay(a nodeengine.CandidateAnswer, ran map[string]string) nodeengine. // of the old form) is given none, so it is folded there again, as the node's own composer folds it, rather than // composed at the trunk's build as if the candidate added it (issue 1126: nats and photos have a trunk build that no // want names). -func shadowBuilds(running []engcomp.Composed, placement meshmoduleassigner.Placement, modules map[string]meshcatalog.ModulesByModuleValue, inOwn []string) map[string]string { +func shadowBuilds(running []engcomp.Composed, assignment meshmoduleassigner.Assignments, modules map[string]meshcatalog.ModulesByModuleValue, inOwn []string) map[string]string { fps := map[string]string{} for _, r := range running { if r.Module != "" && r.FP != "" { @@ -675,7 +675,7 @@ func shadowBuilds(running []engcomp.Composed, placement meshmoduleassigner.Place for _, m := range inOwn { folded[m] = true } - for _, m := range placement.Modules { + for _, m := range assignment.Modules { if _, runs := fps[m.Module]; !runs && !folded[m.Module] { if mod, ok := modules[m.Module]; ok && mod.Fp2 != "" { fps[m.Module] = mod.Fp2 diff --git a/testdata/beside/mesh-networking/modules/route-proxy/module.json.captured b/testdata/beside/mesh-networking/modules/route-proxy/module.json.captured index 668ab88..b4055ab 100644 --- a/testdata/beside/mesh-networking/modules/route-proxy/module.json.captured +++ b/testdata/beside/mesh-networking/modules/route-proxy/module.json.captured @@ -265,7 +265,7 @@ ] }, "reads": [ - "mesh-delivery-assigner.placement", + "mesh-delivery-assigner.assignments", "mesh-provisioner.plans", "mesh-provisioner.provisions" ]