diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits.go index 4b7f7095..13a6e22d 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits.go @@ -54,7 +54,7 @@ func (p *passer) entryEdges(node, module string) []seat.Edge { // actingReleased is the waits released by hand and not yet run, by `@`. func (p *passer) actingReleased() map[string]bool { out := map[string]bool{} - for key, raw := range p.w.read(buckets.Waits) { + for key, raw := range p.w.read(buckets.Waits()) { if w, err := acting.Decode(raw); err == nil && w.Released && (acting.Is(key) || acting.IsOrder(key)) { out[key+"@"+w.Fp] = true } @@ -66,11 +66,11 @@ func (p *passer) actingReleased() map[string]bool { // wait of one that ran, was replaced or was released, and says each. func (p *passer) actingWaits(ctx context.Context, n assign.Nodes, wanted map[string]map[string]meshwants.Want) { g := p.graph - if g == nil || p.kvs[buckets.Waits] == nil { + if g == nil || p.kvs[buckets.Waits()] == nil { return } now := p.clock() - kept := p.w.read(buckets.Waits) + kept := p.w.read(buckets.Waits()) standing := map[string]bool{} for _, ref := range g.computed.Revision.Entries { entry := g.computed.Entries[ref.Digest] @@ -97,11 +97,11 @@ func (p *passer) actingWaits(ctx context.Context, n assign.Nodes, wanted map[str } w := acting.Wait{Step: e.Step, Node: ref.Node, Module: ref.Module, Part: e.Module, PartNode: e.Node, Fp: e.Fp, Entry: entry.Fp, Since: now, ClockFrom: now, Bound: acting.BoundOf(e).String()} - if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module), key+"."+e.Fp+"."+entry.Fp); err != nil { + if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module()), key+"."+e.Fp+"."+entry.Fp); err != nil { p.logf("the wait of %s on %s for %s is not said, and is said at the next pass: %v", ref.Module, ref.Node, e.Module, err) continue } - p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return acting.Encode(w) }) + p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return acting.Encode(w) }) } } for key, raw := range kept { @@ -119,14 +119,14 @@ func (p *passer) actingWaits(ctx context.Context, n assign.Nodes, wanted map[str how = acting.HowReplaced } if !w.EndSaid { - body := acting.Body(w, assignment.Module) + body := acting.Body(w, assignment.Module()) body.How = how if err := p.wantsSay(seat.EventWaitEnded, body, key+"."+w.Fp+"."+w.Entry+"."+how); err != nil { p.logf("the end of the wait of %s on %s for %s is not said, and is said at the next pass: %v", w.Module, w.Node, w.Part, err) continue } } - p.remove(ctx, buckets.Waits, key) + p.remove(ctx, buckets.Waits(), key) } } @@ -135,7 +135,7 @@ func (p *passer) actingReleaseSaid(ctx context.Context, key string, w acting.Wai if !w.Released || w.EndSaid { return } - body := acting.Body(w, assignment.Module) + body := acting.Body(w, assignment.Module()) body.How = acting.HowReleased body.By = w.By if err := p.wantsSay(seat.EventWaitEnded, body, key+"."+w.Fp+"."+w.Entry+"."+acting.HowReleased); err != nil { @@ -143,7 +143,7 @@ func (p *passer) actingReleaseSaid(ctx context.Context, key string, w acting.Wai return } w.EndSaid = true - p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return acting.Encode(w) }) + p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return acting.Encode(w) }) } // edgeOf is the edge a kept wait stands on, as an acting edge. diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits_test.go index 3ee22779..188bce6f 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/actingwaits_test.go @@ -21,7 +21,7 @@ import ( // `now` and ended when the version runs. func TestAnEntryWaitsForItsActingPartAndTheWaitIsKeptSaidAndEnded(t *testing.T) { now := time.Date(2026, 11, 12, 9, 0, 0, 0, time.UTC) - p := newPasser(kvSet{buckets.Waits: &memKV{values: map[string][]byte{}}}, now) + p := newPasser(kvSet{buckets.Waits(): &memKV{values: map[string][]byte{}}}, now) var events []string var bodies []meshwants.Wait p.publishWants = func(event string, body any, id string) error { @@ -52,14 +52,14 @@ func TestAnEntryWaitsForItsActingPartAndTheWaitIsKeptSaidAndEnded(t *testing.T) t.Errorf("wait-held %+v", b) } key := acting.Key("ace", "nats", "mesh-host") - if _, err := acting.Decode(p.w.read(buckets.Waits)[key]); err != nil { + if _, err := acting.Decode(p.w.read(buckets.Waits())[key]); err != nil { t.Fatalf("the wait is kept: %v", err) } // a release by hand lets the entry move, and ends the wait once - w, _ := acting.Decode(p.w.read(buckets.Waits)[key]) + w, _ := acting.Decode(p.w.read(buckets.Waits())[key]) w.Released = true raw, _ := acting.Encode(w) - p.w.put(buckets.Waits, key, raw) + p.w.put(buckets.Waits(), key, raw) if got := p.actingOf("nats", p.actingReleased())("ace"); len(got) != 0 { t.Errorf("a released wait still holds: %+v", got) } @@ -71,13 +71,13 @@ func TestAnEntryWaitsForItsActingPartAndTheWaitIsKeptSaidAndEnded(t *testing.T) // the version runs: the record goes, said nothing more n.Running["ace"]["mesh-host"] = nodeengine.Running{Fingerprint: "src2:new"} p.actingWaits(ctx, n, wanted) - if len(p.w.read(buckets.Waits)) != 0 || len(events) != 2 { - t.Errorf("kept %v events %v", p.w.read(buckets.Waits), events) + if len(p.w.read(buckets.Waits())) != 0 || len(events) != 2 { + t.Errorf("kept %v events %v", p.w.read(buckets.Waits()), events) } } func TestAnActingWaitOfANodeAwayStartsNoClockAndAnEntryWantedAlreadyWaitsNot(t *testing.T) { - p := newPasser(kvSet{buckets.Waits: &memKV{values: map[string][]byte{}}}, time.Now()) + p := newPasser(kvSet{buckets.Waits(): &memKV{values: map[string][]byte{}}}, time.Now()) p.publishWants = func(string, any, string) error { t.Fatal("said"); return nil } p.graph = &graphKeeper{} p.graph.computed.Revision.Entries = []meshwants.NodeEntryRef{{Node: "ace", Module: "nats", Digest: "d1"}} @@ -87,8 +87,8 @@ func TestAnActingWaitOfANodeAwayStartsNoClockAndAnEntryWantedAlreadyWaitsNot(t * p.actingWaits(context.Background(), n, map[string]map[string]mwc.Want{}) n.Online["ace"] = true p.actingWaits(context.Background(), n, map[string]map[string]mwc.Want{"nats": {"ace": {Fp: "src2:a"}}}) - if len(p.w.read(buckets.Waits)) != 0 || strings.Contains(p.graph.computed.Entries["d1"].Fp, "x") { - t.Errorf("kept %v", p.w.read(buckets.Waits)) + if len(p.w.read(buckets.Waits())) != 0 || strings.Contains(p.graph.computed.Entries["d1"].Fp, "x") { + t.Errorf("kept %v", p.w.read(buckets.Waits())) } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askended.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askended.go index ef2e4617..3573bc89 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askended.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askended.go @@ -48,7 +48,7 @@ func staleOfWorld(w *world) staleOf { func hearAskEnded(ctx context.Context, js jetstream.JetStream, hub *process.EventHub, asksKV jetstream.KeyValue, stale staleOf, logf func(string, ...any)) error { return hub.Handle(askEndedSubject, func(m jetstream.Msg) { var putsKV jetstream.KeyValue - if kv, err := js.KeyValue(ctx, buckets.Puts); err == nil { + if kv, err := js.KeyValue(ctx, buckets.Puts()); err == nil { putsKV = kv } if err := askAgain(ctx, asksKV, putsKV, stale, m.Data(), time.Now(), logf); err != nil { @@ -161,7 +161,7 @@ func endedAtBind(ctx context.Context, js jetstream.JetStream, asksKV jetstream.K } var putsKV jetstream.KeyValue if js != nil { - if kv, err := js.KeyValue(ctx, buckets.Puts); err == nil { + if kv, err := js.KeyValue(ctx, buckets.Puts()); err == nil { putsKV = kv } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/buildended.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/buildended.go index e95fc285..5c09da9d 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/buildended.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/buildended.go @@ -133,7 +133,7 @@ var buildsStreamName = catalogue.StreamBusName(builds.BuildsStream) // it returns, so the first pass knows every build; a stream it cannot hear is said, and no want names a build until the assigner // starts again. func hearBuilds(ctx context.Context, js jetstream.JetStream, ends *builtEnds, wake func(), findings assign.Findings, logf func(string, ...any)) { - acct := process.Account{Node: thisNode, Module: assignment.Module} + acct := process.Account{Node: thisNode, Module: assignment.Module()} nh := notHeardWriter(findings, logf) cc, err := acct.HearStream(ctx, js, buildsStreamName, func(m jetstream.Msg) { if err := ends.put(m.Data()); err != nil { diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/compositionstep_integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/compositionstep_integration_test.go index b7891797..daef24b8 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/compositionstep_integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/compositionstep_integration_test.go @@ -177,7 +177,7 @@ func TestTheCompositionStepComposesEachMachineThroughTheBuildSeat(t *testing.T) t.Error("the machine away was asked for its input") } } - keys, _ := b.buckets[buckets.Inputs].Keys(b.ctx) + keys, _ := b.buckets[buckets.Inputs()].Keys(b.ctx) if len(keys) != 0 { t.Errorf("the inputs stay kept past their step-ended: %v", keys) } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered.go index 8e861022..c49f7e43 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered.go @@ -111,7 +111,7 @@ func presenceOf(data []byte) (string, time.Time, error) { // machine-away and machine-back. A stream not heard is said as the assigner's finding, and what it carries is not known until // the assigner starts again. func hearDelivery(ctx context.Context, js jetstream.JetStream, w *world, wake func(), findings assign.Findings, logf func(string, ...any)) { - acct := process.Account{Node: thisNode, Module: assignment.Module} + acct := process.Account{Node: thisNode, Module: assignment.Module()} nh := notHeardWriter(findings, logf) for _, s := range []struct { stream, words string @@ -321,10 +321,10 @@ type deliveryWaiting struct { // saidDeliveryWaits is every delivery wait the state delivery-waits holds, by module; a value that does not decode is said and left. func (p *passer) saidDeliveryWaits() map[string]deliveryWaiting { out := map[string]deliveryWaiting{} - for key, value := range p.w.read(buckets.DeliveryWaits) { + for key, value := range p.w.read(buckets.DeliveryWaits()) { var w deliveryWaiting if err := json.Unmarshal(value, &w); err != nil || w.Module == "" { - p.logf("%s %s holds no delivery wait, left: %v", buckets.DeliveryWaits, key, err) + p.logf("%s %s holds no delivery wait, left: %v", buckets.DeliveryWaits(), key, err) continue } out[w.Module] = w @@ -335,7 +335,7 @@ func (p *passer) saidDeliveryWaits() map[string]deliveryWaiting { // holdForDelivery says whether module's want, changing to fp on branch, is held for a dependency's delivery this pass: it says // wait-held once and keeps it; a wait released by hand holds nothing; a wait that no longer holds is ended ran. func (p *passer) holdForDelivery(ctx context.Context, module, fp, branch string, nodes []string) bool { - if p.kvs[buckets.DeliveryWaits] == nil { + if p.kvs[buckets.DeliveryWaits()] == nil { return false } key := keys.EscapeName(module) @@ -364,7 +364,7 @@ func (p *passer) holdForDelivery(ctx context.Context, module, fp, branch string, return true } kept := deliveryWaiting{Wait: body} - p.put(ctx, buckets.DeliveryWaits, key, func() ([]byte, error) { return json.Marshal(kept) }) + p.put(ctx, buckets.DeliveryWaits(), key, func() ([]byte, error) { return json.Marshal(kept) }) return true } @@ -376,7 +376,7 @@ func (p *passer) endDeliveryWait(ctx context.Context, key string, w deliveryWait p.logf("the end of the delivery wait of %s is not said, and is said at the next pass: %v", w.Module, err) return } - p.remove(ctx, buckets.DeliveryWaits, key) + p.remove(ctx, buckets.DeliveryWaits(), key) } // againAsk is one ask of assigner_delivered_again, kept in the state delivered-again under its module (A-250): the version to say @@ -408,13 +408,13 @@ func (r *releasedVersions) releaseOf(module, version string) (releaseHeard, bool // version-delivered again when every present machine placing the module runs that version healthy and removes the ask, else keeps // it with why it does not hold, which assigner_waits reads. func (p *passer) deliveredAgain(ctx context.Context) { - if p.kvs[buckets.DeliveredAgain] == nil { + if p.kvs[buckets.DeliveredAgain()] == nil { return } - for key, value := range p.w.read(buckets.DeliveredAgain) { + for key, value := range p.w.read(buckets.DeliveredAgain()) { var a againAsk if err := json.Unmarshal(value, &a); err != nil || a.Version == "" { - p.logf("%s %s holds no ask, left: %v", buckets.DeliveredAgain, key, err) + p.logf("%s %s holds no ask, left: %v", buckets.DeliveredAgain(), key, err) continue } module := a.Module @@ -440,12 +440,12 @@ func (p *passer) deliveredAgain(ctx context.Context) { continue } p.logf("version-delivered of %s %s said again, asked by %s: %s", module, a.Version, a.By, a.Why) - p.remove(ctx, buckets.DeliveredAgain, key) + p.remove(ctx, buckets.DeliveredAgain(), key) continue } if a.Waits != waits { a.Waits = waits - p.put(ctx, buckets.DeliveredAgain, key, func() ([]byte, error) { return json.Marshal(a) }) + p.put(ctx, buckets.DeliveredAgain(), key, func() ([]byte, error) { return json.Marshal(a) }) } } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered_test.go index 75364e8f..a8d3827f 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/delivered_test.go @@ -26,7 +26,7 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) { at := time.Date(2026, 11, 12, 9, 0, 0, 0, time.UTC) waits := &memKV{values: map[string][]byte{}} again := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Wants: &memKV{values: map[string][]byte{}}, buckets.DeliveryWaits: waits, buckets.DeliveredAgain: again}, at) + p := newPasser(kvSet{buckets.Wants(): &memKV{values: map[string][]byte{}}, buckets.DeliveryWaits(): waits, buckets.DeliveredAgain(): again}, at) p.logf = t.Logf var events []said p.publishWants = func(event string, body any, id string) error { @@ -44,7 +44,7 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) { if err != nil { t.Fatal(err) } - p.w.put(buckets.Wants, key, raw) + p.w.put(buckets.Wants(), key, raw) } runs := func(node, module, fp string) { raw, _ := json.Marshal(map[string]any{"v": 1, "node": node, "module": module, "fingerprint": fp, "at": at}) @@ -152,7 +152,7 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) { _ = json.Unmarshal(waits.values["app"], &kept) kept.Released = true raw, _ := json.Marshal(kept) - p.w.put(buckets.DeliveryWaits, "app", raw) + p.w.put(buckets.DeliveryWaits(), "app", raw) if p.holdForDelivery(ctx, "app", fp("3"), trunkBranch, []string{"ace"}) { t.Fatal("a released wait holds app") } @@ -170,18 +170,18 @@ func TestADependentWaitsForItsDependencysDelivery(t *testing.T) { // away; one of 1.2.0, which no machine runs, is kept with why. ask := func(version string) { raw, _ := json.Marshal(againAsk{Module: "db", Version: version, By: "op", Why: "the catalog missed it"}) - p.w.put(buckets.DeliveredAgain, "db", raw) + p.w.put(buckets.DeliveredAgain(), "db", raw) } before := len(saidOf(meshwants.EventVersionDelivered)) ask("1.1.0") p.deliveredAgain(ctx) - if len(saidOf(meshwants.EventVersionDelivered)) != before+1 || p.w.has(buckets.DeliveredAgain, "db") { - t.Errorf("the ask of 1.1.0 said %d version-delivered and is kept %v", len(saidOf(meshwants.EventVersionDelivered))-before, p.w.has(buckets.DeliveredAgain, "db")) + if len(saidOf(meshwants.EventVersionDelivered)) != before+1 || p.w.has(buckets.DeliveredAgain(), "db") { + t.Errorf("the ask of 1.1.0 said %d version-delivered and is kept %v", len(saidOf(meshwants.EventVersionDelivered))-before, p.w.has(buckets.DeliveredAgain(), "db")) } ask("1.2.0") p.deliveredAgain(ctx) var a againAsk - if json.Unmarshal(p.w.value(buckets.DeliveredAgain, "db"), &a) != nil || a.Waits != "db 1.2.0 does not run healthy on ace" || + if json.Unmarshal(p.w.value(buckets.DeliveredAgain(), "db"), &a) != nil || a.Waits != "db 1.2.0 does not run healthy on ace" || len(saidOf(meshwants.EventVersionDelivered)) != before+1 { t.Errorf("the ask of 1.2.0 = %+v", a) } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/graphkept_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/graphkept_test.go index b471ccc9..225a98df 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/graphkept_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/graphkept_test.go @@ -31,7 +31,7 @@ func TestTheWantsAreSaidWantedFromTheKeptAssignmentGraphRevision(t *testing.T) { ctx := context.Background() at := time.Date(2026, 11, 12, 9, 0, 0, 0, time.UTC) kv := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Wants: kv}, at) + p := newPasser(kvSet{buckets.Wants(): kv}, at) p.logf = t.Logf var events []said p.publishWants = func(event string, body any, id string) error { @@ -60,9 +60,9 @@ func TestTheWantsAreSaidWantedFromTheKeptAssignmentGraphRevision(t *testing.T) { t.Fatalf("with no revision kept: %d events, %d puts", len(events), kv.puts) } key, _ := mwc.WantKey("ace", "web") - delete(p.w.read(buckets.Wants), key) // the world forgets the want, as before the first pass + delete(p.w.read(buckets.Wants()), key) // the world forgets the want, as before the first pass kv.puts = 0 - p.w.put(buckets.Wants, key, nil) + p.w.put(buckets.Wants(), key, nil) p.keepGraph(ctx) if len(events) != 1 || events[0].event != meshwants.EventAssignmentRevision || len(objects) == 0 { diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index c00a478a..b7c67c52 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -164,12 +164,12 @@ func startBed(t *testing.T, leaveOut string) *bed { t.Cleanup(cancel) b := &bed{srv: srv, js: js, ctx: ctx, dir: dir, buckets: map[string]jetstream.KeyValue{}, journal: &journal{grew: make(chan struct{})}, cat: &catalogBed{entries: map[string]map[string]meshcatalog.CatalogEntry{}, rev: map[string]int64{}, bodies: map[string]string{}}} - for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) { + for _, name := range append(append([]string{}, readBuckets...), ownBuckets()...) { if name == leaveOut { continue } cfg := jetstream.KeyValueConfig{Bucket: name, History: 5} - if name == plc.PulsesBucket { + if name == plc.PulsesBucket() { cfg.History, cfg.LimitMarkerTTL = 1, time.Minute // a time to live per key, as the controller makes it (key-ttl) } kv, err := js.CreateKeyValue(ctx, cfg) @@ -240,10 +240,10 @@ func startBed(t *testing.T, leaveOut string) *bed { // The seat list (ADR 0636), which the assigner's seat server checks its verbs' callers against (A-278). b.seatListRead = serveSeatList(t, nc) // The assigner's views family is its ordinary events on EVENTS (ADR 0635 §5); its marks are its state. - if kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: marksBucket, History: 1}); err != nil { + if kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: marksBucket(), History: 1}); err != nil { t.Fatal(err) } else { - b.buckets[marksBucket] = kv + b.buckets[marksBucket()] = kv } // The condition keeper's verb conditions (design 47 G §706): no condition is open, so a failing check is the delivery's after the // settle, which the bed sets to a second. @@ -378,14 +378,14 @@ func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) { was := PutsReopenEvery PutsReopenEvery = 0 defer func() { PutsReopenEvery = was }() - b := startBed(t, buckets.Puts) + b := startBed(t, buckets.Puts()) done := b.start(t) select { case err := <-done: - t.Fatalf("without %s the assigner stopped: %v", buckets.Puts, err) + t.Fatalf("without %s the assigner stopped: %v", buckets.Puts(), err) case <-time.After(2 * time.Second): } - putsKV, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buckets.Puts, History: 5}) + putsKV, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buckets.Puts(), History: 5}) if err != nil { t.Fatal(err) } @@ -412,37 +412,37 @@ func TestTheAssignerDoesNotStartWithoutABucketItReads(t *testing.T) { // Without the seat's assignments state it starts all the same, and once the bucket is made (the controller makes // it from the manifest) it is read with no restart: a node it names gets its own want, the node composing its own. - pb := startBed(t, plc.AssignmentsBucket) + pb := startBed(t, plc.AssignmentsBucket()) pdone := pb.start(t) select { case err := <-pdone: - t.Fatalf("without %s the assigner stopped: %v", plc.AssignmentsBucket, err) + t.Fatalf("without %s the assigner stopped: %v", plc.AssignmentsBucket(), err) case <-time.After(2 * time.Second): } // ADR 0641: the state the seat's assignments were kept in before holds n1's keys, and the form of one key per node; the // assigner copies the keys into the state made after its start, not the second form, and leaves the old state as it is. values, _ := mmac.AssignmentsValues(mma.Assignments{Node: "n1", Modules: []mma.PlacedModule{{Module: "web"}}}) for _, k := range slices.Sorted(maps.Keys(values)) { // a key per module, then the node key - pb.putJSON(t, retiredBucket, k, values[k]) + pb.putJSON(t, retiredBucket(), k, values[k]) } - pb.putJSON(t, retiredBucket, "placement.n1", values["n1.node"]) - assignments, err := pb.js.CreateKeyValue(pb.ctx, jetstream.KeyValueConfig{Bucket: plc.AssignmentsBucket, History: 5}) + pb.putJSON(t, retiredBucket(), "placement.n1", values["n1.node"]) + assignments, err := pb.js.CreateKeyValue(pb.ctx, jetstream.KeyValueConfig{Bucket: plc.AssignmentsBucket(), History: 5}) if err != nil { t.Fatal(err) } - if nw, err := mwc.DecodeNodeWant(pb.get(t, buckets.Wants, "want.n1.node")); err != nil || !nw.Self { + if nw, err := mwc.DecodeNodeWant(pb.get(t, buckets.Wants(), "want.n1.node")); err != nil || !nw.Self { t.Fatalf("the assignments state made after the start was not read: n1's own want = %+v, %v", nw, err) } for _, k := range slices.Sorted(maps.Keys(values)) { if e, err := assignments.Get(pb.ctx, k); err != nil || !slices.Equal(e.Value(), values[k]) { - t.Errorf("%s was not copied into %s: %v", k, plc.AssignmentsBucket, err) + t.Errorf("%s was not copied into %s: %v", k, plc.AssignmentsBucket(), err) } } // The readers delivered before this one still read the retired state: nothing in it is deleted or written, and the end of the copy is marked in the state copies. - if _, err := pb.buckets[copiesBucket].Get(pb.ctx, copiedMarker); err != nil { - t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket, err) + if _, err := pb.buckets[copiesBucket()].Get(pb.ctx, copiedMarker); err != nil { + t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket(), err) } - if left, err := pb.buckets[retiredBucket].Keys(pb.ctx); err != nil || len(left) != len(values)+1 { + if left, err := pb.buckets[retiredBucket()].Keys(pb.ctx); err != nil || len(left) != len(values)+1 { t.Errorf("the retired state holds %v (%v), want its %d keys as they were", left, err, len(values)+1) } } @@ -501,7 +501,7 @@ func (b *bed) place(t *testing.T, node, module string) { t.Fatal(err) } for key, raw := range values { - b.putJSON(t, plc.AssignmentsBucket, key, raw) + b.putJSON(t, plc.AssignmentsBucket(), key, raw) } } @@ -563,7 +563,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { // the indexer and graph, and reads nothing of the indexer's state; the answer clears it. firstViewBound, firstViewWait = 300*time.Millisecond, 50*time.Millisecond t.Cleanup(func() { catalogLate, firstViewBound, firstViewWait = nil, 0, 0 }) - for _, name := range append(append([]string{}, readBuckets...), watched...) { + for _, name := range append(append([]string{}, readBuckets...), watched()...) { if strings.HasPrefix(name, buckets.IndexerModule+"_") { t.Errorf("the assigner reads the indexer's state %s itself", name) } @@ -603,9 +603,9 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { "wants": {"n1.mesh-grants"}, }) // The reader raises the finding of the first view through the module's finding writer (A-26), its id escaped as a key's name. - graphKey := views.FindingKey(views.FindingScope, keys.EscapeName(assignment.Module+"."+meshcatalog.Seat+"."+views.BranchState(meshcatalog.StateCatalogRevisions, trunkBranch)), views.UnansweredKind) + graphKey := views.FindingKey(views.FindingScope, keys.EscapeName(assignment.Module()+"."+meshcatalog.Seat+"."+views.BranchState(meshcatalog.StateCatalogRevisions, trunkBranch)), views.UnansweredKind) graphRaised := func() bool { - e, err := b.buckets[assign.FindingsBucket].Get(b.ctx, graphKey) + e, err := b.buckets[assign.FindingsBucket()].Get(b.ctx, graphKey) return err == nil && strings.Contains(string(e.Value()), "mesh-catalog.revision") } for deadline := time.Now().Add(10 * time.Second); !graphRaised(); time.Sleep(50 * time.Millisecond) { @@ -621,7 +621,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { } wkey, _ := mwc.WantKey("ace", "web") - want, err := mwc.DecodeWant(b.get(t, buckets.Wants, wkey)) + want, err := mwc.DecodeWant(b.get(t, buckets.Wants(), wkey)) if err != nil || want.Fp != fNew { t.Fatalf("the canary ace's want = %+v, %v", want, err) } @@ -642,10 +642,10 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { if neither.Subject.Machine != "g14" || !strings.HasPrefix(neither.Subject.ID, "g14.web") || neither.Severity != occ.SeverityUrgent || !strings.Contains(neither.Explanation, "canary") { t.Errorf("the condition of web on neither list on g14 = %+v", neither) } - if keys, err := b.buckets[buckets.Differences].Keys(b.ctx); err == nil && len(keys) != 0 { + if keys, err := b.buckets[buckets.Differences()].Keys(b.ctx); err == nil && len(keys) != 0 { t.Errorf("differences were written: %v", keys) } - if e, err := b.buckets[buckets.Wants].Get(b.ctx, "want.g14.web"); err == nil { + if e, err := b.buckets[buckets.Wants()].Get(b.ctx, "want.g14.web"); err == nil { t.Errorf("g14, which runs an old-form build and is held back by the canary, was given a want: %s", e.Value()) } @@ -674,7 +674,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { t.Fatal(err) } for _, k := range slices.Sorted(maps.Keys(values)) { - b.putJSON(t, plc.AssignmentsBucket, k, values[k]) + b.putJSON(t, plc.AssignmentsBucket(), k, values[k]) } } if w := b.wantNow(t, "ace", "web", "ace's want once placed: the build alone, composed by the node", func(w mwc.Want) bool { return w.Self }); w.Composed != "" || w.Fp != fNew { @@ -683,7 +683,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { nkey, _ := mwc.WantNodeKey("ace") var self bool for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline) && !self; time.Sleep(100 * time.Millisecond) { - if e, err := b.buckets[buckets.Wants].Get(b.ctx, nkey); err == nil { + if e, err := b.buckets[buckets.Wants()].Get(b.ctx, nkey); err == nil { nw, err := mwc.DecodeNodeWant(e.Value()) self = err == nil && nw.Self } @@ -739,7 +739,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { t.Errorf("tool's want = %+v", w) } b.wantNow(t, "ace", "db", "the provider's want at its fingerprint", func(w mwc.Want) bool { return w.Fp == fpOf('2') }) - if e, err := b.buckets[buckets.Wants].Get(b.ctx, "want.ace.app"); err == nil { + if e, err := b.buckets[buckets.Wants()].Get(b.ctx, "want.ace.app"); err == nil { t.Errorf("app was wanted while its provision was asked: %s", e.Value()) } asked(meshprovisionerclient.Provided, "") @@ -816,7 +816,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { statement := func(node string, ok func(enginereads.Statement) bool) enginereads.Statement { var last enginereads.Statement for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { - if e, err := b.buckets[engineReadsBucket].Get(b.ctx, node); err == nil { + if e, err := b.buckets[engineReadsBucket()].Get(b.ctx, node); err == nil { if last, err = enginereads.Decode(e.Value()); err == nil && ok(last) { return last } @@ -861,7 +861,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { } b.wantNow(t, "ace", "forms", "the want moved once ace's engine said it reads the form", func(w mwc.Want) bool { return w.Fp == formsNew }) // assigner_engine_reads asks ace's engine again through the state: the assigner answers there with what it states now. - again, err := enginereads.AskAgainOf(bedState{b, engineReadsBucket}, "ace", "n1.ada", "a person asks", time.Now(), 15*time.Second) + again, err := enginereads.AskAgainOf(bedState{b, engineReadsBucket()}, "ace", "n1.ada", "a person asks", time.Now(), 15*time.Second) if err != nil || again.From != enginereads.FromVerb || !slices.Contains(again.ManifestReads, "form:new") || again.AskAgain != nil { t.Errorf("the ask again of ace answered %+v, %v", again, err) } @@ -919,12 +919,12 @@ func TestAHeldFingerprintIsAskedAbout(t *testing.T) { b.start(t) hkey, _ := mwc.HeldKey("web", fNew) // 1906/H3: the held record names the machine and keeps the health, when and the node's words. - if h, err := mwc.DecodeHeld(b.get(t, buckets.Wants, hkey)); err != nil || h.Node != "ace" || + if h, err := mwc.DecodeHeld(b.get(t, buckets.Wants(), hkey)); err != nil || h.Node != "ace" || !strings.HasPrefix(h.Words, "health failed, phase running, seen at ") || !strings.HasSuffix(h.Words, ": connection refused") { t.Fatalf("held = %+v, %v", h, err) } akey, _ := asks.HoldAskKey("web", fNew) - a, err := decodeAsk(akey, b.get(t, buckets.Asks, akey)) + a, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey)) if err != nil || a.Asking <= 0 || a.Ask.ID != meshdeliveryclient.AskingID(akey, a.Asking) || len(a.Ask.Options) != 2 { t.Fatalf("the ask = %+v, %v", a, err) } @@ -935,7 +935,7 @@ func TestAHeldFingerprintIsAskedAbout(t *testing.T) { // with the words its last finding said and the subject the finding names (`[.]`). func (b *bed) conditionsSaid(t *testing.T) map[string]oc.Condition { t.Helper() - w, err := b.buckets[assign.FindingsBucket].WatchAll(b.ctx, jetstream.IncludeHistory()) + w, err := b.buckets[assign.FindingsBucket()].WatchAll(b.ctx, jetstream.IncludeHistory()) if err != nil { t.Fatal(err) } @@ -973,16 +973,16 @@ func TestAFailedDataModuleIsAskedToRestore(t *testing.T) { b.putJSON(t, buckets.Have, "ace.mail", failed) wantKey, _ := mwc.WantKey("ace", "mail") want, _ := mwc.EncodeWant(mwc.Want{Fp: fNew, Composed: "sha256:" + strings.Repeat("c", 64)}) - b.putJSON(t, buckets.Wants, wantKey, want) + b.putJSON(t, buckets.Wants(), wantKey, want) b.place(t, "ace", "mail") b.start(t) akey, _ := asks.RestoreAskKey("ace", "mail", fNew) - a, err := decodeAsk(akey, b.get(t, buckets.Asks, akey)) + a, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey)) if err != nil || len(a.Ask.Options) != 2 || a.Ask.Options[0].ID != "restore" || a.Ask.Options[1].ID != "leave-stopped" { t.Fatalf("the restore ask = %+v, %v", a, err) } - if w, err := mwc.DecodeWant(b.get(t, buckets.Wants, wantKey)); err != nil || w.Fp != fNew { + if w, err := mwc.DecodeWant(b.get(t, buckets.Wants(), wantKey)); err != nil || w.Fp != fNew { t.Errorf("the data module was set back: want = %+v, %v", w, err) } key := "" @@ -1028,7 +1028,7 @@ func TestTheAssignerReplacedResumesWithNothingLostOrRepeated(t *testing.T) { b.place(t, "ace", name) } count := func() int { - keys, err := b.buckets[buckets.Wants].Keys(b.ctx) + keys, err := b.buckets[buckets.Wants()].Keys(b.ctx) if err != nil { return 0 } @@ -1044,7 +1044,7 @@ func TestTheAssignerReplacedResumesWithNothingLostOrRepeated(t *testing.T) { time.Sleep(time.Second) // and no pass of the second writes any again for _, name := range names { key, _ := mwc.WantKey("ace", name) - history, err := b.buckets[buckets.Wants].History(b.ctx, key) + history, err := b.buckets[buckets.Wants()].History(b.ctx, key) if err != nil { t.Errorf("the want of %s was never written: %v", name, err) continue @@ -1077,7 +1077,7 @@ func (b *bed) putWant(t *testing.T, node, module string, w mwc.Want) { if err != nil { t.Fatal(err) } - b.putJSON(t, buckets.Wants, key, v) + b.putJSON(t, buckets.Wants(), key, v) } // wantNow waits until the want of module on node satisfies ok and answers it. @@ -1086,7 +1086,7 @@ func (b *bed) wantNow(t *testing.T, node, module, what string, ok func(mwc.Want) key, _ := mwc.WantKey(node, module) var last mwc.Want for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { - if e, err := b.buckets[buckets.Wants].Get(b.ctx, key); err == nil { + if e, err := b.buckets[buckets.Wants()].Get(b.ctx, key); err == nil { if w, err := mwc.DecodeWant(e.Value()); err == nil { last = w if ok(w) { @@ -1102,7 +1102,7 @@ func (b *bed) wantNow(t *testing.T, node, module, what string, ok func(mwc.Want) // answer says the teller wrote the operator's answer to the ask that stands at key (its warrant names that asking's id). func (b *bed) answer(t *testing.T, key, option string) { t.Helper() - a, err := decodeAsk(key, b.get(t, buckets.Asks, key)) + a, err := decodeAsk(key, b.get(t, buckets.Asks(), key)) if err != nil { t.Fatal(err) } @@ -1143,7 +1143,7 @@ func TestAReleaseAnswerWritesTheWantAgain(t *testing.T) { t.Fatalf("the set-back want = %+v", w) } hold, _ := asks.HoldAskKey("web", fNew) - b.get(t, buckets.Asks, hold) + b.get(t, buckets.Asks(), hold) b.answer(t, hold, "release") w = b.wantNow(t, "ace", "web", "the held fingerprint's want written again", func(w mwc.Want) bool { return w.Fp == fNew }) if w.Composed != "" || !w.Self || w.Rule != 0 { @@ -1165,7 +1165,7 @@ func TestAReleaseAnswerWritesTheWantAgain(t *testing.T) { func (b *bed) askNow(t *testing.T, key string, asking int) decodedAsk { t.Helper() for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { - if e, err := b.buckets[buckets.Asks].Get(b.ctx, key); err == nil { + if e, err := b.buckets[buckets.Asks()].Get(b.ctx, key); err == nil { if a, err := decodeAsk(key, e.Value()); err == nil && a.Asking == asking { return a } @@ -1224,11 +1224,11 @@ func TestABackupAnswerIsWrittenIntoTheNodesWant(t *testing.T) { for k, c := range b.conditionsSaid(t) { cleared = cleared || (strings.Contains(k, "backup") && c.State == occ.StateCleared) } - if !b.hasKey(buckets.Asks, akey) && cleared { + if !b.hasKey(buckets.Asks(), akey) && cleared { break } if time.Now().After(deadline) { - t.Fatalf("the backup of a move the want left behind: ask there %v, condition cleared %v", b.hasKey(buckets.Asks, akey), cleared) + t.Fatalf("the backup of a move the want left behind: ask there %v, condition cleared %v", b.hasKey(buckets.Asks(), akey), cleared) } } } @@ -1249,12 +1249,12 @@ func TestAHoldIsLiftedOnceEveryNodeRunsItHealthy(t *testing.T) { b.start(t) hkey, _ := mwc.HeldKey("web", fNew) akey, _ := asks.HoldAskKey("web", fNew) - b.get(t, buckets.Wants, hkey) - b.get(t, buckets.Asks, akey) + b.get(t, buckets.Wants(), hkey) + b.get(t, buckets.Asks(), akey) healthy, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fNew, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK}) b.putJSON(t, buckets.Have, "ace.web", healthy) for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) { - if b.hasKey(buckets.Wants, hkey) || b.hasKey(buckets.Asks, akey) { + if b.hasKey(buckets.Wants(), hkey) || b.hasKey(buckets.Asks(), akey) { continue } for k, c := range b.conditionsSaid(t) { @@ -1266,7 +1266,7 @@ func TestAHoldIsLiftedOnceEveryNodeRunsItHealthy(t *testing.T) { } } } - t.Fatalf("the hold was not lifted: held %v, ask %v", b.hasKey(buckets.Wants, hkey), b.hasKey(buckets.Asks, akey)) + t.Fatalf("the hold was not lifted: held %v, ask %v", b.hasKey(buckets.Wants(), hkey), b.hasKey(buckets.Asks(), akey)) } // 1906/H2, whole on the bus: a hold lifted by itself and written again after the assigner was started again is asked under a @@ -1283,12 +1283,12 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) { _, stop := b.startStoppable(t) hkey, _ := mwc.HeldKey("web", fNew) akey, _ := asks.HoldAskKey("web", fNew) - first, err := decodeAsk(akey, b.get(t, buckets.Asks, akey)) + first, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey)) if err != nil { t.Fatal(err) } b.putJSON(t, buckets.Have, "ace.web", healthy) - for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants, hkey); time.Sleep(100 * time.Millisecond) { + for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants(), hkey); time.Sleep(100 * time.Millisecond) { if time.Now().After(deadline) { t.Fatal("the hold was not lifted") } @@ -1298,7 +1298,7 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) { b.answerTo(t, akey, "keep", first.Ask.ID) // an answer to the first hold, which the later hold must not take b.putJSON(t, buckets.Have, "ace.web", failed) _, stop = b.startStoppable(t) - again, err := decodeAsk(akey, b.get(t, buckets.Asks, akey)) + again, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey)) if err != nil { t.Fatal(err) } @@ -1326,7 +1326,7 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) { subscribed(t, nc) b.start(t) for deadline := time.Now().Add(15 * time.Second); ; time.Sleep(50 * time.Millisecond) { - if third, err := decodeAsk(akey, b.get(t, buckets.Asks, akey)); err == nil && third.Ask.ID != again.Ask.ID { + if third, err := decodeAsk(akey, b.get(t, buckets.Asks(), akey)); err == nil && third.Ask.ID != again.Ask.ID { break } if time.Now().After(deadline) { @@ -1334,7 +1334,7 @@ func TestAHoldWrittenAgainAfterALiftAndARestartIsAskedAgain(t *testing.T) { } } b.putJSON(t, buckets.Have, "ace.web", healthy) - for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants, hkey); time.Sleep(100 * time.Millisecond) { + for deadline := time.Now().Add(15 * time.Second); b.hasKey(buckets.Wants(), hkey); time.Sleep(100 * time.Millisecond) { if time.Now().After(deadline) { t.Fatal("the stale \"keep\" of the first hold kept the second one held") } @@ -1349,7 +1349,7 @@ func (s bedStore) kv(state string) jetstream.KeyValue { if strings.Contains(state, ".") { return s.b.buckets[strings.Replace(state, ".", "_", 1)] } - return s.b.buckets[assignment.Module+"_"+state] + return s.b.buckets[assignment.Module()+"_"+state] } func (s bedStore) get(state, key string) ([]byte, uint64, bool, error) { @@ -1391,7 +1391,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { t.Fatal(err) } } - if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "MOD_" + assignment.Module + "_assignments", + if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "MOD_" + assignment.Module() + "_assignments", Subjects: assignmentsSubjects()}); err != nil { // the seat's four events: the views family has a stream of its own t.Fatal(err) } @@ -1433,11 +1433,11 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { if k == key { continue } - if _, err := b.buckets[plc.AssignmentsBucket].Put(b.ctx, k, values[k]); err != nil { + if _, err := b.buckets[plc.AssignmentsBucket()].Put(b.ctx, k, values[k]); err != nil { t.Fatal(err) } } - rev, err := b.buckets[plc.AssignmentsBucket].Put(b.ctx, key, values[key]) + rev, err := b.buckets[plc.AssignmentsBucket()].Put(b.ctx, key, values[key]) if err != nil { t.Fatal(err) } @@ -1452,9 +1452,9 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { // The holder converts anchor, held in the form before alone, to its node and module keys once at its start. var anchorRev uint64 for deadline := time.Now().Add(15 * time.Second); anchorRev == 0; time.Sleep(50 * time.Millisecond) { - if e, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "anchor.node"); err == nil { + if e, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "anchor.node"); err == nil { anchorRev = e.Revision() - if _, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "anchor.web"); err != nil { + if _, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "anchor.web"); err != nil { t.Fatalf("anchor converted without its module key: %v", err) } } @@ -1487,7 +1487,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { t.Helper() var last mma.Proposal for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { - if e, err := b.buckets[plc.ProposalsBucket].Get(b.ctx, id); err == nil { + if e, err := b.buckets[plc.ProposalsBucket()].Get(b.ctx, id); err == nil { if p, err := plc.DecodeProposal(e.Value()); err == nil { if last = p; ok(p) { return p @@ -1502,7 +1502,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { t.Helper() for _, node := range p.Must { key, _ := plc.CandidateKey(p.ID, node) - e, err := b.buckets[plc.CandidatesBucket].Get(b.ctx, key) + e, err := b.buckets[plc.CandidatesBucket()].Get(b.ctx, key) if err != nil { t.Fatalf("no candidate %s: %v", key, err) } @@ -1534,7 +1534,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { // The node key names the modules, each module key carries its own. values := map[string][]byte{} for _, key := range []string{node + ".node", node + ".web", node + ".shell"} { - if e, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, key); err == nil { + if e, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, key); err == nil { values[key] = e.Value() } } @@ -1543,7 +1543,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { t.Errorf("%s = %v, %v, want %v", node, mmac.Assigned(got[node]), err, want) } } - if b.hasKey(plc.ProposalsBucket, p.ID) { + if b.hasKey(plc.ProposalsBucket(), p.ID) { t.Error("an applied proposal stays in the state") } @@ -1561,13 +1561,13 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { proposalNow(moved.ID, "asked", func(p mma.Proposal) bool { return p.State == mmac.ProposalAsked }) akey, _ = asks.AssignmentsAskKey(moved.ID) b.answer(t, akey, assign.OptionNo) - for deadline := time.Now().Add(15 * time.Second); b.hasKey(plc.ProposalsBucket, moved.ID); time.Sleep(50 * time.Millisecond) { + for deadline := time.Now().Add(15 * time.Second); b.hasKey(plc.ProposalsBucket(), moved.ID); time.Sleep(50 * time.Millisecond) { if time.Now().After(deadline) { t.Fatal("a declined proposal was not ended") } } key, _ := mmac.NodeKey("laptop") - if e, _ := b.buckets[plc.AssignmentsBucket].Get(b.ctx, key); e == nil || !strings.Contains(string(e.Value()), "web") { + if e, _ := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, key); e == nil || !strings.Contains(string(e.Value()), "web") { t.Error("a declined proposal wrote its key") } @@ -1608,10 +1608,10 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { case <-time.After(15 * time.Second): t.Fatal("no assignments-changed heard for the joining node") } - if ace, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "ace.node"); err != nil || !strings.Contains(string(ace.Value()), "10.9.0.2") { + if ace, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "ace.node"); err != nil || !strings.Contains(string(ace.Value()), "10.9.0.2") { t.Errorf("the joining node's key = %v, %v", ace, err) } - if akey, _ := asks.AssignmentsAskKey(joined.(proposed).Proposal.ID); b.hasKey(buckets.Asks, akey) || b.hasKey(plc.ProposalsBucket, joined.(proposed).Proposal.ID) { + if akey, _ := asks.AssignmentsAskKey(joined.(proposed).Proposal.ID); b.hasKey(buckets.Asks(), akey) || b.hasKey(plc.ProposalsBucket(), joined.(proposed).Proposal.ID) { t.Error("an ask was raised for the joining node, or its proposal stays open") } // Its enrolment ends before it confirms: a remove-node on the same warrant deletes its key at once, with no ask, and @@ -1631,7 +1631,7 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { case <-time.After(15 * time.Second): t.Fatal("no assignments-changed heard for the removed node") } - if _, err := b.buckets[plc.AssignmentsBucket].Get(b.ctx, "ace.node"); err == nil { + if _, err := b.buckets[plc.AssignmentsBucket()].Get(b.ctx, "ace.node"); err == nil { t.Error("the removed node's key stays") } } @@ -1640,15 +1640,15 @@ func TestAProposalIsAnsweredApprovedAndWritten(t *testing.T) { // the bucket does, and checks that no views event landed where a waiter on the assigner's put waits. func (b *bed) viewsHold(t *testing.T, state, key string) { t.Helper() - kv, marks := b.buckets[viewedStates[state]], b.buckets[marksBucket] + kv, marks := b.buckets[viewedStates[state]], b.buckets[marksBucket()] src := func(kv jetstream.KeyValue) views.Source { return bedSource{b.ctx, kv} } st, err := b.js.Stream(b.ctx, "EVENTS") if err != nil { t.Fatal(err) } for deadline := time.Now().Add(20 * time.Second); ; time.Sleep(50 * time.Millisecond) { - f := &views.Follower{Owner: assignment.Module, Tool: state, State: state, Ask: func(context.Context, string) (views.Answer, error) { - return views.FirstView(assignment.Module, state, src(kv), src(marks), "", time.Now()) + f := &views.Follower{Owner: assignment.Module(), Tool: state, State: state, Ask: func(context.Context, string) (views.Answer, error) { + return views.FirstView(assignment.Module(), state, src(kv), src(marks), "", time.Now()) }} if err := f.Start(b.ctx); err != nil { t.Fatal(err) @@ -1657,7 +1657,7 @@ func (b *bed) viewsHold(t *testing.T, state, key string) { gap := false for seq := info.State.FirstSeq; seq > 0 && seq <= info.State.LastSeq; seq++ { m, err := st.GetMsg(b.ctx, seq) - if owner, _ := views.OwnerOfEvent(m.Subject); err != nil || owner != assignment.Module || views.StateOfEvent(m.Subject) != state { + if owner, _ := views.OwnerOfEvent(m.Subject); err != nil || owner != assignment.Module() || views.StateOfEvent(m.Subject) != state { continue } e, err := views.DecodeEvent(m.Data) @@ -1715,7 +1715,7 @@ func (s bedSource) Get(key string) (*views.Entry, bool, error) { func assignmentsSubjects() []string { var out []string for _, e := range mma.Events() { - out = append(out, process.ModuleStreamSubject(assignment.Module, e)) + out = append(out, process.ModuleStreamSubject(assignment.Module(), e)) } return out } @@ -1827,7 +1827,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][] } state := func(name string) views.Source { if name == views.MarksState { - return bedSource{b.ctx, b.buckets[marksBucket]} + return bedSource{b.ctx, b.buckets[marksBucket()]} } return bedSource{b.ctx, b.buckets[viewedStates[name]]} } @@ -1837,7 +1837,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][] outR, outW := io.Pipe() t.Cleanup(func() { inW.Close(); outW.Close() }) go func() { - _ = stdio.ServeOn(assignment.Module, viewTools(func(k string) string { return env[k] }, state, time.Now), inR, outW) + _ = stdio.ServeOn(assignment.Module(), viewTools(func(k string) string { return env[k] }, state, time.Now), inR, outW) }() answers := make(chan map[string]any, 16) go func() { @@ -1858,7 +1858,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][] t.Fatal(err) } t.Cleanup(nc.Close) - sub, err := nc.Subscribe("mesh.mod."+assignment.Module+".call.*.*", func(msg *nats.Msg) { + sub, err := nc.Subscribe("mesh.mod."+assignment.Module()+".call.*.*", func(msg *nats.Msg) { one <- struct{}{} defer func() { <-one }() tokens := strings.Split(msg.Subject, ".") @@ -1891,7 +1891,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][] ask := func(user, tool string) (views.Answer, error) { return views.AskVia(func(key string, body any) (json.RawMessage, error) { data, _ := json.Marshal(body) - msg, err := nc.Request("mesh.mod."+assignment.Module+".call."+tool+"."+strings.ReplaceAll(user, ".", "~"), data, 5*time.Second) + msg, err := nc.Request("mesh.mod."+assignment.Module()+".call."+tool+"."+strings.ReplaceAll(user, ".", "~"), data, 5*time.Second) if err != nil { return nil, err } @@ -1907,7 +1907,7 @@ func firstViewsBeforeAnyNewFormState(t *testing.T, b *bed, granted map[string][] t.Errorf("%s's first view of %s before any new-form state: %v", user, tool, err) case slices.Contains(allowed, user): keys, _ := b.buckets[viewedStates[tool]].Keys(b.ctx) - if a.Owner != assignment.Module || a.State != tool || len(a.Values) != len(keys) { + if a.Owner != assignment.Module() || a.State != tool || len(a.Values) != len(keys) { t.Errorf("%s's first view of %s = %+v, the state holds %v", user, tool, a, keys) } case err == nil || !strings.Contains(err.Error(), "refused"): diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go index fa7256de..53dd4f7b 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go @@ -78,25 +78,29 @@ var readFamilies = []string{buckets.ComposedRead} // ownBuckets are the assigner's own states, as its manifest declares them: the seat mesh-module-assigner's three among them // (ADR 0567 §1), named after the assigner as every module state is. -var ownBuckets = []string{buckets.Asks, buckets.Differences, - buckets.Puts, buckets.Wants, plc.AssignmentsBucket, plc.ProposalsBucket, - plc.CandidatesBucket, plc.PulsesBucket, assign.FindingsBucket, assign.PoliciesBucket, plc.SeatHoldersBucket, - engineReadsBucket, retiredBucket, copiesBucket, writtenBucket, buckets.Waits, buckets.PutWaits, buckets.DeliveryWaits, buckets.DeliveredAgain, buckets.Inputs, pinsBucket} +func ownBuckets() []string { + return []string{buckets.Asks(), buckets.Differences(), + buckets.Puts(), buckets.Wants(), plc.AssignmentsBucket(), plc.ProposalsBucket(), + plc.CandidatesBucket(), plc.PulsesBucket(), assign.FindingsBucket(), assign.PoliciesBucket(), plc.SeatHoldersBucket(), + engineReadsBucket(), retiredBucket(), copiesBucket(), writtenBucket(), buckets.Waits(), buckets.PutWaits(), buckets.DeliveryWaits(), buckets.DeliveredAgain(), buckets.Inputs(), pinsBucket()} +} // engineReadsState is the assigner's state of what each node's engine states it reads (ADR 0618 §3), one key ``, as // the verb manifest-reads last answered it or the fallback read it: what assigner_engine_reads lists and asks again. const engineReadsState = "engine-reads" // engineReadsBucket is that state's bucket. -const engineReadsBucket = assignment.Module + "_" + engineReadsState +func engineReadsBucket() string { return assignment.Module() + "_" + engineReadsState } // writtenBucket is the bucket of the state written (pipelines.go): the pipeline ends the wants follow. -const writtenBucket = assignment.Module + "_" + writtenState +func writtenBucket() string { return assignment.Module() + "_" + writtenState } // seatBuckets are the seat's states, new with it: until the controller has made them from the manifest and the bus grants // them, the assigner starts without them and binds each once the bus says it was created (issue 1790's kind). -var seatBuckets = []string{plc.AssignmentsBucket, plc.ProposalsBucket, plc.CandidatesBucket, - plc.PulsesBucket, copiesBucket} +func seatBuckets() []string { + return []string{plc.AssignmentsBucket(), plc.ProposalsBucket(), plc.CandidatesBucket(), + plc.PulsesBucket(), copiesBucket()} +} // keptStates is what the assigner keeps and who may read it (ADR 0495 §2-§3): its wants by the watchman and the view // (and the node agent from release 6), its differences by the view, its asks by the view @@ -138,18 +142,18 @@ type kvSet map[string]jetstream.KeyValue // without it and takes no put further until it starts again (ADR 0546). func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any)) (kvSet, error) { out := kvSet{} - for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) { + for _, name := range append(append([]string{}, readBuckets...), ownBuckets()...) { kv, err := js.KeyValue(ctx, name) - if name == buckets.Puts && err != nil { + if name == buckets.Puts() && err != nil { logf("the bucket %s cannot be opened (%v): no put is taken further until the assigner starts again", name, err) continue } - if slices.Contains(seatBuckets, name) && err != nil { + if slices.Contains(seatBuckets(), name) && err != nil { // The seat's states come in the same delivery as this assigner (ADR 0567 §1): without assignments no module is placed. logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err) continue } - if name == plc.SeatHoldersBucket && err != nil { + if name == plc.SeatHoldersBucket() && err != nil { // The seat's state, new with ADR 0571 §4: bound once the bus says it was made; until then nobody is told who // holds which seat here, and the controller's seats still answer. logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err) @@ -161,56 +165,56 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err) continue } - if name == assign.PoliciesBucket && err != nil { + if name == assign.PoliciesBucket() && err != nil { // The seat mesh-wants' state, new with it (ADR 0565 §7): bound once the bus says it was made; until then no // person's policy is read, so the controller's beside each composition holds, as before. logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err) continue } - if name == assign.FindingsBucket && err != nil { + if name == assign.FindingsBucket() && err != nil { // New with health leaving the controller (issue 1952 WM6): without it the conditions are said straight to the // router and kept as no finding. logf("the bucket %s cannot be opened (%v): the assigner's conditions are said straight to the router until it starts again", name, err) continue } - if name == engineReadsBucket && err != nil { + if name == engineReadsBucket() && err != nil { // New with ADR 0618 §3: without it the statements the verb answers are kept in memory only, and // assigner_engine_reads lists nothing. logf("the bucket %s cannot be opened (%v): what each node's engine reads is kept in memory only until it starts again", name, err) continue } - if name == buckets.Inputs && err != nil { + if name == buckets.Inputs() && err != nil { // New with the verb input (design 47 G §354): only the tool process reads it, so the program starts without it. logf("the bucket %s cannot be opened (%v): it is read by the tool process alone", name, err) continue } - if name == pinsBucket && err != nil { + if name == pinsBucket() && err != nil { // New with the pins (design 47 G §579): without it no pin is kept and no revision is removed. logf("the bucket %s cannot be opened (%v): no pin is kept and no revision removed until it starts again", name, err) continue } - if name == buckets.Waits && err != nil { + if name == buckets.Waits() && err != nil { // New with the wait on an outside cause (design 47 G §706): without it no wait is made, and a failing check is judged // as before. logf("the bucket %s cannot be opened (%v): no outside wait is kept until the assigner starts again", name, err) continue } - if name == buckets.DeliveredAgain && err != nil { + if name == buckets.DeliveredAgain() && err != nil { // New with A-250: without it assigner_delivered_again is not taken until the assigner starts again. logf("the bucket %s cannot be opened (%v): no version is said delivered again until the assigner starts again", name, err) continue } - if name == buckets.DeliveryWaits && err != nil { + if name == buckets.DeliveryWaits() && err != nil { // New with A-246: without it no dependent waits for its dependency's delivery until the assigner starts again. logf("the bucket %s cannot be opened (%v): no delivery wait is kept until the assigner starts again", name, err) continue } - if name == buckets.PutWaits && err != nil { + if name == buckets.PutWaits() && err != nil { // New with A-211: without it no put's wait is said until the assigner starts again. logf("the bucket %s cannot be opened (%v): no put's wait is said until the assigner starts again", name, err) continue } - if name == retiredBucket && err != nil { + if name == retiredBucket() && err != nil { // The retired state is read only for the copy into `assignments` (migrate.go): a mesh made after the switch has // none, and nothing is copied. logf("the bucket %s cannot be opened (%v): nothing is copied from it", name, err) @@ -233,11 +237,11 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any // states and those new with a delivery of their own, bound once made, and the two that choose the grants' source. func startsWithout(name string) bool { switch name { - case plc.SeatHoldersBucket, buckets.Provisions, assign.PoliciesBucket, assign.FindingsBucket, - buckets.Puts, engineReadsBucket, retiredBucket, writtenBucket, buckets.Waits, buckets.PutWaits, buckets.DeliveryWaits, buckets.DeliveredAgain, buckets.Inputs, pinsBucket: + case plc.SeatHoldersBucket(), buckets.Provisions, assign.PoliciesBucket(), assign.FindingsBucket(), + buckets.Puts(), engineReadsBucket(), retiredBucket(), writtenBucket(), buckets.Waits(), buckets.PutWaits(), buckets.DeliveryWaits(), buckets.DeliveredAgain(), buckets.Inputs(), pinsBucket(): return true } - return slices.Contains(seatBuckets, name) || name == buckets.Grants + return slices.Contains(seatBuckets(), name) || name == buckets.Grants } // run is the program: it returns when ctx ends or the bus cannot be used. @@ -247,6 +251,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro if err != nil { return err } + plc.SetModule(account.Module) // the assigner names its own states from its credential's module (A-296, §428) conn, js, err := account.Dial() if err != nil { return err @@ -285,10 +290,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro } }() iv := &catalogViews{js: js, account: account, call: said.Call, writer: wireOf(writer), logf: logger.Printf, - status: process.AwaitBucket(ctx, js, conn, firstViewsBucket, "assigner_first_views reads nothing and assigner_first_view_again is not heard", logger.Printf)} + status: process.AwaitBucket(ctx, js, conn, firstViewsBucket(), "assigner_first_views reads nothing and assigner_first_view_again is not heard", logger.Printf)} // The inputs of the composition steps it asks a build seat to run: the seat mesh-wants' verb input is answered here in chunks // of the core connection, of any size (design 47 G §354, §712, A-237). - inputs := inputsOver(kvs[buckets.Inputs], js, account.Module, logger.Printf) + inputs := inputsOver(kvs[buckets.Inputs()], js, account.Module, logger.Printf) // The seat list (seatlist.go), followed in follow over the connection and the hub: the verb input's callers are checked // against the seat's definition it holds (A-186). gate := seatlist.NewPart("assigner", seatListAgainCallers) @@ -306,7 +311,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // condition keeper carries (novox/hq ADR 0590 §3). Without it the conditions are said straight to the router. func findingsOf(ctx context.Context, account process.Account, conn *nats.Conn, js jetstream.JetStream, said *process.Conn, kvs kvSet, logf func(string, ...any)) (assign.Findings, *heldWriter) { var f assign.Findings - if kvs[assign.FindingsBucket] == nil { + if kvs[assign.FindingsBucket()] == nil { return f, nil } state := said.State(assign.FindingsState) @@ -354,30 +359,34 @@ type emitting struct{ *process.Conn } func (e emitting) Emit(eventType string, body any) error { return e.Conn.Emit(eventType, body) } // optionalEnds is what stops when the watch of a bucket that never gates the first pass ends. -var optionalEnds = map[string]string{buckets.Puts: "no put is taken further", buckets.Answers: "no ask is answered"} +func optionalEnds() map[string]string { + return map[string]string{buckets.Puts(): "no put is taken further", buckets.Answers: "no ask is answered"} +} // watched are the buckets whose every value a pass reads; the manifests are read by digest when a pass needs one, and // watched only for keys, so a manifest that arrives wakes a composition that waits for it. -var watched = []string{ - buckets.Grants, - plc.AssignmentsBucket, - plc.ProposalsBucket, - plc.CandidatesBucket, - plc.PulsesBucket, - copiesBucket, - buckets.Have, - buckets.Asks, - buckets.Differences, - buckets.Wants, - assign.PoliciesBucket, - buckets.Provisions, - plc.SeatHoldersBucket, - engineReadsBucket, - writtenBucket, - buckets.Waits, - buckets.PutWaits, - buckets.DeliveryWaits, - buckets.DeliveredAgain, // its watch wakes a pass at once on an ask of assigner_delivered_again (A-250) +func watched() []string { + return []string{ + buckets.Grants, + plc.AssignmentsBucket(), + plc.ProposalsBucket(), + plc.CandidatesBucket(), + plc.PulsesBucket(), + copiesBucket(), + buckets.Have, + buckets.Asks(), + buckets.Differences(), + buckets.Wants(), + assign.PoliciesBucket(), + buckets.Provisions, + plc.SeatHoldersBucket(), + engineReadsBucket(), + writtenBucket(), + buckets.Waits(), + buckets.PutWaits(), + buckets.DeliveryWaits(), + buckets.DeliveredAgain(), // its watch wakes a pass at once on an ask of assigner_delivered_again (A-250) + } } // follow watches every bucket a pass reads and makes a pass at start, once every watch handed what stands, and after @@ -385,7 +394,7 @@ var watched = []string{ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, kvs kvSet, say func(oc.Condition) error, findings assign.Findings, bus caller, iv *catalogViews, inputs *inputKeeper, gate *seatlist.Part, logger *log.Logger, getenv func(string) string) error { w := newWorld() // The callers' seat entries are resolved from the assigner's own seat-holders as it last read them (A-298). - gate.Holders(seatlist.HoldersIn(func() (map[string][]byte, error) { return w.read(plc.SeatHoldersBucket), nil })) + gate.Holders(seatlist.HoldersIn(func() (map[string][]byte, error) { return w.read(plc.SeatHoldersBucket()), nil })) dirty := make(chan struct{}, 1) wake := func() { select { @@ -393,8 +402,8 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, default: } } - errs := make(chan error, len(watched)+2) - settled := make(chan struct{}, len(watched)+2) + errs := make(chan error, len(watched())+2) + settled := make(chan struct{}, len(watched())+2) // What each node's engine reads (ADR 0618 §3, §4): asked through the seat's verb, kept in the state engine-reads, read // from the node's record where the engine does not answer (the fallback, said in the journal). reads := &enginereads.Keeper{Fallback: w.recordReads, Changed: wake, Logf: logger.Printf} @@ -402,7 +411,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, reads.Call = bus.CallKeptOn // a kept call, called again through a transport fault (ADR 0668 §1) reads.Once = bus.CallOn // its fault keeps the fallback at once while the kept call asks on (ADR 0668 §3) } - if kv := kvs[engineReadsBucket]; kv != nil { + if kv := kvs[engineReadsBucket()]; kv != nil { reads.Put = func(node string, v []byte) error { _, err := kv.Put(ctx, node, v); return err } reads.Delete = func(node string) error { return kv.Delete(ctx, node) } } @@ -412,7 +421,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, return fmt.Errorf("the bucket %s cannot be watched: %w", name, err) } // The puts' watch never stops the wants (ADR 0546): when it ends, the puts are no longer heard, said in the journal. - optional := name == buckets.Puts || name == buckets.Answers + optional := name == buckets.Puts() || name == buckets.Answers go func() { defer kw.Stop() for { @@ -423,7 +432,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, case e, ok := <-kw.Updates(): if !ok { if optional { - logger.Printf("the watch of %s ended: %s until the assigner starts again", name, optionalEnds[name]) + logger.Printf("the watch of %s ended: %s until the assigner starts again", name, optionalEnds()[name]) return } errs <- fmt.Errorf("the watch of %s ended", name) @@ -439,7 +448,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, if e.Operation() == jetstream.KeyValuePut { value = e.Value() } - if name == engineReadsBucket { + if name == engineReadsBucket() { reads.Heard(e.Key(), value, value == nil) // it wakes a pass when a statement changed continue } @@ -452,10 +461,10 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, } // The puts' watch is not waited for before the first pass: a watch of them that ends before it handed what stands only // stops the puts, never the wants. - if kvs[buckets.Puts] != nil { - if err := watch(buckets.Puts); err != nil { + if kvs[buckets.Puts()] != nil { + if err := watch(buckets.Puts()); err != nil { logger.Printf("%v: no put is taken further until it is opened again", err) - delete(kvs, buckets.Puts) + delete(kvs, buckets.Puts()) } } // The puts' bucket is made by the controller from the manifest's states at its start (ADR 0201), and the assigner's @@ -464,27 +473,27 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, // further, without a restart. var triedPuts time.Time reopenPuts := func(p *passer) { - if kvs[buckets.Puts] != nil || time.Since(triedPuts) < PutsReopenEvery { + if kvs[buckets.Puts()] != nil || time.Since(triedPuts) < PutsReopenEvery { return } triedPuts = time.Now() - kv, err := js.KeyValue(ctx, buckets.Puts) + kv, err := js.KeyValue(ctx, buckets.Puts()) if err != nil { return } - kvs[buckets.Puts] = kv - if err := watch(buckets.Puts); err != nil { + kvs[buckets.Puts()] = kv + if err := watch(buckets.Puts()); err != nil { logger.Printf("%v: no put is taken further until it is opened again", err) - delete(kvs, buckets.Puts) + delete(kvs, buckets.Puts()) return } if bus != nil { p.acts = newPutActs(bus, wake, logger.Printf) } - logger.Printf("the bucket %s is open: puts are taken further", buckets.Puts) + logger.Printf("the bucket %s is open: puts are taken further", buckets.Puts()) } started := 0 - for _, name := range watched { + for _, name := range watched() { if kvs[name] == nil && startsWithout(name) { continue // not on the bus at this start (open said so) } @@ -541,15 +550,15 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, name string kv jetstream.KeyValue } - assignmentsAppeared := make(chan appeared, len(seatBuckets)+2) - for _, name := range seatBuckets { + assignmentsAppeared := make(chan appeared, len(seatBuckets())+2) + for _, name := range seatBuckets() { if kvs[name] == nil && js != nil { process.AwaitBucket(ctx, js, nc, name, "the seat mesh-module-assigner waits for it", logger.Printf).Ready(func(kv jetstream.KeyValue) { assignmentsAppeared <- appeared{name, kv} }) } } // And the two that choose where grants are read from, with the same delivery as the grants part that writes one. - for _, name := range []string{buckets.Grants, assign.PoliciesBucket, buckets.Provisions, plc.SeatHoldersBucket} { + for _, name := range []string{buckets.Grants, assign.PoliciesBucket(), buckets.Provisions, plc.SeatHoldersBucket()} { if kvs[name] == nil && js != nil { name := name process.AwaitBucket(ctx, js, nc, name, "until then the bucket is not read", @@ -581,7 +590,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, reads: reads, seats: gate} // The wait on an outside cause (outside.go, design 47 G §706): the keeper's three events say when to judge again, and the // seat mesh-wants' wait-held and wait-ended are said; registered before the hub starts. - if bus != nil && kvs[buckets.Waits] != nil { + if bus != nil && kvs[buckets.Waits()] != nil { bound, settle, err := outsideSettings(getenv) if err != nil { return err @@ -603,7 +612,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, if js != nil { p.publishWants = wantsPublisher(ctx, js, thisNode) if iv != nil && iv.h != nil { - objects := newBusObjects(js, process.Account{Node: thisNode, Module: assignment.Module}, assigngraph.RevisionsObjects) + objects := newBusObjects(js, process.Account{Node: thisNode, Module: assignment.Module()}, assigngraph.RevisionsObjects) p.graph = &graphKeeper{objects: objects, halves: iv.h.views} keep := func(ctx context.Context, ev meshcatalog.CatalogRevisionEvent) error { return p.keepCandidate(ctx, iv.call, ev) @@ -614,7 +623,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, p.entries.Changed = func(meshcatalogclient.EntryRow) { wake() } // The pins of the revisions (pins.go, design 47 G §579): the gate's pinned, freed, heads-replaced and pins-stated // on the hub, registered before it starts. - if kv := kvs[pinsBucket]; kv != nil { + if kv := kvs[pinsBucket()]; kv != nil { k := &pinKeeper{ctx: ctx, st: kvEnds{ctx, kv}, objects: objects, inUse: inUseOf(w), findings: findings, now: time.Now, logf: logger.Printf} if err := k.made(); err != nil { logger.Printf("the state %s is not marked made, so no revision is removed until it is: %v", pinsState, err) @@ -634,11 +643,11 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, if js != nil { // The teller's ask-ended (issue 1966): an ask ended at the channel without an answer is written again; registered // before hearReadsChanged starts the hub. - if err := hearAskEnded(ctx, js, hub, kvs[buckets.Asks], staleOfWorld(w), logger.Printf); err != nil { + if err := hearAskEnded(ctx, js, hub, kvs[buckets.Asks()], staleOfWorld(w), logger.Printf); err != nil { logger.Printf("%s is not heard, so an ask that ends at the operator channel without an answer is not asked again until the assigner starts again: %v", askEndedSubject, err) } - readEndedAtBind(ctx, js, hub, kvs[buckets.Asks], staleOfWorld(w), bus, callWait{heard: func() bool { return w.seatHeard(oc.Seat) }, findings: findings}, logger.Printf) - if kv := kvs[writtenBucket]; kv != nil { + readEndedAtBind(ctx, js, hub, kvs[buckets.Asks()], staleOfWorld(w), bus, callWait{heard: func() bool { return w.seatHeard(oc.Seat) }, findings: findings}, logger.Printf) + if kv := kvs[writtenBucket()]; kv != nil { // The runner's pipeline-ended and group-ended (design 47 G §29): kept in the state written, which the wants follow // once the first registrations are written (§95, §353). p.ends = newPipelineEnds(kvEnds{ctx, kv}, wake, logger.Printf) @@ -660,7 +669,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, hearReadsChanged(ctx, js, hub, reads, findings, gate.EventsLive, logger.Printf) hearRunning(ctx, js, w, wake, findings, logger.Printf) } - if bus != nil && kvs[buckets.Puts] != nil { + if bus != nil && kvs[buckets.Puts()] != nil { p.acts = newPutActs(bus, wake, logger.Printf) } // The composition step (design 47 G §22, §354): the seat mesh-wants' accepted kind step, run by one run ask per machine. @@ -699,7 +708,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, // be said before this credential may hear it: every pass, itself started by a watched change, binds each one the // bus now holds, and says the seat waits while one is missing. var unbound []string - for _, name := range seatBuckets { + for _, name := range seatBuckets() { if kvs[name] != nil || js == nil { continue } @@ -724,7 +733,7 @@ var PutsReopenEvery = time.Minute // (ADR 0437 clause 9). func sayVia(ctx context.Context, js jetstream.JetStream) func(oc.Condition) error { return func(c oc.Condition) error { - subject, err := occ.ConditionSubject(assignment.Module) + subject, err := occ.ConditionSubject(assignment.Module()) if err != nil { return err } @@ -764,7 +773,7 @@ func readsChangedHandler(reads *enginereads.Keeper, logf func(string, ...any)) f // consumer on EVENTS, and asks the node it names again; the hub also hands the indexer's views to the followers of // indexerViews, and starts once both are registered. func hearReadsChanged(ctx context.Context, js jetstream.JetStream, hub *process.EventHub, reads *enginereads.Keeper, findings assign.Findings, live func(), logf func(string, ...any)) { - account := process.Account{Node: thisNode, Module: assignment.Module} + account := process.Account{Node: thisNode, Module: assignment.Module()} w := notHeardWriter(findings, logf) handle := readsChangedHandler(reads, logf) err := hub.Handle(enginereads.SubjectPrefix+"*", handle) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main_test.go index 5c0c3c8a..5cfe19f8 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main_test.go @@ -69,7 +69,7 @@ func TestOpensWhatItsManifestNames(t *testing.T) { for _, pair := range []struct { what string code, want []string - }{{"reads", readBuckets, reads}, {"states", append(slices.Clone(ownBuckets), marksBucket, firstViewsBucket), states}} { // the marks are bound by the views family (views.go) + }{{"reads", readBuckets, reads}, {"states", append(slices.Clone(ownBuckets()), marksBucket(), firstViewsBucket()), states}} { // the marks are bound by the views family (views.go) got, want := slices.Sorted(slices.Values(pair.code)), slices.Sorted(slices.Values(pair.want)) if !reflect.DeepEqual(got, want) { t.Errorf("the program's %s are %v, the manifest's %v", pair.what, got, want) @@ -79,7 +79,7 @@ func TestOpensWhatItsManifestNames(t *testing.T) { for _, s := range keptStates { kept = append(kept, m.Module+"_"+s.Name) } - if got, want := slices.Sorted(slices.Values(kept)), slices.Sorted(slices.Values(append(slices.Clone(ownBuckets), marksBucket, firstViewsBucket))); !reflect.DeepEqual(got, want) { + if got, want := slices.Sorted(slices.Values(kept)), slices.Sorted(slices.Values(append(slices.Clone(ownBuckets()), marksBucket(), firstViewsBucket()))); !reflect.DeepEqual(got, want) { t.Errorf("the program announces %v, keeps %v", got, want) } // ADR 0546: whatever keeps the puts' bucket from opening (not there, not granted, no answer in time), the assigner opens @@ -90,17 +90,17 @@ func TestOpensWhatItsManifestNames(t *testing.T) { starts bool said string }{ - {buckets.Puts, jetstream.ErrBucketNotFound, true, "no put is taken further"}, - {buckets.Puts, errors.New("nats: permissions violation for subscription to \"$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_puts\""), true, "no put is taken further"}, - {buckets.Puts, context.DeadlineExceeded, true, "no put is taken further"}, - {buckets.Wants, errors.New("nats: permissions violation"), false, ""}, + {buckets.Puts(), jetstream.ErrBucketNotFound, true, "no put is taken further"}, + {buckets.Puts(), errors.New("nats: permissions violation for subscription to \"$JS.API.STREAM.INFO.KV_mesh-delivery-assigner_puts\""), true, "no put is taken further"}, + {buckets.Puts(), context.DeadlineExceeded, true, "no put is taken further"}, + {buckets.Wants(), errors.New("nats: permissions violation"), false, ""}, // Issue 774: the grants part's grants not there do not stop the start. {buckets.Grants, jetstream.ErrBucketNotFound, true, "no grant is read"}, } { var logged string kvs, err := open(context.Background(), refusingJS{missing: c.missing, err: c.err}, func(f string, a ...any) { logged += fmt.Sprintf(f, a...) }) switch { - case c.starts && (err != nil || kvs[c.missing] != nil || len(kvs) != len(readBuckets)+len(ownBuckets)-1 || !strings.Contains(logged, c.said)): + case c.starts && (err != nil || kvs[c.missing] != nil || len(kvs) != len(readBuckets)+len(ownBuckets())-1 || !strings.Contains(logged, c.said)): t.Errorf("%s refused with %v: the start answered %v, %d buckets, logged %q", c.missing, c.err, err, len(kvs), logged) case !c.starts && (err == nil || !strings.Contains(err.Error(), "grant of "+c.missing)): // 1945: named with its grant t.Errorf("%s refused with %v: the start answered %v", c.missing, c.err, err) @@ -112,7 +112,7 @@ func TestOpensWhatItsManifestNames(t *testing.T) { defer cancel() wrote := make(chan string, 8) kvs := kvSet{} - for _, name := range append(append([]string{}, readBuckets...), ownBuckets...) { + for _, name := range append(append([]string{}, readBuckets...), ownBuckets()...) { kvs[name] = &watchKV{memKV: &memKV{values: map[string][]byte{}}} } placed, err := mmac.AssignmentsValues(mma.Assignments{Node: "ace"}) @@ -120,11 +120,11 @@ func TestOpensWhatItsManifestNames(t *testing.T) { t.Fatal(err) } for key, raw := range placed { - kv := kvs[plc.AssignmentsBucket].(*watchKV) + kv := kvs[plc.AssignmentsBucket()].(*watchKV) kv.entries = append(kv.entries, kvEntry{key: key, value: raw}) } - kvs[buckets.Puts].(*watchKV).closed = true - kvs[buckets.Wants].(*watchKV).wrote = wrote + kvs[buckets.Puts()].(*watchKV).closed = true + kvs[buckets.Wants()].(*watchKV).wrote = wrote go follow(ctx, nil, nil, kvs, func(oc.Condition) error { return nil }, assign.Findings{}, nil, nil, nil, seatlist.NewPart("assigner", seatListAgainCallers), log.New(io.Discard, "", 0), func(string) string { return "" }) select { case key := <-wrote: diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/migrate.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/migrate.go index f1d27e8b..95e9dace 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/migrate.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/migrate.go @@ -18,10 +18,9 @@ import ( // The state this seat's assignments were kept in before ADR 0641 gave it its name. The assigner reads it here and nowhere // else: at its start it copies each key into `assignments` (copyRetired) and keeps no reader of it afterwards. The // manifest still declares the state, so the assigner's account may read it for the copy; a later release drops it. -const ( - retiredState = "placement" - retiredBucket = plc.Module + "_" + retiredState -) +const retiredState = "placement" + +func retiredBucket() string { return plc.Module() + "_" + retiredState } // CopiedEvent is the module's event saying the copy was made, once: mesh.mod.mesh-delivery-assigner.event.assignments-copied. const CopiedEvent = "assignments-copied" @@ -37,10 +36,9 @@ type copiedSaid struct { // copiesState is the assigner's own state of its copies, read by its tool assigner_read_copies, and copiesBucket its bucket. // The retired state gets nothing written: readers of other modules still parse every key in it. -const ( - copiesState = "copies" - copiesBucket = plc.Module + "_" + copiesState -) +const copiesState = "copies" + +func copiesBucket() string { return plc.Module() + "_" + copiesState } // copiedMarker is the key, in the state copies, that says the copy ended: its time and the keys it copied. It is written // after the last key, and the copy is made at every start until it is there and never after, so a copy cut short by a @@ -63,12 +61,12 @@ func (p *passer) copyRetired(ctx context.Context) bool { if p.copied { return true } - old, next, copies := p.kvs[retiredBucket], p.kvs[plc.AssignmentsBucket], p.kvs[copiesBucket] + old, next, copies := p.kvs[retiredBucket()], p.kvs[plc.AssignmentsBucket()], p.kvs[copiesBucket()] if next == nil || copies == nil { return false } if old == nil { - p.logf("assignments: the retired state %s cannot be read, so nothing is copied from it", retiredBucket) + p.logf("assignments: the retired state %s cannot be read, so nothing is copied from it", retiredBucket()) p.copied = true return true } @@ -77,12 +75,12 @@ func (p *passer) copyRetired(ctx context.Context) bool { p.copied = true // the copy ended at an earlier start: nothing is copied again return true case !errors.Is(err, jetstream.ErrKeyNotFound): - p.logf("assignments: the marker %s of %s cannot be read, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket, err) + p.logf("assignments: the marker %s of %s cannot be read, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket(), err) return false } names, err := old.Keys(ctx) if err != nil && !errors.Is(err, jetstream.ErrNoKeysFound) { - p.logf("assignments: the keys of %s cannot be listed, and are tried again at the next pass: %v", retiredBucket, err) + p.logf("assignments: the keys of %s cannot be listed, and are tried again at the next pass: %v", retiredBucket(), err) return false } // A node's module keys before its node key: the node key is the commit the readers wait for. @@ -120,13 +118,13 @@ func (p *passer) copyRetired(ctx context.Context) bool { return false default: said.Copied++ - p.w.putEntry(plc.AssignmentsBucket, key, entry.Value(), rev, p.clock()) + p.w.putEntry(plc.AssignmentsBucket(), key, entry.Value(), rev, p.clock()) } } } mark, _ := json.Marshal(copiedMark{At: p.clock(), Copied: said.Copied + said.Present}) if _, err := copies.Put(ctx, copiedMarker, mark); err != nil { - p.logf("assignments: the end of the copy is not written as %s in %s, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket, err) + p.logf("assignments: the end of the copy is not written as %s in %s, and the copy is tried again at the next pass: %v", copiedMarker, copiesBucket(), err) return false } p.copied = true @@ -134,7 +132,7 @@ func (p *passer) copyRetired(ctx context.Context) bool { return true // the retired state holds nothing } p.logf("assignments: copied %d keys of %s into %s (%d already there kept as they are, %d of the form placement. not copied, %d fitting no form left)", - said.Copied, retiredBucket, plc.AssignmentsBucket, said.Present, said.Dropped, said.Skipped) + said.Copied, retiredBucket(), plc.AssignmentsBucket(), said.Present, said.Dropped, said.Skipped) if p.seat != nil { if err := p.seat.bus.Emit(CopiedEvent, said); err != nil { p.logf("assignments: the copy is not said as an event: %v", err) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits.go index 4a883737..dcdfdb3e 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits.go @@ -220,11 +220,11 @@ func (p *passer) orderActing(module string, released map[string]bool, unread map // orderWaits keeps the order waits after the pass's wants, as actingWaits keeps the acting waits: it starts the wait of each edge not met, // ends the wait of one that ran, was replaced or was released, and says each. func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[string]map[string]mwc.Want) { - if p.kvs[buckets.Waits] == nil { + if p.kvs[buckets.Waits()] == nil { return } now := p.clock() - kept := p.w.read(buckets.Waits) + kept := p.w.read(buckets.Waits()) trunk := p.w.trunk() if p.orderSaid == nil { p.orderSaid = map[string]bool{} @@ -242,7 +242,7 @@ func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[stri if w, err := acting.Decode(raw); err == nil && w.Entry == e.entry && w.Fp == e.fp { if !w.Released && !p.orderSaid[key] { // A wait still held after a restart is said again, as wait-held (A-260). - if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module), key+"."+e.fp+"."+e.entry+".again"); err != nil { + if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module()), key+"."+e.fp+"."+e.entry+".again"); err != nil { p.logf("the wait of %s on %s for %s is not said again, and is said at the next pass: %v", e.module, e.node, e.part, err) } else { p.orderSaid[key] = true @@ -257,11 +257,11 @@ func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[stri } w := acting.Wait{Step: seatdef.StepPlace, Node: e.node, Module: e.module, Part: e.part, PartNode: e.partNode, Fp: e.fp, Entry: e.entry, Order: e.order, Since: now, ClockFrom: now, Bound: acting.BoundOf(seat.Edge{Step: seatdef.StepPlace}).String()} - if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module), key+"."+e.fp+"."+e.entry); err != nil { + if err := p.wantsSay(seat.EventWaitHeld, acting.Body(w, assignment.Module()), key+"."+e.fp+"."+e.entry); err != nil { p.logf("the wait of %s on %s for %s is not said, and is said at the next pass: %v", e.module, e.node, e.part, err) continue } - p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return acting.Encode(w) }) + p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return acting.Encode(w) }) p.orderSaid[key] = true } for key, raw := range kept { @@ -283,14 +283,14 @@ func (p *passer) orderWaits(ctx context.Context, n assign.Nodes, wanted map[stri how = acting.HowReplaced } if !w.EndSaid { - body := acting.Body(w, assignment.Module) + body := acting.Body(w, assignment.Module()) body.How = how if err := p.wantsSay(seat.EventWaitEnded, body, key+"."+w.Fp+"."+w.Entry+"."+how); err != nil { p.logf("the end of the wait of %s on %s for %s is not said, and is said at the next pass: %v", w.Module, w.Node, w.Part, err) continue } } - p.remove(ctx, buckets.Waits, key) + p.remove(ctx, buckets.Waits(), key) delete(p.orderSaid, key) } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits_test.go index ad5b0d12..6a2a9ee4 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/orderwaits_test.go @@ -60,7 +60,7 @@ func newOrderFixture(t *testing.T, mods []orderMod, history ...map[string]string t.Helper() ctx := context.Background() f := &orderFixture{t: t, wants: &memKV{values: map[string][]byte{}}, answers: make(chan struct{}, 64)} - f.p = newPasser(kvSet{buckets.Wants: f.wants, buckets.Asks: &memKV{values: map[string][]byte{}}, buckets.Waits: &memKV{values: map[string][]byte{}}}, time.Now()) + f.p = newPasser(kvSet{buckets.Wants(): f.wants, buckets.Asks(): &memKV{values: map[string][]byte{}}, buckets.Waits(): &memKV{values: map[string][]byte{}}}, time.Now()) p := f.p p.logf = t.Logf p.say = func(oc.Condition) error { return nil } @@ -97,7 +97,7 @@ func newOrderFixture(t *testing.T, mods []orderMod, history ...map[string]string f.runs(m.name, m.run) wkey, _ := mwc.WantKey("ace", m.name) old, _ := mwc.EncodeWant(mwc.Want{Fp: m.run, Self: true}) - p.w.put(buckets.Wants, wkey, old) + p.w.put(buckets.Wants(), wkey, old) f.wants.values[wkey] = old } composes(t, p.w, "ace", digest) @@ -106,7 +106,7 @@ func newOrderFixture(t *testing.T, mods []orderMod, history ...map[string]string t.Fatal(err) } for key, raw := range values { - p.w.put(plc.AssignmentsBucket, key, raw) + p.w.put(plc.AssignmentsBucket(), key, raw) } p.w.heardPresence("ace", false, time.Now()) // ace is online: its last presence event heard is machine-back objects := memObjects{} @@ -180,7 +180,7 @@ func (f *orderFixture) wantAt(module string) string { func (f *orderFixture) waits() []string { var out []string - for key := range f.p.w.read(buckets.Waits) { + for key := range f.p.w.read(buckets.Waits()) { if acting.IsOrder(key) { out = append(out, key) } @@ -212,7 +212,7 @@ func TestAModuleWhoseKeptDataChangesFormWaitsForTheNewBackupHolderAndTheBusModul if len(f.waits()) != 2 || len(f.events) != 2 || f.events[0] != meshwants.EventWaitHeld || f.bodies[0].Step != seatdef.StepPlace || f.bodies[0].Module != "web" { t.Fatalf("waits %v events %v bodies %+v: want two wait-held in the step place, `order.ace.web.backup` and `order.ace.web.bus`", f.waits(), f.events, f.bodies) } - if _, err := acting.Decode(f.p.w.read(buckets.Waits)[acting.OrderKey("ace", "web", "backup")]); err != nil { + if _, err := acting.Decode(f.p.w.read(buckets.Waits())[acting.OrderKey("ace", "web", "backup")]); err != nil { t.Fatalf("the wait is not kept under its key: %v", err) } f.pass() @@ -241,7 +241,7 @@ func TestAnOrderWaitIsReadAndReleasedByTheExistingTools(t *testing.T) { f.pass() f.pass() var acts []string - s := newPutTools(memStore{waitsState: f.p.kvs[buckets.Waits].(*memKV), buckets.DeliveryWaitsState: &memKV{values: map[string][]byte{}}, buckets.DeliveredAgainState: &memKV{values: map[string][]byte{}}}, func(key string, body any) (json.RawMessage, error) { + s := newPutTools(memStore{waitsState: f.p.kvs[buckets.Waits()].(*memKV), buckets.DeliveryWaitsState: &memKV{values: map[string][]byte{}}, buckets.DeliveredAgainState: &memKV{values: map[string][]byte{}}}, func(key string, body any) (json.RawMessage, error) { acts = append(acts, key) return json.RawMessage(`{"ok":true,"log":"logged"}`), nil }) @@ -252,7 +252,7 @@ func TestAnOrderWaitIsReadAndReleasedByTheExistingTools(t *testing.T) { if _, err := s.doRelease(map[string]any{"node": "ace", "module": "web", "how": "now", "why": "the restore point is taken by hand"}, "shanks.node-tools"); err != nil || len(acts) == 0 { t.Fatalf("%v %v", err, acts) } - f.p.w.put(buckets.Waits, "order.ace.web.backup", f.p.kvs[buckets.Waits].(*memKV).values["order.ace.web.backup"]) + f.p.w.put(buckets.Waits(), "order.ace.web.backup", f.p.kvs[buckets.Waits()].(*memKV).values["order.ace.web.backup"]) f.pass() f.pass() if got := f.wantAt("web"); got != orderFP("b") || f.bodies[len(f.bodies)-1].How != acting.HowReleased { diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside.go index 6fc18759..ad477697 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside.go @@ -48,7 +48,7 @@ func wantsPublisher(ctx context.Context, js jetstream.JetStream, node string) pu } msg := nats.NewMsg(seatdef.EventSubject(meshwants.Seat, event)) msg.Header.Set("x-event-id", id) - msg.Header.Set("x-source", assignment.Module) + msg.Header.Set("x-source", assignment.Module()) msg.Header.Set("x-node", node) msg.Header.Set("x-time", time.Now().UTC().Format(time.RFC3339Nano)) msg.Header.Set("content-type", "application/json") @@ -236,7 +236,7 @@ func (o *outsideActs) take(machine string) ([]outside.Cond, bool) { // events, finding and ask. moved says the delivery moved owner to node. func (p *passer) outsideReview(ctx context.Context, module, fp string, nodes []string, n assign.Nodes, keeperSelf bool, moved func(owner, node string) bool) assign.Nodes { o := p.outside - if o == nil || fp == "" || p.kvs[buckets.Waits] == nil { + if o == nil || fp == "" || p.kvs[buckets.Waits()] == nil { return n } hex, err := catalogkey.KeyPart(fp) @@ -244,7 +244,7 @@ func (p *passer) outsideReview(ctx context.Context, module, fp string, nodes []s return n } now := p.clock() - kept := p.w.read(buckets.Waits) + kept := p.w.read(buckets.Waits()) override := map[string]string{} // A wait of another fingerprint of this module is replaced by the newer want. for key, raw := range kept { @@ -409,12 +409,12 @@ func containsString(l []string, s string) bool { // keepWait writes the wait's record. func (p *passer) keepWait(ctx context.Context, key string, w outside.Wait) { - p.put(ctx, buckets.Waits, key, func() ([]byte, error) { return outside.Encode(w) }) + p.put(ctx, buckets.Waits(), key, func() ([]byte, error) { return outside.Encode(w) }) } func (p *passer) outsideBody(w outside.Wait) meshwants.Wait { since, from := w.Since, w.ClockFrom - return meshwants.Wait{Step: meshwants.WaitStepOutside, Owner: assignment.Module, Node: w.Node, Module: w.Module, Fingerprint: w.FP, On: w.On, + return meshwants.Wait{Step: meshwants.WaitStepOutside, Owner: assignment.Module(), Node: w.Node, Module: w.Module, Fingerprint: w.FP, On: w.On, Since: since, ClockFrom: &from, Bound: w.Bound, Words: fmt.Sprintf("%s on %s waits on %s", w.Module, w.Node, w.On)} } @@ -432,14 +432,14 @@ func (p *passer) outsideEnd(ctx context.Context, key string, w outside.Wait, how } p.outsideFinding(w, false) if ask, err := asks.OutsideAskKey(w.Node, w.Module, w.FP); err == nil { - p.remove(ctx, buckets.Asks, ask) + p.remove(ctx, buckets.Asks(), ask) } if p.outside != nil && w.Cause != "" { p.outside.mu.Lock() p.outside.causes[w.Cause] = removeString(p.outside.causes[w.Cause], w.Node) p.outside.mu.Unlock() } - p.remove(ctx, buckets.Waits, key) + p.remove(ctx, buckets.Waits(), key) } func removeString(l []string, s string) []string { @@ -483,7 +483,7 @@ func (p *passer) outsideAsk(ctx context.Context, node, module, fp string, w outs } asking := assign.AskingSeconds(now) a := assign.OutsideAsk(key, asks.OutsideAbout(key), node, module, fp, w.On, w.ClockFrom, asking, now) - p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) + p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) if ask, err := assign.AskOf(a); err == nil { return ask.ID } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go index 4fbac555..04cf6c25 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/outside_test.go @@ -38,9 +38,9 @@ type outsideRig struct { func newOutsideRig(t *testing.T) *outsideRig { r := &outsideRig{t: t, now: time.Date(2026, 11, 11, 10, 0, 0, 0, time.UTC), found: map[string]bool{}, woke: make(chan struct{}, 16), health: nodeengineclient.HealthFailed} - r.p = newPasser(kvSet{buckets.Waits: &memKV{values: map[string][]byte{}}, buckets.Asks: &memKV{values: map[string][]byte{}}}, r.now) + r.p = newPasser(kvSet{buckets.Waits(): &memKV{values: map[string][]byte{}}, buckets.Asks(): &memKV{values: map[string][]byte{}}}, r.now) r.p.now = func() time.Time { return r.now } - r.p.w.put(buckets.Asks, "x", nil) + r.p.w.put(buckets.Asks(), "x", nil) r.p.outside = newOutsideActs(func(key string, body any) (json.RawMessage, error) { if r.unread { return nil, context.DeadlineExceeded @@ -82,7 +82,7 @@ func (r *outsideRig) review(keeperSelf bool, moved func(string, string) bool) as func (r *outsideRig) wait() outside.Wait { r.t.Helper() - for _, v := range r.p.w.read(buckets.Waits) { + for _, v := range r.p.w.read(buckets.Waits()) { w, err := outside.Decode(v) if err != nil { r.t.Fatal(err) @@ -113,7 +113,7 @@ func TestTheWaitOnAnOutsideCause(t *testing.T) { r.p.outside.Heard("mesh.seat.mesh-conditions.event.condition-cleared", []byte(`{"key":"machine.hub.unreachable","event":"cleared","change":"cleared","at":"2026-11-11T10:01:00Z","subject":{"id":"hub","scope":"machine","machine":"hub","also":["ace"]}}`)) <-r.woke r.review(false, nil) - if len(r.events) != 2 || r.events[1] != meshwants.EventWaitEnded || r.bodies[1].How != outside.HowCleared || len(r.found) != 0 || len(r.p.w.read(buckets.Waits)) != 0 { + if len(r.events) != 2 || r.events[1] != meshwants.EventWaitEnded || r.bodies[1].How != outside.HowCleared || len(r.found) != 0 || len(r.p.w.read(buckets.Waits())) != 0 { t.Errorf("events %v bodies %+v findings %v", r.events, r.bodies, r.found) } }) @@ -175,7 +175,7 @@ func TestTheWaitOnAnOutsideCause(t *testing.T) { <-r.woke r.review(false, nil) key, _ := asks.OutsideAskKey("ace", "nats", fpTest) - raw := r.p.w.value(buckets.Asks, key) + raw := r.p.w.value(buckets.Asks(), key) if raw == nil { t.Fatalf("%s: no ask at %s", how, key) } @@ -189,7 +189,7 @@ func TestTheWaitOnAnOutsideCause(t *testing.T) { if r.events[len(r.events)-1] != meshwants.EventWaitEnded || r.bodies[len(r.bodies)-1].How != how || healthOf(n) != want { t.Errorf("%s: events %v last %+v health %s", how, r.events, r.bodies[len(r.bodies)-1], healthOf(n)) } - if r.p.w.value(buckets.Asks, key) != nil || len(r.found) != 0 { + if r.p.w.value(buckets.Asks(), key) != nil || len(r.found) != 0 { t.Errorf("%s: the ask or the finding stands after the end", how) } // a check that goes on failing is not waited on again @@ -263,7 +263,7 @@ func TestTheToolsReadAndReleaseAnOutsideWait(t *testing.T) { acts = append(acts, key) return json.RawMessage(`{"ok":true,"log":"logged"}`), nil }) - for k, v := range r.p.w.read(buckets.Waits) { + for k, v := range r.p.w.read(buckets.Waits()) { s.st.put(waitsState, k, v) } got, err := s.doWaits(map[string]any{"module": "nats"}) @@ -291,7 +291,7 @@ func TestTheToolsReadAndReleaseAnOutsideWait(t *testing.T) { t.Errorf("wait = %+v", w) } // the process takes the release at its next pass: ended go-on, counted healthy - r.p.w.put(buckets.Waits, keys[0], raw) + r.p.w.put(buckets.Waits(), keys[0], raw) n := r.review(false, nil) if r.bodies[len(r.bodies)-1].How != "go-on" || n.Haves["ace"]["nats"].Health != nodeengineclient.HealthOK { t.Errorf("last %+v health %s", r.bodies[len(r.bodies)-1], n.Haves["ace"]["nats"].Health) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/ownmodule_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/ownmodule_test.go new file mode 100644 index 00000000..b8c8f079 --- /dev/null +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/ownmodule_test.go @@ -0,0 +1,6 @@ +package main + +import plc "mesh-delivery-assigner/internal/assignment" + +// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296). +func init() { plc.SetModule("mesh-delivery-assigner") } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go index db2d1d79..fc67fab7 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go @@ -226,7 +226,7 @@ func (p *passer) pass(ctx context.Context) { // nodeWants writes each placed node's own want, `want..node`: the node composes its own (ADR 0547 §2, ADR 0571 §6). func (p *passer) nodeWants(ctx context.Context) { - written := p.w.read(buckets.Wants) + written := p.w.read(buckets.Wants()) placed := p.w.placedNodes() // A node the catalogue places composes its own composition (ADR 0547 §2): its own want names none. for node := range placed { @@ -238,7 +238,7 @@ func (p *passer) nodeWants(ctx context.Context) { continue } want := mwc.NodeWant{Self: true, Words: "the node composes its own composition (ADR 0547 §2)"} - p.put(ctx, buckets.Wants, key, func() ([]byte, error) { return mwc.EncodeNodeWant(want) }) + p.put(ctx, buckets.Wants(), key, func() ([]byte, error) { return mwc.EncodeNodeWant(want) }) } } @@ -269,7 +269,7 @@ func (p *passer) wants(ctx context.Context) { } } wanted := p.w.wantsOf() - chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket)) + chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket())) provisionsOf := p.provisionsOf() missing := map[string]bool{} defer func() { p.provisionsMissing(ctx, missing) }() @@ -348,7 +348,7 @@ func (p *passer) wants(ctx context.Context) { m.KeptBuilds = p.namedBuild(name, m.FP) m.Released = p.answeredWith(hold, "release") m.Kept = p.answeredWith(hold, "keep") - m.Built, m.Held, m.Granted = p.w.built(name, fp), p.w.has(buckets.Wants, held) && !m.Released, p.w.has(grants, grant) + m.Built, m.Held, m.Granted = p.w.built(name, fp), p.w.has(buckets.Wants(), held) && !m.Released, p.w.has(grants, grant) m.BuildFailed, m.BuildError = p.w.buildStanding(name, fp) if raw := p.w.value(grants, grant); raw != nil { if g, err := grantsstate.DecodeGrant(raw); err == nil { @@ -396,7 +396,7 @@ func (p *passer) wants(ctx context.Context) { key, err := mwc.HeldKey(name, fp) if err == nil { held := *out.Held - p.put(ctx, buckets.Wants, key, func() ([]byte, error) { return mwc.EncodeHeld(held) }) + p.put(ctx, buckets.Wants(), key, func() ([]byte, error) { return mwc.EncodeHeld(held) }) } } if out.Lifted { @@ -698,11 +698,11 @@ func (p *passer) putWant(ctx context.Context, node, module string, want mwc.Want } fp := want.Fp if want.Removed { - if before, err := mwc.DecodeWant(p.w.value(buckets.Wants, key)); err == nil { + if before, err := mwc.DecodeWant(p.w.value(buckets.Wants(), key)); err == nil { fp = before.Fp } } - if p.written(ctx, buckets.Wants, key, func() ([]byte, error) { return mwc.EncodeWant(want) }) { + if p.written(ctx, buckets.Wants(), key, func() ([]byte, error) { return mwc.EncodeWant(want) }) { p.sayWanted(node, module, fp, want.Removed) } } @@ -712,7 +712,7 @@ func (p *passer) putWant(ctx context.Context, node, module string, want mwc.Want // node (wants are only compared, ADR 0429 §7), and the options say so. func (p *passer) holdAsk(ctx context.Context, module, fp string, fresh *mwc.Held) { key, err := asks.HoldAskKey(module, fp) - if err != nil || p.w.has(buckets.Asks, key) { + if err != nil || p.w.has(buckets.Asks(), key) { return } // The asking is the hold's time in seconds: a hold of the same fingerprint written again after a lift is another asking, under @@ -721,7 +721,7 @@ func (p *passer) holdAsk(ctx context.Context, module, fp string, fresh *mwc.Held asking := int(now.Unix()) h := p.heldHold(module, fp, fresh) a := assign.HoldAsk(key, module, fp, h.Node, h.Words, asking, now) - p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) + p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) } // lift takes away a hold the assigner lifted by itself, since every node that runs the module runs the held fingerprint healthy @@ -749,13 +749,13 @@ func (p *passer) lift(ctx context.Context, module, fp string) { p.said[c.Key+".lifted"] = true delete(p.said, c.Key+".raised") // a hold written again is said again } - p.remove(ctx, buckets.Wants, held) - p.remove(ctx, buckets.Asks, ask) + p.remove(ctx, buckets.Wants(), held) + p.remove(ctx, buckets.Asks(), ask) // The restore asks of that fingerprint, on every node, go with the hold: every node runs it healthy, so no node waits to be // set back from it (issue 1906, note 28). - for key := range p.w.read(buckets.Asks) { + for key := range p.w.read(buckets.Asks()) { if k, err := asks.ParseAnyAskKey(key); err == nil && k.Kind == asks.AskKindRestore && k.Module == module && k.FP == fp { - p.remove(ctx, buckets.Asks, key) + p.remove(ctx, buckets.Asks(), key) } } } @@ -778,7 +778,7 @@ func (p *passer) staleAsk(key string, trunk map[string]meshcatalog.ModulesByModu if err != nil { return "" } - raw := p.w.value(buckets.Wants, wkey) + raw := p.w.value(buckets.Wants(), wkey) if raw == nil { return "" } @@ -843,7 +843,7 @@ func (p *passer) yesWritten(k asks.AskKey) bool { if err != nil { return false } - w, err := mwc.DecodeWant(p.w.value(buckets.Wants, key)) + w, err := mwc.DecodeWant(p.w.value(buckets.Wants(), key)) return err == nil && w.Yes == k.Composed } @@ -858,7 +858,7 @@ func (p *passer) fingerprintsOf(node, module, trunkFP string) []string { } add(trunkFP) if key, err := mwc.WantKey(node, module); err == nil { - if w, err := mwc.DecodeWant(p.w.value(buckets.Wants, key)); err == nil { + if w, err := mwc.DecodeWant(p.w.value(buckets.Wants(), key)); err == nil { add(w.Fp) } } @@ -877,7 +877,7 @@ func (p *passer) staleAsks(ctx context.Context, trunk map[string]meshcatalog.Mod if len(trunk) == 0 { return } - for key := range p.w.read(buckets.Asks) { + for key := range p.w.read(buckets.Asks()) { if why := p.staleAsk(key, trunk); why != "" { // The condition is cleared first and the ask goes only once that was said: the ask is what brings this key back at the // next pass, so a clear that failed once is made again, with no state of its own (issue 1906, note 29). @@ -899,7 +899,7 @@ func (p *passer) staleAsks(ctx context.Context, trunk map[string]meshcatalog.Mod p.revertFinding(assign.RevertHeldFinding(k.Node, k.Module, "", "", ""), false) } p.logf("the ask %s is about a fingerprint or composition the trunk has left behind and is taken away: %s", key, why) - p.remove(ctx, buckets.Asks, key) + p.remove(ctx, buckets.Asks(), key) // The held record stays: it is the memory that this fingerprint failed (ADR 0408 §4), and a revert returns the trunk to // exactly it. Only the condition, which would else ask for an answer that can no longer be given, is cleared, and it is // raised again if the trunk returns to the fingerprint. @@ -932,7 +932,7 @@ func (p *passer) heldHold(module, fp string, fresh *mwc.Held) mwc.Held { if err != nil { return mwc.Held{} } - h, _ := mwc.DecodeHeld(p.w.read(buckets.Wants)[key]) + h, _ := mwc.DecodeHeld(p.w.read(buckets.Wants())[key]) return h } @@ -956,13 +956,13 @@ func (p *passer) heldCondition(ctx context.Context, module, fp string, fresh *mw // restoreAsk writes, once, the ask about a module holding data whose held fingerprint failed its health check on node. func (p *passer) restoreAsk(ctx context.Context, module, node, fp string) { key, err := asks.RestoreAskKey(node, module, fp) - if err != nil || p.w.has(buckets.Asks, key) { + if err != nil || p.w.has(buckets.Asks(), key) { return } words := p.heldHold(module, fp, nil).Words now := p.clock() a := assign.RestoreAsk(key, node, module, fp, words, assign.AskingSeconds(now), now) - p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) + p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) } // answeredWith says whether the teller wrote an answer to the ask at key as it stands now and the option chosen is option. The @@ -974,7 +974,7 @@ func (p *passer) answeredWith(key, option string) bool { if !ok || a.Option != option { return false } - _, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks, key)) + _, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks(), key)) if err != nil { return false } @@ -1000,12 +1000,12 @@ func (p *passer) clearHeld(module, fp string) bool { // recordAsk writes, once, the ask about a move of a module of policy record that waits for the operator's yes. func (p *passer) recordAsk(ctx context.Context, module, fp string, need assign.RecordNeed) { key, err := asks.RecordAskKey(need.Node, module, need.Digest) - if err != nil || p.w.has(buckets.Asks, key) { + if err != nil || p.w.has(buckets.Asks(), key) { return } now := p.clock() a := assign.RecordAsk(key, need.Node, module, fp, need, assign.AskingSeconds(now), now) - p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) + p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) } // backups sees to the modules held on a node because the backup before an apply failed (the have's phase held, ADR 0539 §16): @@ -1038,7 +1038,7 @@ func (p *passer) backups(ctx context.Context, reverting map[string]bool) { if err != nil { continue } - current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks)[akey]) + current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks())[akey]) if there != nil || assign.BackupAskDigest(current) != want.Composed { after := -1 if there == nil { @@ -1075,7 +1075,7 @@ func (p *passer) backups(ctx context.Context, reverting map[string]bool) { // backupAsk writes the backup ask at its asking and raises the condition of the failed backup, once. func (p *passer) backupAsk(ctx context.Context, key, node, module string, want mwc.Want, words string, asking int) { a := assign.BackupAsk(key, node, module, want.Fp, want.Composed, words, asking, time.Now()) - p.put(ctx, buckets.Asks, key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) + p.put(ctx, buckets.Asks(), key, func() ([]byte, error) { return assign.EncodeAsk(key, a) }) c := assign.BackupCondition(node, module, want.Fp, words, true) if p.say == nil || p.said[c.Key] { return @@ -1227,7 +1227,7 @@ func missingPulse(key string) string { return "provision-missing." + key } // `provider....provision-missing` is written; one keyed again ends both. The pulse's expiry, heard on // the watch, is the pass that writes it: no timer. func (p *passer) provisionsMissing(ctx context.Context, missing map[string]bool) { - pulses := p.kvs[plc.PulsesBucket] + pulses := p.kvs[plc.PulsesBucket()] if p.missing == nil { p.missing = map[string]bool{} } @@ -1263,7 +1263,7 @@ func (p *passer) provisionsMissing(ctx context.Context, missing map[string]bool) delete(p.missing, key) if pulses != nil && p.pulsing(missingPulse(key)) { if err := pulses.Delete(ctx, missingPulse(key)); err == nil { - p.w.put(plc.PulsesBucket, missingPulse(key), nil) + p.w.put(plc.PulsesBucket(), missingPulse(key), nil) } } if p.missingSaid[key] && p.findings.Delete != nil { @@ -1318,7 +1318,7 @@ func (p *passer) unplacedFindings() { // assignments holds any more deleted. A pass follows each assignments change and each manifest the indexer takes in, so no // timer writes it. func (p *passer) seatHolders(ctx context.Context) { - kv := p.kvs[plc.SeatHoldersBucket] + kv := p.kvs[plc.SeatHoldersBucket()] if kv == nil { return } @@ -1335,13 +1335,13 @@ func (p *passer) seatHolders(ctx context.Context) { return } want := plc.SeatHoldersOf(assignments) - have := p.w.read(plc.SeatHoldersBucket) + have := p.w.read(plc.SeatHoldersBucket()) for key, sh := range want { value, err := json.Marshal(sh) if err != nil || string(have[key]) == string(value) { continue } - p.put(ctx, plc.SeatHoldersBucket, key, func() ([]byte, error) { return value, nil }) + p.put(ctx, plc.SeatHoldersBucket(), key, func() ([]byte, error) { return value, nil }) } for key := range have { if _, ok := want[key]; ok { @@ -1351,7 +1351,7 @@ func (p *passer) seatHolders(ctx context.Context) { p.logf("the seat holders %s are not deleted: %v", key, err) continue } - p.w.put(plc.SeatHoldersBucket, key, nil) + p.w.put(plc.SeatHoldersBucket(), key, nil) } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go index 426c570e..f29693ec 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass_test.go @@ -86,7 +86,7 @@ func placeOn(t *testing.T, w *world, node string, modules ...string) { t.Fatal(err) } for key, raw := range values { - w.put(plc.AssignmentsBucket, key, raw) + w.put(plc.AssignmentsBucket(), key, raw) } } @@ -108,7 +108,7 @@ var fpTest = meshcatalogclient.SourcePrefix2 + strings.Repeat("b", 64) // an id it keeps). func TestAHoldAskedAgainAfterALiftAndARestartHasANewID(t *testing.T) { asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}} - kvs := kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV} + kvs := kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV} key, _ := asks.HoldAskKey("web", fpTest) held := &mwc.Held{Node: "ace", Words: "health failed"} first := newPasser(kvs, time.Date(2026, 10, 25, 1, 0, 0, 0, time.UTC)) @@ -167,7 +167,7 @@ func TestAHoldAskedAgainAfterALiftAndARestartHasANewID(t *testing.T) { } { t.Run("ended backup ask: "+c.name, func(t *testing.T) { bkv := &memKV{values: map[string][]byte{}} - pb := newPasser(kvSet{buckets.Asks: bkv, buckets.Wants: &memKV{values: map[string][]byte{}}}, now) + pb := newPasser(kvSet{buckets.Asks(): bkv, buckets.Wants(): &memKV{values: map[string][]byte{}}}, now) ba := assign.BackupAsk(backKey, "ace", "web", fpTest, "sha256:"+strings.Repeat("d", 64), "w", 2, now) bv, err := assign.EncodeAsk(backKey, ba) if err != nil { @@ -177,7 +177,7 @@ func TestAHoldAskedAgainAfterALiftAndARestartHasANewID(t *testing.T) { if c.want != nil { wkey, _ := mwc.WantKey("ace", "web") raw, _ := mwc.EncodeWant(*c.want) - pb.w.put(buckets.Wants, wkey, raw) + pb.w.put(buckets.Wants(), wkey, raw) } _, bask, _ := assign.DecodeAsk(backKey, bv) body, err := tellerevents.EncodeEnded(tellerevents.Ended{Key: backKey, Ask: bask.ID, Outcome: occ.OutcomeExpired}) @@ -250,7 +250,7 @@ func TestAnAnswerCountsOnlyForTheAskingItAnswered(t *testing.T) { if err != nil { t.Fatal(err) } - p.w.put(buckets.Asks, key, v) + p.w.put(buckets.Asks(), key, v) return mustAskID(a) } first := put(100) @@ -266,7 +266,7 @@ func TestAnAnswerCountsOnlyForTheAskingItAnswered(t *testing.T) { if p.answeredWith(key, "release") { t.Error("the answer to an earlier hold carried over to a later one") } - p.w.put(buckets.Asks, key, nil) // the ask taken away + p.w.put(buckets.Asks(), key, nil) // the ask taken away if p.answeredWith(key, "release") { t.Error("an answer to an ask taken away was taken") } @@ -492,7 +492,7 @@ func (b *fakeBus) Emit(eventType string, body any) error { func aPlacementsBuildIsPut(t *testing.T) { ctx := context.Background() putsKV := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Puts: putsKV}, time.Date(2026, 11, 10, 18, 0, 0, 0, time.UTC)) + p := newPasser(kvSet{buckets.Puts(): putsKV}, time.Date(2026, 11, 10, 18, 0, 0, 0, time.UTC)) earlier := meshcatalogclient.SourcePrefix2 + strings.Repeat("e", 64) place := func(build string) { values, err := mmac.AssignmentsValues(mma.Assignments{Node: "g14", Modules: []mma.PlacedModule{{Module: "web", Build: build}, {Module: "db"}}}) @@ -500,7 +500,7 @@ func aPlacementsBuildIsPut(t *testing.T) { t.Fatal(err) } for key, raw := range values { - p.w.putEntry(plc.AssignmentsBucket, key, raw, uint64(len(raw)), time.Now()) + p.w.putEntry(plc.AssignmentsBucket(), key, raw, uint64(len(raw)), time.Now()) } } read := func() (puts.Put, bool) { @@ -536,7 +536,7 @@ func aPlacementsBuildIsPut(t *testing.T) { person := puts.Put{FP: earlier, Hold: true, Why: "a try", By: "lead@shanks", At: time.Now()}.Said(time.Now(), puts.Holding, "held") raw, _ := puts.Encode(person) putsKV.values["g14.web"] = raw - p.w.put(buckets.Puts, "g14.web", raw) + p.w.put(buckets.Puts(), "g14.web", raw) p.placementPuts(ctx) if got, _ := read(); got.Step != puts.Holding || got.By != "lead@shanks" { t.Errorf("a person's put was released by the placement: %+v", got) @@ -554,7 +554,7 @@ func putOfTheBus(t *testing.T) { digest := "sha256:" + strings.Repeat("d", 64) mem := func() *memKV { return &memKV{values: map[string][]byte{}} } putsKV, asksKV, wantsKV := mem(), mem(), mem() - kvs := kvSet{buckets.Puts: putsKV, buckets.Asks: asksKV, buckets.Wants: wantsKV} + kvs := kvSet{buckets.Puts(): putsKV, buckets.Asks(): asksKV, buckets.Wants(): wantsKV} p := newPasser(kvs, now) bus := &fakeBus{calls: make(chan string, 8)} woke := make(chan struct{}, 8) // each act's goroutine wakes a pass once it is done @@ -617,7 +617,7 @@ func putOfTheBus(t *testing.T) { pkey, _ := puts.Key("novox", "nats") step := func() puts.Put { t.Helper() - world(buckets.Puts, pkey, putsKV.values[pkey]) + world(buckets.Puts(), pkey, putsKV.values[pkey]) p.pass(ctx) got, err := puts.Decode(putsKV.values[pkey]) if err != nil { @@ -649,7 +649,7 @@ func putOfTheBus(t *testing.T) { if _, there := wantsKV.values["want.novox.nats"]; there { t.Fatal("the bus's want was written before the operator's warrant") } - world(buckets.Asks, akey, asksKV.values[akey]) + world(buckets.Asks(), akey, asksKV.values[akey]) yes, _ := mdc.EncodeAnswer(mdc.Answer{Option: assign.OptionYes, Warrant: a.Ask.ID + "@2026-10-25T22:01:00Z", Words: "the operator, verified"}) world(buckets.Answers, akey, yes) if got := step(); got.Step != puts.Snapshot { @@ -664,7 +664,7 @@ func putOfTheBus(t *testing.T) { if call := <-bus.calls; !strings.HasPrefix(call, "seat:mesh-hand-acts.warranted ") || !strings.Contains(call, a.Ask.ID) { // ADR 0565 §11: into the seat t.Errorf("the warrant was recorded as %q", call) } - world(buckets.Wants, "want.novox.nats", wantsKV.values["want.novox.nats"]) + world(buckets.Wants(), "want.novox.nats", wantsKV.values["want.novox.nats"]) // Design 47 G §309: the put ends on the node-engine seat's running naming its fingerprint, never on the have. haveRunning, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fp, Composed: digest, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK}) world(buckets.Have, hkey, haveRunning) @@ -715,7 +715,7 @@ func putOfTheBus(t *testing.T) { if again.Step != puts.Person || err != nil || a2.Ask.ID == a.Ask.ID { t.Fatalf("put again, it stands at %s (%s); its ask %q, %v; the first ask was %q", again.Step, again.Words, a2.Ask.ID, err, a.Ask.ID) } - world(buckets.Asks, akey, asksKV.values[akey]) + world(buckets.Asks(), akey, asksKV.values[akey]) yes2, _ := mdc.EncodeAnswer(mdc.Answer{Option: assign.OptionYes, Warrant: a2.Ask.ID + "@2026-10-25T22:03:00Z", Words: "the operator, verified"}) world(buckets.Answers, akey, yes2) if got := step(); got.Step != puts.Snapshot { @@ -725,7 +725,7 @@ func putOfTheBus(t *testing.T) { // Released between the pass's read and its write: the pass writes no want for the put that no longer stands. oldWant, _ := mwc.EncodeWant(mwc.Want{Fp: meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64)}) wantsKV.values["want.novox.nats"] = oldWant - world(buckets.Wants, "want.novox.nats", oldWant) + world(buckets.Wants(), "want.novox.nats", oldWant) if _, err := s.doRelease(map[string]any{"node": "novox", "module": "nats", "why": "changed my mind"}, "shanks.node-tools"); err != nil { t.Fatal(err) } @@ -911,7 +911,7 @@ func testThePassGathersTheModulesOnNeitherList(t *testing.T) { func TestTheFirstPassAfterAStartClearsWhatAPreviousProcessRaised(t *testing.T) { t.Run("a held module", testAHeldModuleRaisesNoNeitherAndClearsAStandingOne) wantsKV := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Wants: wantsKV, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now()) + p := newPasser(kvSet{buckets.Wants(): wantsKV, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now()) var said []oc.Condition p.say = func(c oc.Condition) error { said = append(said, c); return nil } mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath}) @@ -923,7 +923,7 @@ func TestTheFirstPassAfterAStartClearsWhatAPreviousProcessRaised(t *testing.T) { p.w.put(buckets.Have, "g14.web", have) key, _ := mwc.WantKey("g14", "web") want, _ := mwc.EncodeWant(mwc.Want{Fp: fpTest, Composed: "sha256:" + strings.Repeat("c", 64)}) - p.w.put(buckets.Wants, key, want) + p.w.put(buckets.Wants(), key, want) p.wants(context.Background()) cleared := map[string]bool{} for _, c := range said { @@ -949,7 +949,7 @@ func TestTheFirstPassAfterAStartClearsWhatAPreviousProcessRaised(t *testing.T) { // raised (the first pass after a start), each once. func testAHeldModuleRaisesNoNeitherAndClearsAStandingOne(t *testing.T) { setup := func() (*passer, *[]oc.Condition) { - p := newPasser(kvSet{buckets.Wants: &memKV{values: map[string][]byte{}}, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now()) + p := newPasser(kvSet{buckets.Wants(): &memKV{values: map[string][]byte{}}, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now()) said := &[]oc.Condition{} p.say = func(c oc.Condition) error { *said = append(*said, c); return nil } mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/resolved", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath}) @@ -961,7 +961,7 @@ func testAHeldModuleRaisesNoNeitherAndClearsAStandingOne(t *testing.T) { p.w.put(buckets.Have, "shanks.systemd-resolved", have) hkey, _ := mwc.HeldKey("systemd-resolved", fpTest) held, _ := mwc.EncodeHeld(mwc.Held{Node: "shanks", Words: "health failed"}) - p.w.put(buckets.Wants, hkey, held) + p.w.put(buckets.Wants(), hkey, held) return p, said } key := assign.NeitherCondition("shanks", "systemd-resolved", assign.Holding{}, false).Key @@ -1034,7 +1034,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) { } { t.Run(c.name, func(t *testing.T) { asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now()) + p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now()) var said []oc.Condition p.say = func(c oc.Condition) error { said = append(said, c); return nil } for _, key := range all { @@ -1055,7 +1055,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) { t.Fatal(err) } asksKV.values[key] = v - p.w.put(buckets.Asks, key, v) + p.w.put(buckets.Asks(), key, v) } placeOn(t, p.w, "novox", "web") // a placed node composes its own: its module's have waits on a composition if !c.noDigit { @@ -1073,11 +1073,11 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) { if err != nil { t.Fatal(err) } - p.w.put(buckets.Wants, wkey, raw) + p.w.put(buckets.Wants(), wkey, raw) } heldOld, _ := mwc.HeldKey("web", fpOld) wantsKV.values[heldOld] = []byte("x") - p.w.put(buckets.Wants, heldOld, []byte("x")) + p.w.put(buckets.Wants(), heldOld, []byte("x")) firstAsk, _ := decodeAsk(holdOld, asksKV.values[holdOld]) p.staleAsks(context.Background(), c.trunk) // the held record and condition of a fingerprint whose hold ask went are taken away with it @@ -1119,7 +1119,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) { t.Run("a clear that failed to be said is said at the next pass, and the ask goes after it", func(t *testing.T) { asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now()) + p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now()) var said []oc.Condition fail := true p.say = func(c oc.Condition) error { @@ -1136,7 +1136,7 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) { t.Fatal(err) } asksKV.values[holdOld] = v - p.w.put(buckets.Asks, holdOld, v) + p.w.put(buckets.Asks(), holdOld, v) p.staleAsks(context.Background(), trunkOf(fpNew)) if _, there := asksKV.values[holdOld]; !there || len(said) != 0 { t.Fatalf("after a failed clear: ask left = %v, conditions said = %d; want the ask kept and nothing said", there, len(said)) @@ -1149,12 +1149,12 @@ func TestAsksOfAFingerprintTheTrunkLeftBehindAreTakenAway(t *testing.T) { t.Run("a lift takes the restore asks of its fingerprint on every node", func(t *testing.T) { asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now()) + p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now()) keep, _ := asks.RestoreAskKey("ace", "web", fpNew) keepOther, _ := asks.RestoreAskKey("ace", "other", fpOld) for _, key := range []string{restOld, mustKey(asks.RestoreAskKey("ace", "web", fpOld)), keep, keepOther} { asksKV.values[key] = []byte("x") - p.w.put(buckets.Asks, key, []byte("x")) + p.w.put(buckets.Asks(), key, []byte("x")) } p.lift(context.Background(), "web", fpOld) for key, gone := range map[string]bool{restOld: true, mustKey(asks.RestoreAskKey("ace", "web", fpOld)): true, keep: false, keepOther: false} { @@ -1189,7 +1189,7 @@ func aReleasesApproveAnswersTheMoveOfItsBuildAlone(t *testing.T) { waiting, was := "sha256:"+strings.Repeat("e", 64), "sha256:"+strings.Repeat("d", 64) commit := strings.Repeat("1", 40) asksKV := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV}, time.Now()) + p := newPasser(kvSet{buckets.Asks(): asksKV}, time.Now()) p.logf = t.Logf mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: commit, Src: 7, FpSrc: 7, Path: meshcatalogclient.RootPath}) putModuleRaw(p.w, trunkBranch, "web", mod) @@ -1230,14 +1230,14 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) { fpOld := meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64) waiting, was := "sha256:"+strings.Repeat("e", 64), "sha256:"+strings.Repeat("d", 64) asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now()) + p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now()) p.logf = t.Logf put := func(bucket, key string, v []byte) { p.w.put(bucket, key, v) } mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fpTest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath}) putModuleRaw(p.w, trunkBranch, "web", mod) // the trunk's build is not done: rule 1 holds g14 at fpOld placeOn(t, p.w, "g14", "web") pl, _ := mmac.EncodeAssignments(mma.Assignments{Node: "g14", Modules: []mma.PlacedModule{{Module: "web"}}}) - put(plc.AssignmentsBucket, "assignments.g14", pl) + put(plc.AssignmentsBucket(), "assignments.g14", pl) on := true p.w.heardPresence("g14", !on, time.Now()) have, _ := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fpOld, Composed: was, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK}) @@ -1245,10 +1245,10 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) { wkey, _ := mwc.WantKey("g14", "web") want, _ := mwc.EncodeWant(mwc.Want{Fp: fpOld, Composed: was}) wantsKV.values[wkey] = want - put(buckets.Wants, wkey, want) + put(buckets.Wants(), wkey, want) sync := func() { for k, v := range wantsKV.values { - put(buckets.Wants, k, v) + put(buckets.Wants(), k, v) } } @@ -1287,7 +1287,7 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) { t.Fatalf("a pass that changed nothing put %d wants and %d asks", wantsKV.puts-wantPuts, asksKV.puts-askPuts) } // Whichever rule asks it, a want put with the value the bucket holds reaches no bucket: each put is a change its node hears. - p.put(context.Background(), buckets.Wants, wkey, func() ([]byte, error) { return wantsKV.values[wkey], nil }) + p.put(context.Background(), buckets.Wants(), wkey, func() ([]byte, error) { return wantsKV.values[wkey], nil }) if wantsKV.puts != wantPuts { t.Fatalf("a want put unchanged reached the bucket") } @@ -1295,7 +1295,7 @@ func aPlacedNodesWantsNameTheBuildAloneAndItsYesComesFromItsHave(t *testing.T) { if err != nil { t.Fatal(err) } - put(buckets.Asks, akey, asksKV.values[akey]) + put(buckets.Asks(), akey, asksKV.values[akey]) yes, _ := mdc.EncodeAnswer(mdc.Answer{Option: assign.OptionYes, Warrant: a.Ask.ID + "@2026-10-26T19:00:00Z", Words: "the operator"}) put(buckets.Answers, akey, yes) p.wants(context.Background()) @@ -1454,7 +1454,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai } { t.Run(c.name, func(t *testing.T) { asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, time.Now()) + p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, time.Now()) placeOn(t, p.w, "novox", "web") composes(t, p.w, "novox", "sha256:"+strings.Repeat("9", 64)) // the whole node's composition is never a module's a := assign.RecordAsk(rkey, "novox", "web", fp, assign.RecordNeed{Node: "novox", Digest: dig}, 2, time.Now()) @@ -1463,7 +1463,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai t.Fatal(err) } asksKV.values[rkey] = v - p.w.put(buckets.Asks, rkey, v) + p.w.put(buckets.Asks(), rkey, v) hkey, _ := nodeengineclient.HaveKey("novox", "web") raw, err := nodeengineclient.EncodeHave(c.have) if err != nil { @@ -1477,7 +1477,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai if c.wanted != "" { wkey, _ := mwc.WantKey("novox", "web") w, _ := mwc.EncodeWant(mwc.Want{Fp: fp, Self: true, Yes: c.wanted}) - p.w.put(buckets.Wants, wkey, w) + p.w.put(buckets.Wants(), wkey, w) } p.staleAsks(context.Background(), trunk) if _, there := asksKV.values[rkey]; there == c.gone { @@ -1507,9 +1507,9 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai }{"record": {rec, rkey}, "restore": {restore, rest}, "hold": {holdIt, hold}} { at := time.Now() asksKV := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: &memKV{values: map[string][]byte{}}}, at) + p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): &memKV{values: map[string][]byte{}}}, at) first := c.write(p) - p.remove(context.Background(), buckets.Asks, c.key) + p.remove(context.Background(), buckets.Asks(), c.key) p.now = func() time.Time { return at.Add(time.Minute) } if again := c.write(p); again == first { t.Errorf("%s: the ask written again carries the id %s the router kept, so it is never asked again", name, first) @@ -1521,7 +1521,7 @@ func aRecordAskIsNotTakenAwayBeforeItsYesIsWrittenAndIsAskedAgainWhenWrittenAgai // readAskID is the router id of the ask at key in the pass's asks. func readAskID(t *testing.T, p *passer, key string) string { t.Helper() - d, err := decodeAsk(key, p.w.value(buckets.Asks, key)) + d, err := decodeAsk(key, p.w.value(buckets.Asks(), key)) if err != nil { t.Fatalf("%s: %v", key, err) } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pins.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pins.go index fa6055ac..7b437b80 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pins.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pins.go @@ -39,7 +39,6 @@ import ( const ( pinsState = "pins" - pinsBucket = assignment.Module + "_" + pinsState pinsTool = "assigner_pins" unpinTool = "assigner_unpin" pinsMadeKey = "made" @@ -177,11 +176,11 @@ func (k *pinKeeper) missing(revision int64, record string) { k.logf("the revision %d the record %s pins is not held, and no finding writer is wired to say so", revision, record) return } - o := assign.Observed{Scope: assignment.Module, ID: strconv.FormatInt(revision, 10), Kind: pinMissingKind, + o := assign.Observed{Scope: assignment.Module(), ID: strconv.FormatInt(revision, 10), Kind: pinMissingKind, Summary: fmt.Sprintf("the delivery record %s pins the assignment graph revision %d, which the assigner no longer holds", record, revision), Headline: "A delivery record pins a revision the assigner no longer holds", Explanation: fmt.Sprintf("The record %s pinned revision %d after it was removed; %s clears it.", record, revision, unpinTool), - Source: assignment.Module} + Source: assignment.Module()} if err := k.findings.Put(o.Finding(k.now())); err != nil { k.logf("the finding %s of revision %d is not written: %v", pinMissingKind, revision, err) } @@ -536,3 +535,5 @@ func inUseOf(w *world) func(module, fp string) bool { return false } } + +func pinsBucket() string { return assignment.Module() + "_" + pinsState } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pipelines_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pipelines_test.go index 8c7e9449..f2476c4e 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pipelines_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pipelines_test.go @@ -190,7 +190,7 @@ func TestAPassWritesNoWantWithoutAPassedPipeline(t *testing.T) { want bool }{{"the trunk-only want", false, false, true}, {"no pipeline passed", true, false, false}, {"a pipeline passed", true, true, true}} { wantsKV := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Wants: wantsKV, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now()) + p := newPasser(kvSet{buckets.Wants(): wantsKV, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now()) p.say = func(oc.Condition) error { return nil } mdigest := strings.Repeat("e", 64) mod, _ := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: mdigest, Repository: "novox/web", Commit: strings.Repeat("1", 40), Path: meshcatalogclient.RootPath}) @@ -264,7 +264,7 @@ func TestTheWantsSwitchOnTheFirstRegistration(t *testing.T) { func TestACommitOnBetaMovesOnlyTheNodeThatChoseBeta(t *testing.T) { fp := func(c string) string { return meshcatalogclient.SourcePrefix2 + strings.Repeat(c, 64) } wantsKV := &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Wants: wantsKV, buckets.Asks: &memKV{values: map[string][]byte{}}}, time.Now()) + p := newPasser(kvSet{buckets.Wants(): wantsKV, buckets.Asks(): &memKV{values: map[string][]byte{}}}, time.Now()) p.say = func(oc.Condition) error { return nil } p.ends = newPipelineEnds(newMemEnds(), func() {}, t.Logf) p.ends.trunk = p.w.trunkName @@ -282,7 +282,7 @@ func TestACommitOnBetaMovesOnlyTheNodeThatChoseBeta(t *testing.T) { t.Fatal(err) } for key, raw := range values { - p.w.put(plc.AssignmentsBucket, key, raw) + p.w.put(plc.AssignmentsBucket(), key, raw) } } // publish makes the half of branch name web at fingerprint f: built, granted and passed by its pipeline. diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/placementbuild.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/placementbuild.go index 3fb5d39c..2db32d76 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/placementbuild.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/placementbuild.go @@ -20,7 +20,7 @@ const placementBy = "the placement" // `assigner_puts` and released with `assigner_release` like any other; a placement's build the builder never built is refused // by its build step, said in the put's words. A put asked by a person stands until the placement names another build. func (p *passer) placementPuts(ctx context.Context) { - kv := p.kvs[buckets.Puts] + kv := p.kvs[buckets.Puts()] if kv == nil { return } @@ -36,7 +36,7 @@ func (p *passer) placementPuts(ctx context.Context) { } } } - held := p.w.read(buckets.Puts) + held := p.w.read(buckets.Puts()) keys := make([]string, 0, len(named)+len(held)) for k := range named { keys = append(keys, k) @@ -86,7 +86,7 @@ func (p *passer) placementPuts(ctx context.Context) { p.logf("the placement's put %s could not be written, and is written at the next pass: %v", key, err) continue } - p.w.put(buckets.Puts, key, data) + p.w.put(buckets.Puts(), key, data) p.logf("put %s: %s: %s", key, next.Step, next.Words) if p.acts != nil { p.acts.say(key, next) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/puts.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/puts.go index ed4c9c78..dc4b3db9 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/puts.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/puts.go @@ -83,7 +83,7 @@ func identity(key string, p puts.Put) string { // putsByModule is every put the assigner holds, by module then node. func (p *passer) putsByModule() map[string]map[string]puts.Put { out := map[string]map[string]puts.Put{} - for key, value := range p.w.read(buckets.Puts) { + for key, value := range p.w.read(buckets.Puts()) { node, module, err := puts.ParseKey(key) if err != nil { continue @@ -192,7 +192,7 @@ func (p *passer) putWorld(node, module string, put puts.Put, manifest catalogue. w.Composed, w.Granted = p.composedFor(node, module, put.FP) if d := w.Composed; d != nil { policy := d.Policy - chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket)) + chosen, _ := assign.DecodePolicies(p.w.read(assign.PoliciesBucket())) if c, ok := chosen[module]; ok { policy, _, _, _ = catalogue.Upgrade(manifest, &c) // a person's choice at the assigner (ADR 0565 §7) } else if policy == "" { @@ -201,7 +201,7 @@ func (p *passer) putWorld(node, module string, put puts.Put, manifest catalogue. w.Recorded = policy == catalogue.PolicyRecord if akey, err := asks.RecordAskKey(node, module, d.Digest); err == nil && put.Ask == akey { // Only this put's own ask counts: an ask (and its answer) of an earlier put under the same key is another asking. - if a, ask, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks, akey)); err == nil && a.Asking == assign.PutAsking(put) { + if a, ask, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks(), akey)); err == nil && a.Asking == assign.PutAsking(put) { switch { case p.answeredWith(akey, assign.OptionYes): w.Answer = assign.OptionYes @@ -252,13 +252,13 @@ func (p *passer) actOn(ctx context.Context, node, module, id string, w assign.Pu // The put's own ask, written again over one an earlier put left under the key (its asking differs), so the teller // raises it anew under its own id. akey := next.Put.Ask - current, _, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks, akey)) + current, _, err := assign.DecodeAsk(akey, p.w.value(buckets.Asks(), akey)) if err != nil || current.Asking != assign.PutAsking(next.Put) { a, err := assign.PutAsk(akey, node, module, next.Put, *w.Composed, w.Bus, w.Now) if err != nil { p.logf("the ask about putting %s on %s is not written: %v", module, node, err) } else { - p.put(ctx, buckets.Asks, akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) }) + p.put(ctx, buckets.Asks(), akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) }) } } } @@ -269,7 +269,7 @@ func (p *passer) actOn(ctx context.Context, node, module, id string, w assign.Pu // performs checks that the act about to be performed is the one the yes of the ask at key bound (occ.Performs). func (p *passer) performs(key string, act occ.Act) error { - _, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks, key)) + _, ask, err := assign.DecodeAsk(key, p.w.value(buckets.Asks(), key)) if err != nil { return fmt.Errorf("the ask %s cannot be read: %w", key, err) } @@ -289,7 +289,7 @@ func samePut(a, b puts.Put) bool { // writePut writes a put's next value over the one the pass read, by compare-and-set, unless the tool has put again or // released it since; and says it as the event put. It says whether it wrote. func (p *passer) writePut(ctx context.Context, key string, read, next puts.Put) bool { - kv := p.kvs[buckets.Puts] + kv := p.kvs[buckets.Puts()] if kv == nil { return false } @@ -313,7 +313,7 @@ func (p *passer) writePut(ctx context.Context, key string, read, next puts.Put) } return false } - p.w.put(buckets.Puts, key, data) + p.w.put(buckets.Puts(), key, data) p.logf("put %s: %s: %s", key, next.Step, next.Words) p.acts.say(key, next) return true diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits.go index 5aca048a..37fe8fb6 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits.go @@ -36,7 +36,7 @@ func putWaits(p puts.Put) bool { // putWaitBody is the wait of the put p of module on node. func putWaitBody(node, module string, p puts.Put) meshwants.Wait { since := p.StandsSince().UTC() - return meshwants.Wait{Step: meshwants.WaitStepPut, Owner: assignment.Module, Node: node, Module: module, Fingerprint: p.FP, + return meshwants.Wait{Step: meshwants.WaitStepPut, Owner: assignment.Module(), Node: node, Module: module, Fingerprint: p.FP, Since: since, ClockFrom: &since, Bound: putWaitBound.String(), Words: fmt.Sprintf("the put of %s at %s on %s waits for %s to run it", module, shortFP(p.FP), node, node)} } @@ -51,10 +51,10 @@ type putWaiting struct { // saidPutWaits is every put wait the state put-waits holds, by key; a value that does not decode is said and left. func (p *passer) saidPutWaits() map[string]putWaiting { out := map[string]putWaiting{} - for key, value := range p.w.read(buckets.PutWaits) { + for key, value := range p.w.read(buckets.PutWaits()) { var w putWaiting if err := json.Unmarshal(value, &w); err != nil || w.ID == "" { - p.logf("%s %s holds no put wait, left: %v", buckets.PutWaits, key, err) + p.logf("%s %s holds no put wait, left: %v", buckets.PutWaits(), key, err) continue } out[key] = w @@ -66,7 +66,7 @@ func (p *passer) saidPutWaits() map[string]putWaiting { // while this process ran or while the assigner was stopped (A-204), and wait-held of each put that waits and was not said; // one not said, or not kept, is said at the next pass. Without the state no put's wait is said. func (p *passer) putWaits(ctx context.Context, all map[string]map[string]puts.Put) { - if p.kvs[buckets.PutWaits] == nil { + if p.kvs[buckets.PutWaits()] == nil { return } waiting := p.saidPutWaits() @@ -92,7 +92,7 @@ func (p *passer) putWaits(ctx context.Context, all map[string]map[string]puts.Pu p.logf("the end of the put %s's wait is not said, and is said at the next pass: %v", key, err) continue } - p.remove(ctx, buckets.PutWaits, key) + p.remove(ctx, buckets.PutWaits(), key) delete(waiting, key) } for module, byNode := range all { @@ -110,7 +110,7 @@ func (p *passer) putWaits(ctx context.Context, all map[string]map[string]puts.Pu continue } kept := putWaiting{ID: id, Put: put} - p.put(ctx, buckets.PutWaits, key, func() ([]byte, error) { return json.Marshal(kept) }) + p.put(ctx, buckets.PutWaits(), key, func() ([]byte, error) { return json.Marshal(kept) }) } } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits_test.go index 1106d2a7..c5ce059a 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/putwaits_test.go @@ -22,9 +22,9 @@ func TestAPutWithoutHoldIsSaidAsAWait(t *testing.T) { var events []string var bodies []meshwants.Wait start := func() *passer { // a process's start: a fresh world that hands what the state holds - p := newPasser(kvSet{buckets.PutWaits: kept}, at) + p := newPasser(kvSet{buckets.PutWaits(): kept}, at) for k, v := range kept.values { - p.w.put(buckets.PutWaits, k, v) + p.w.put(buckets.PutWaits(), k, v) } p.acts = newPutActs(&fakeBus{calls: make(chan string, 8)}, func() {}, func(string, ...any) {}) p.publishWants = func(event string, body any, id string) error { diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert.go index 5b61ea2d..1b783a48 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert.go @@ -58,7 +58,7 @@ func (p *passer) reverts(ctx context.Context) map[string]bool { } reason := revertReason(have.Detail) if have.Phase != nodeengineclient.PhaseHeld || reason == "" { - if have.Fp == want.Fp && p.w.has(buckets.Asks, akey) { + if have.Fp == want.Fp && p.w.has(buckets.Asks(), akey) { p.revertFinding(assign.RevertHeldFinding(node, module, "", "", ""), false) // the reverted build runs } continue @@ -68,11 +68,11 @@ func (p *passer) reverts(ctx context.Context) map[string]bool { if !ok { continue } - current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks)[akey]) + current, currentAsk, there := assign.DecodeAsk(akey, p.w.read(buckets.Asks())[akey]) if there != nil { now := p.clock() a := assign.RevertAsk(akey, node, module, want.Fp, kept, form, have.Detail, 0, now) - p.put(ctx, buckets.Asks, akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) }) + p.put(ctx, buckets.Asks(), akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) }) p.revertFinding(assign.RevertHeldFinding(node, module, kept, form, reason), true) continue } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert_test.go index 87660323..ea662350 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/revert_test.go @@ -29,7 +29,7 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) { at := time.Date(2026, 11, 12, 10, 0, 0, 0, time.UTC) fpOld := meshcatalogclient.SourcePrefix2 + strings.Repeat("a", 64) asksKV, wantsKV := &memKV{values: map[string][]byte{}}, &memKV{values: map[string][]byte{}} - p := newPasser(kvSet{buckets.Asks: asksKV, buckets.Wants: wantsKV}, at) + p := newPasser(kvSet{buckets.Asks(): asksKV, buckets.Wants(): wantsKV}, at) p.logf = t.Logf findings := map[string][]byte{} p.findings = assign.Findings{Put: putInto(findings), Delete: func(k string) error { delete(findings, k); return nil }} @@ -63,7 +63,7 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) { wkey, _ := mwc.WantKey("ace", "web") want := mwc.Want{Fp: fpOld, Self: true} raw, _ := mwc.EncodeWant(want) - p.w.put(buckets.Wants, wkey, raw) + p.w.put(buckets.Wants(), wkey, raw) hkey, _ := nodeengineclient.HaveKey("ace", "web") have := func(fp, phase, detail string) { raw, err := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Fp: fp, Phase: phase, Detail: detail, Health: nodeengineclient.HealthOK}) @@ -86,10 +86,10 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) { pass() rkey, _ := asks.RevertAskKey("ace", "web", fpOld) bkey, _ := asks.BackupAskKey("ace", "web", fpOld) - if !p.w.has(buckets.Asks, rkey) || p.w.has(buckets.Asks, bkey) { + if !p.w.has(buckets.Asks(), rkey) || p.w.has(buckets.Asks(), bkey) { t.Fatalf("asks %v: want the revert ask %s and no backup ask", asksKV.values, rkey) } - _, ask, err := assign.DecodeAsk(rkey, p.w.read(buckets.Asks)[rkey]) + _, ask, err := assign.DecodeAsk(rkey, p.w.read(buckets.Asks())[rkey]) if err != nil || !strings.Contains(ask.Explanation, "sessions") || !strings.Contains(ask.Explanation, "form 1") { t.Fatalf("the revert ask = %+v, %v", ask, err) } @@ -110,7 +110,7 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) { } answer(assign.OptionRevertLive) pass() - got, err := mwc.DecodeWant(p.w.read(buckets.Wants)[wkey]) + got, err := mwc.DecodeWant(p.w.read(buckets.Wants())[wkey]) if err != nil || got.Fp != fpOld || got.Revert != mwc.RevertLive || got.Words != `the operator answered "live" to the revert ask (asking 1)` { t.Fatalf("the want after live = %+v, %v", got, err) } @@ -128,9 +128,9 @@ func TestAHeldRevertIsAskedAndALiveAnswerIsWrittenIntoTheWant(t *testing.T) { t.Fatal("the finding is not raised again for a hold again") } raw, _ = mwc.EncodeWant(mwc.Want{Fp: fpTest, Self: true}) - p.w.put(buckets.Wants, wkey, raw) + p.w.put(buckets.Wants(), wkey, raw) p.staleAsks(ctx, map[string]meshcatalog.ModulesByModuleValue{"web": {Fp2: fpTest}}) - if _, raised := findings[fkey]; raised || p.w.has(buckets.Asks, rkey) { - t.Fatalf("a newer want left the ask (%v) or the finding (%v)", p.w.has(buckets.Asks, rkey), raised) + if _, raised := findings[fkey]; raised || p.w.has(buckets.Asks(), rkey) { + t.Fatalf("a newer want left the ask (%v) or the finding (%v)", p.w.has(buckets.Asks(), rkey), raised) } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/running.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/running.go index 1aa6d079..de16ce94 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/running.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/running.go @@ -20,7 +20,7 @@ import ( // hearRunning keeps each `running` the stream holds and each it says from now, and wakes the pass on every one. func hearRunning(ctx context.Context, js jetstream.JetStream, w *world, wake func(), findings assign.Findings, logf func(string, ...any)) { - acct := process.Account{Node: thisNode, Module: assignment.Module} + acct := process.Account{Node: thisNode, Module: assignment.Module()} nh := notHeardWriter(findings, logf) cc, err := acct.HearSeatEvents(ctx, js, nodeengine.StreamRunning, func(m jetstream.Msg) { if err := m.Ack(); err != nil { diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go index 51bcb989..6b12bc1d 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go @@ -57,7 +57,7 @@ func seatPublisher(ctx context.Context, js jetstream.JetStream, node string) pub } msg := nats.NewMsg(seatdef.EventSubject(mma.Seat, event)) msg.Header.Set("x-event-id", id) - msg.Header.Set("x-source", assignment.Module) + msg.Header.Set("x-source", assignment.Module()) msg.Header.Set("x-node", node) msg.Header.Set("x-time", time.Now().UTC().Format(time.RFC3339Nano)) msg.Header.Set("content-type", "application/json") @@ -155,7 +155,7 @@ func (p *passer) seatAssignments() (map[string]mma.Assignments, map[string]uint6 func (p *passer) candidatesOf(id string) (map[string]mma.CandidatesByProposalNodeValue, map[string]uint64, time.Time) { out, revs := map[string]mma.CandidatesByProposalNodeValue{}, map[string]uint64{} var newest time.Time - for key, value := range p.w.read(plc.CandidatesBucket) { + for key, value := range p.w.read(plc.CandidatesBucket()) { pid, node, err := plc.ParseCandidateKey(key) if err != nil || pid != id { continue @@ -164,7 +164,7 @@ func (p *passer) candidatesOf(id string) (map[string]mma.CandidatesByProposalNod if err != nil { continue } - meta := p.w.metaOf(plc.CandidatesBucket, key) + meta := p.w.metaOf(plc.CandidatesBucket(), key) out[node], revs[node] = c, meta.revision if meta.at.After(newest) { newest = meta.at @@ -194,7 +194,7 @@ func (p *passer) answersTo(id string) map[string]*nodeengine.CandidateAnswer { // assignmentsSeat takes every open proposal one step further. func (p *passer) assignmentsSeat(ctx context.Context) { - for _, b := range seatBuckets { + for _, b := range seatBuckets() { if p.kvs[b] == nil { return } @@ -207,7 +207,7 @@ func (p *passer) assignmentsSeat(ctx context.Context) { } current, revs := p.seatAssignments() ids := make([]string, 0) - raws := p.w.read(plc.ProposalsBucket) + raws := p.w.read(plc.ProposalsBucket()) for id := range raws { ids = append(ids, id) } @@ -218,32 +218,32 @@ func (p *passer) assignmentsSeat(ctx context.Context) { p.logf("the proposal %s cannot be read, so it is left: %v", id, err) continue } - p.stepProposal(ctx, prop, p.w.metaOf(plc.ProposalsBucket, id).revision, current, revs) + p.stepProposal(ctx, prop, p.w.metaOf(plc.ProposalsBucket(), id).revision, current, revs) } } // pulse writes a bound's key with its time to live (§4): its expiry, heard on the watch as the server's delete marker, // is the event that ends the wait. Written again over one that stands, so its time starts anew. func (p *passer) pulse(ctx context.Context, key string, ttl time.Duration) { - kv := p.kvs[plc.PulsesBucket] + kv := p.kvs[plc.PulsesBucket()] _ = kv.Delete(ctx, key) rev, err := kv.Create(ctx, key, []byte(`{}`), jetstream.KeyTTL(ttl)) if err != nil { p.logf("the bound %s is not written, so its wait ends at once: %v", key, err) return } - p.w.putEntry(plc.PulsesBucket, key, []byte(`{}`), rev, p.clock()) + p.w.putEntry(plc.PulsesBucket(), key, []byte(`{}`), rev, p.clock()) } // pulsing says whether a bound's key stands. -func (p *passer) pulsing(key string) bool { return p.w.has(plc.PulsesBucket, key) } +func (p *passer) pulsing(key string) bool { return p.w.has(plc.PulsesBucket(), key) } // deletePulses deletes every bound of a proposal. func (p *passer) deletePulses(ctx context.Context, id string) { - for key := range p.w.read(plc.PulsesBucket) { + for key := range p.w.read(plc.PulsesBucket()) { if strings.HasSuffix(key, "."+id) || strings.Contains(key, "."+id+".") { - if err := p.kvs[plc.PulsesBucket].Delete(ctx, key); err == nil { - p.w.put(plc.PulsesBucket, key, nil) + if err := p.kvs[plc.PulsesBucket()].Delete(ctx, key); err == nil { + p.w.put(plc.PulsesBucket(), key, nil) } } } @@ -261,7 +261,7 @@ func (p *passer) ask(ctx context.Context, prop mma.Proposal, akey, brief string) p.logf("the ask about the proposal %s is not written: %v", prop.ID, err) return prop, false } - p.put(ctx, buckets.Asks, akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) }) + p.put(ctx, buckets.Asks(), akey, func() ([]byte, error) { return assign.EncodeAsk(akey, a) }) if pk, err := plc.PulseAskKey(prop.ID); err == nil { p.pulse(ctx, pk, assign.ExpiresAt(placed).Sub(p.clock())) } @@ -423,7 +423,7 @@ func (p *passer) mustAnswer(ctx context.Context, prop mma.Proposal, rev uint64, if pl, ok := current[node]; ok { c.Assignments = &pl } - p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) }) + p.put(ctx, plc.CandidatesBucket(), key, func() ([]byte, error) { return plc.EncodeCandidate(c) }) } for _, node := range must { if k, err := plc.PulseAnswerKey(prop.ID, node); err == nil { @@ -454,7 +454,7 @@ func (p *passer) layAgain(ctx context.Context, prop mma.Proposal, rev uint64, cu for _, node := range plc.Nodes(again) { key, _ := plc.CandidateKey(prop.ID, node) c := mma.CandidatesByProposalNodeValue{Proposal: prop.ID, Assignments: laid[node]} - p.put(ctx, plc.CandidatesBucket, key, func() ([]byte, error) { return plc.EncodeCandidate(c) }) + p.put(ctx, plc.CandidatesBucket(), key, func() ([]byte, error) { return plc.EncodeCandidate(c) }) } again.Written, again.Words = ptr(p.clock().UTC()), why+": laid again over the keys as they stand, to be judged and asked again" p.writeProposal(ctx, again, rev) @@ -527,8 +527,8 @@ func (p *passer) warranted(ctx context.Context, prop mma.Proposal, rev uint64, a } p.deleteCandidates(ctx, prop.ID) p.deletePulses(ctx, prop.ID) - _ = p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID) - p.w.put(plc.ProposalsBucket, prop.ID, nil) + _ = p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID) + p.w.put(plc.ProposalsBucket(), prop.ID, nil) p.followWrite(prop, answer, changes) } @@ -563,8 +563,8 @@ func (p *passer) addNode(ctx context.Context, prop mma.Proposal, revs map[string c.Before, c.After = uint64(prop.Base[node]), rev p.deleteCandidates(ctx, prop.ID) p.deletePulses(ctx, prop.ID) - _ = p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID) - p.w.put(plc.ProposalsBucket, prop.ID, nil) + _ = p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID) + p.w.put(plc.ProposalsBucket(), prop.ID, nil) w := prop.Warrant p.sayChanged(prop, mdc.Answer{Option: assign.OptionYes, Warrant: w.Warrant, Words: fmt.Sprintf("the operator approved the machine %s at %s through the enrolment's ask %s", w.Node, w.Address, w.Ask)}, @@ -590,8 +590,8 @@ func (p *passer) removeNode(ctx context.Context, prop mma.Proposal, current map[ c.Before, c.After = uint64(prop.Base[node]), rev p.deleteCandidates(ctx, prop.ID) p.deletePulses(ctx, prop.ID) - _ = p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID) - p.w.put(plc.ProposalsBucket, prop.ID, nil) + _ = p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID) + p.w.put(plc.ProposalsBucket(), prop.ID, nil) w := prop.Warrant p.sayChanged(prop, mdc.Answer{Option: assign.OptionYes, Warrant: w.Warrant, Words: fmt.Sprintf("the machine %s left assignments: its enrolment (ask %s) ended before it confirmed", w.Node, w.Ask)}, @@ -636,28 +636,28 @@ func (p *passer) sayChanged(prop mma.Proposal, answer mdc.Answer, changes []plc. // endProposal ends a proposal: its ask, its candidates and its key deleted, and assignments-withdrawn said carrying it // whole with why it ended. func (p *passer) endProposal(ctx context.Context, prop mma.Proposal, why string) { - if akey, err := asks.AssignmentsAskKey(prop.ID); err == nil && p.w.has(buckets.Asks, akey) { - if err := p.kvs[buckets.Asks].Delete(ctx, akey); err == nil { - p.w.put(buckets.Asks, akey, nil) + if akey, err := asks.AssignmentsAskKey(prop.ID); err == nil && p.w.has(buckets.Asks(), akey) { + if err := p.kvs[buckets.Asks()].Delete(ctx, akey); err == nil { + p.w.put(buckets.Asks(), akey, nil) } } p.deleteCandidates(ctx, prop.ID) p.deletePulses(ctx, prop.ID) - if err := p.kvs[plc.ProposalsBucket].Delete(ctx, prop.ID); err != nil && !errors.Is(err, context.Canceled) { + if err := p.kvs[plc.ProposalsBucket()].Delete(ctx, prop.ID); err != nil && !errors.Is(err, context.Canceled) { p.logf("the proposal %s is not deleted, and is ended at the next pass: %v", prop.ID, err) return } - p.w.put(plc.ProposalsBucket, prop.ID, nil) + p.w.put(plc.ProposalsBucket(), prop.ID, nil) p.logf("the proposal %s ends: %s", prop.ID, why) p.sayEvent(mma.EventAssignmentsWithdrawn, mma.AssignmentsEvent{Proposal: prop, Ended: why}, prop.ID+".withdrawn") } // deleteCandidates deletes every candidate of a proposal. func (p *passer) deleteCandidates(ctx context.Context, id string) { - for key := range p.w.read(plc.CandidatesBucket) { + for key := range p.w.read(plc.CandidatesBucket()) { if pid, _, err := plc.ParseCandidateKey(key); err == nil && pid == id { - if err := p.kvs[plc.CandidatesBucket].Delete(ctx, key); err == nil { - p.w.put(plc.CandidatesBucket, key, nil) + if err := p.kvs[plc.CandidatesBucket()].Delete(ctx, key); err == nil { + p.w.put(plc.CandidatesBucket(), key, nil) } } } @@ -671,7 +671,7 @@ func (p *passer) writeProposal(ctx context.Context, prop mma.Proposal, rev uint6 p.logf("the proposal %s is not written: %v", prop.ID, err) return false } - kv := p.kvs[plc.ProposalsBucket] + kv := p.kvs[plc.ProposalsBucket()] var at uint64 if rev == 0 { at, err = kv.Create(ctx, prop.ID, raw) @@ -684,7 +684,7 @@ func (p *passer) writeProposal(ctx context.Context, prop mma.Proposal, rev uint6 } return false } - p.w.putEntry(plc.ProposalsBucket, prop.ID, raw, at, p.clock()) + p.w.putEntry(plc.ProposalsBucket(), prop.ID, raw, at, p.clock()) return true } @@ -710,7 +710,7 @@ func (p *passer) writeNode(ctx context.Context, node string, after *mma.Assignme if err != nil { return 0, err } - kv := p.kvs[plc.AssignmentsBucket] + kv := p.kvs[plc.AssignmentsBucket()] committed, rev := false, uint64(0) for _, w := range writes { got, err := writeKey(ctx, kv, w) @@ -721,7 +721,7 @@ func (p *passer) writeNode(ctx context.Context, node string, after *mma.Assignme } return 0, fmt.Errorf("assignments %s at revision %d: %w", w.Key, w.Base, err) } - p.w.putEntry(plc.AssignmentsBucket, w.Key, w.Value, got, p.clock()) + p.w.putEntry(plc.AssignmentsBucket(), w.Key, w.Value, got, p.clock()) if w.Commit { committed, rev = true, got } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go index b59d1a84..e7912602 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools_test.go @@ -318,7 +318,7 @@ func TestTheSeatsVerbs(t *testing.T) { kv := &countingKV{memKV: memKV{values: map[string][]byte{}}, writes: &writes} // Issue 1966: nothing is written while the seat list is unread (ADR 0636 §1), and every seat the list names is no // seat outside it once read. - p := &passer{w: newWorld(), logf: t.Logf, kvs: kvSet{plc.SeatHoldersBucket: kv}, seats: gateOver(t, true)} + p := &passer{w: newWorld(), logf: t.Logf, kvs: kvSet{plc.SeatHoldersBucket(): kv}, seats: gateOver(t, true)} place := func(node string, holds ...mma.Holding) { putAssignments(p.w, mma.Assignments{V: keys.Version, Node: node, Modules: []mma.PlacedModule{{Module: "web"}, {Module: "nats"}}, Holds: holds}) } @@ -355,7 +355,7 @@ func TestTheSeatsVerbs(t *testing.T) { t.Run("research 072 row 37: a node is written as its keys, and history reads the node key", func(t *testing.T) { ctx := context.Background() kv := &revKV{values: map[string][]byte{}, revs: map[string]uint64{}} - p := &passer{w: newWorld(), kvs: kvSet{plc.AssignmentsBucket: kv}, now: time.Now, logf: t.Logf} + p := &passer{w: newWorld(), kvs: kvSet{plc.AssignmentsBucket(): kv}, now: time.Now, logf: t.Logf} first := mma.Assignments{Node: "anchor", Modules: []mma.PlacedModule{{Module: "web"}}} if _, err := p.writeNode(ctx, "anchor", &first); err != nil || strings.Join(kv.log, " ") != "create anchor.web create anchor.node" { t.Fatalf("the first write = %v, %v", kv.log, err) @@ -447,7 +447,7 @@ func TestTheSeatsVerbs(t *testing.T) { } var logs []string bus := &emitted{} - p := &passer{w: newWorld(), kvs: kvSet{retiredBucket: old, plc.AssignmentsBucket: next, copiesBucket: copies}, now: time.Now, seat: &seatActs{bus: bus}, + p := &passer{w: newWorld(), kvs: kvSet{retiredBucket(): old, plc.AssignmentsBucket(): next, copiesBucket(): copies}, now: time.Now, seat: &seatActs{bus: bus}, logf: func(f string, a ...any) { logs = append(logs, fmt.Sprintf(f, a...)) }} if !p.copyRetired(ctx) || !p.copied { t.Fatalf("the copy did not end: %v", logs) @@ -459,7 +459,7 @@ func TestTheSeatsVerbs(t *testing.T) { t.Errorf("assignments = %v, want %v", next.values, c.wantValues) } if _, ok := copies.values[copiedMarker]; !ok { - t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket, copies.values) + t.Errorf("the end of the copy is not marked in %s: %v", copiesBucket(), copies.values) } if !maps.EqualFunc(old.values, c.old, slices.Equal[[]byte]) || old.deletes != 0 { t.Errorf("the retired state changed: %v, %d deletes; the readers delivered before still read it", old.values, old.deletes) @@ -480,7 +480,7 @@ func TestTheSeatsVerbs(t *testing.T) { if !p.copyRetired(ctx) || len(next.log) != before || len(bus.events) > 1 { t.Errorf("a second copy wrote %v, said %v", next.log[before:], bus.events) } - if got := len(p.w.read(plc.AssignmentsBucket)); got != c.said.Copied { + if got := len(p.w.read(plc.AssignmentsBucket())); got != c.said.Copied { t.Errorf("%d copied keys are in the world, want %d", got, c.said.Copied) } }) @@ -511,7 +511,7 @@ func TestTheSeatsVerbs(t *testing.T) { } // A required provision with no key: pulsed with the bound first, said once the pulse expired, ended once keyed. ctx := context.Background() - p.kvs[plc.PulsesBucket] = &memKV{values: map[string][]byte{}} + p.kvs[plc.PulsesBucket()] = &memKV{values: map[string][]byte{}} gone := map[string]bool{"ace.photos.s3": true} p.provisionsMissing(ctx, gone) if !p.pulsing("provision-missing.ace.photos.s3") || len(findings) != 0 { @@ -521,7 +521,7 @@ func TestTheSeatsVerbs(t *testing.T) { if len(findings) != 0 { t.Fatal("said before its bound") } - p.w.put(plc.PulsesBucket, "provision-missing.ace.photos.s3", nil) // the server expired it + p.w.put(plc.PulsesBucket(), "provision-missing.ace.photos.s3", nil) // the server expired it p.provisionsMissing(ctx, gone) if _, ok := findings[assign.ProvisionMissingKey("ace.photos.s3")]; !ok { t.Fatalf("past its bound: findings %v", findings) @@ -816,7 +816,7 @@ func putAssignments(w *world, a mma.Assignments) { panic(err) } for _, k := range slices.Sorted(maps.Keys(values)) { - w.putEntry(plc.AssignmentsBucket, k, values[k], 1, time.Now()) + w.putEntry(plc.AssignmentsBucket(), k, values[k], 1, time.Now()) } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go index c92a7566..51803186 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go @@ -2,6 +2,7 @@ package main import ( mma "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + plc "mesh-delivery-assigner/internal/assignment" "encoding/json" "errors" @@ -385,6 +386,7 @@ func (runtimeStore) keys(state string) ([]string, error) { return stdio.State(st // serveTools serves the tools to the runtime until it stops, hearing every put to answer assigner_put with its first steps. func serveTools() error { + plc.SetModule(os.Getenv(stdio.ServedAs)) // the module the runtime serves these tools as (A-296) s := newPutTools(runtimeStore{}, stdio.Ask) go func() { <-stdio.Served() diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go index d45cba08..ae31f571 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go @@ -29,7 +29,7 @@ import ( ) // marksBucket is the assigner's state of what its views family said each state up to. -var marksBucket = assignment.Module + "_" + views.MarksState +func marksBucket() string { return assignment.Module() + "_" + views.MarksState } // viewedTool is one tool over one state of the assigner's. type viewedTool struct{ tool, state, words string } @@ -56,13 +56,13 @@ var readTools = []viewedTool{ // viewedStates are the states the views family says, each by its bucket: the six its readers read. var viewedStates = map[string]string{ - mma.StateAssignments: plc.AssignmentsBucket, - mma.StateSeatHolders: plc.SeatHoldersBucket, - "wants": buckets.Wants, - assign.FindingsState: assign.FindingsBucket, - "asks": buckets.Asks, - "differences": buckets.Differences, - copiesState: copiesBucket, + mma.StateAssignments: plc.AssignmentsBucket(), + mma.StateSeatHolders: plc.SeatHoldersBucket(), + "wants": buckets.Wants(), + assign.FindingsState: assign.FindingsBucket(), + "asks": buckets.Asks(), + "differences": buckets.Differences(), + copiesState: copiesBucket(), } // viewTools are the assigner's first-view and read tools, reading only its own states, so they answer as soon as its @@ -71,13 +71,13 @@ func viewTools(getenv func(string) string, state func(string) views.Source, now marks := state(views.MarksState) var out []stdio.Tool for _, t := range firstViewTools { - out = append(out, views.FirstViewTool(assignment.Module, t.tool, t.state, t.words, views.ParseCallers(getenv(views.CallersEnv(t.tool))), state(t.state), marks, now)) + out = append(out, views.FirstViewTool(assignment.Module(), t.tool, t.state, t.words, views.ParseCallers(getenv(views.CallersEnv(t.tool))), state(t.state), marks, now)) } for _, t := range readTools { - out = append(out, views.ReadTool(assignment.Module, t.tool, t.state, t.words, state(t.state), marks, now)) + out = append(out, views.ReadTool(assignment.Module(), t.tool, t.state, t.words, state(t.state), marks, now)) } // Its own first views, of the indexer's manifests, modules and graph (DLV-F3r, ADR 0631 §2): read, and asked again by hand. - return append(out, views.StatusTools(assignment.Module, "assigner_first_views", "assigner_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...) + return append(out, views.StatusTools(assignment.Module(), "assigner_first_views", "assigner_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, now)...) } func keptSource(name string) views.Source { return views.KeptSource{State: stdio.State(name)} } @@ -85,7 +85,7 @@ func keptSource(name string) views.Source { return views.KeptSource{State: stdio // startViews says each change of the assigner's viewed states on its views family, once its marks state is bound, // each state as soon as its bucket is (kvs, or bound once it appears). A watch that ends is sent on errs. func startViews(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, account process.Account, kvs kvSet, writer views.WireWriter, errs chan<- error, logf func(string, ...any)) { - process.AwaitBucket(ctx, js, nc, marksBucket, "no change is said on the views family, so every reader waits on its first view", logf).Ready(func(mk jetstream.KeyValue) { + process.AwaitBucket(ctx, js, nc, marksBucket(), "no change is said on the views family, so every reader waits on its first view", logf).Ready(func(mk jetstream.KeyValue) { em := viewsEmitter(js, account, mk, writer, logf) for _, state := range []string{"asks", "differences", assign.FindingsState, mma.StateAssignments, mma.StateSeatHolders, "wants", copiesState} { state := state @@ -106,11 +106,11 @@ func startViews(ctx context.Context, js jetstream.JetStream, nc process.Subscrib // viewsEmitter is the assigner's emitter of its views family: a change not said past views.Bound is its finding // view-unsaid, in its findings, written through its finding writer (review round 2); with none it is said in the log only. func viewsEmitter(js jetstream.JetStream, account process.Account, mk jetstream.KeyValue, writer views.WireWriter, logf func(string, ...any)) views.Emitter { - return views.Emitter{Owner: assignment.Module, Subject: account.EventSubject, Publish: views.JetStreamPublish(js), Marks: mk, Findings: writer, Logf: logf} + return views.Emitter{Owner: assignment.Module(), Subject: account.EventSubject, Publish: views.JetStreamPublish(js), Marks: mk, Findings: writer, Logf: logf} } // firstViewsBucket is the assigner's own state of its first views (views.StatusState). -var firstViewsBucket = assignment.Module + "_" + views.StatusState +func firstViewsBucket() string { return assignment.Module() + "_" + views.StatusState } // catalogViews is the assigner's first view of the catalog half of each watched branch (design 47 G §14, §184, §195, A-26): the // module's views reader adopts each branch's follower, so it asks the seat mesh-catalog's verb `revision` (the trunk's, then @@ -142,7 +142,7 @@ var firstViewBound, firstViewWait time.Duration // start adopts the trunk's catalog half through the reader and starts the reader; it answers how many settles to wait for. func (iv *catalogViews) start(ctx context.Context, w *world, wake func(), settled chan<- struct{}) int { - reader := &views.ProcessReader{Module: assignment.Module, Call: iv.call, Logf: iv.logf, Hub: iv.hub, Bound: firstViewBound, Wait: firstViewWait} + reader := &views.ProcessReader{Module: assignment.Module(), Call: iv.call, Logf: iv.logf, Hub: iv.hub, Bound: firstViewBound, Wait: firstViewWait} if iv.writer != nil { reader.Findings = func() views.WireWriter { return iv.writer } } @@ -172,7 +172,7 @@ func (iv *catalogViews) start(ctx context.Context, w *world, wake func(), settle // notHeardWriter writes the assigner's finding of a family it does not hear into its findings (heard.Writer); with no // findings state it is said in the log alone. func notHeardWriter(findings assign.Findings, logf func(string, ...any)) heard.Writer { - w := heard.Writer{Observer: assignment.Module, Logf: logf} + w := heard.Writer{Observer: assignment.Module(), Logf: logf} if findings.Put != nil { w.Put = func(_ string, value []byte) error { var f mcseat.Finding diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go index 4599e74a..c9927d9c 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/world.go @@ -98,7 +98,7 @@ func (w *world) putEntry(bucket, key string, value []byte, revision uint64, at t return } w.meta[bucket+"|"+key] = entryMeta{revision: revision, at: at} - if bucket == plc.AssignmentsBucket { + if bucket == plc.AssignmentsBucket() { _, _ = w.assignmentKeys.Put(key, value, revision, false) // put took it already; this is its revision } } @@ -120,14 +120,14 @@ func (w *world) put(bucket, key string, value []byte) { w.changes[bucket]++ // the bucket's generation (bucketGen), a delete included if value == nil { delete(w.values[bucket], key) - if bucket == plc.AssignmentsBucket { + if bucket == plc.AssignmentsBucket() { _, _ = w.assignmentKeys.Put(key, nil, 0, true) } return } w.values[bucket][key] = value w.changes[bucket+"|"+key]++ - if bucket == plc.AssignmentsBucket { + if bucket == plc.AssignmentsBucket() { // A key or value that does not read places nothing new on its node; the indexer's read tool says it. _, _ = w.assignmentKeys.Put(key, value, 0, false) } @@ -197,7 +197,7 @@ func (w *world) placed() (map[string][]string, bool) { for node, p := range w.assignments() { out[node] = mmac.Assigned(p) } - return out, len(w.read(plc.AssignmentsBucket)) > 0 + return out, len(w.read(plc.AssignmentsBucket())) > 0 } // assignments is every node's assignments, assembled from its keys in either form (mmac.Keys). @@ -359,7 +359,7 @@ func (w *world) awayNow() map[string]bool { // the seat's verb is heard (novox/hq ADR 0668 §3). func (w *world) seatHeard(seat string) bool { online := w.online() - for _, value := range w.read(plc.SeatHoldersBucket) { + for _, value := range w.read(plc.SeatHoldersBucket()) { var sh mma.SeatHolders if json.Unmarshal(value, &sh) != nil || sh.Seat != seat { continue @@ -387,7 +387,7 @@ func (w *world) online() map[string]bool { // wantsOf is the wants the assigner wrote, by module then node. func (w *world) wantsOf() map[string]map[string]mwc.Want { out := map[string]map[string]mwc.Want{} - for key, value := range w.read(buckets.Wants) { + for key, value := range w.read(buckets.Wants()) { node, module, err := mwc.ParseWantKey(key) if err != nil { continue diff --git a/modules/mesh-delivery-assigner/internal/assign/asks.go b/modules/mesh-delivery-assigner/internal/assign/asks.go index aee5b660..f5359702 100644 --- a/modules/mesh-delivery-assigner/internal/assign/asks.go +++ b/modules/mesh-delivery-assigner/internal/assign/asks.go @@ -111,7 +111,7 @@ func WaitsOutsideFinding(node, module, causeKey, on string, at time.Time) Observ // words, under the assigner's own name (ADR 0437 clause 9), keyed by the held fingerprint. needsYou is true for a module holding // data, whose restore ask waits for the operator. func HeldCondition(module, fp, node, words string, raised, needsYou bool) oc.Condition { - key := assignment.Module + ".held." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2) + key := assignment.Module() + ".held." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2) subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node} if !raised { return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared} @@ -134,7 +134,7 @@ func HeldCondition(module, fp, node, words string, raised, needsYou bool) oc.Con // the node keeps its want. It names the node, the module, fp and the have's words, keyed by node and module, and is cleared // (raised false) once the node no longer fails alone. func NodeFailedCondition(node, module, fp string, f NodeFailure, raised bool) oc.Condition { - key := assignment.Module + ".failed-alone." + node + "." + module + key := assignment.Module() + ".failed-alone." + node + "." + module subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node} if !raised { return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared, @@ -157,7 +157,7 @@ func NodeFailedCondition(node, module, fp string, f NodeFailure, raised bool) oc // naming the module, the node and the rule holding its first want back with the rule's words, under the assigner's own name keyed by // node and module, and cleared (raised false) when the node has a want for it again. func NeitherCondition(node, module string, h Holding, raised bool) oc.Condition { - key := assignment.Module + ".neither." + node + "." + module + key := assignment.Module() + ".neither." + node + "." + module subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node} if !raised { return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared, @@ -333,7 +333,7 @@ func BackupAsk(key, node, module, fp, digest, words string, asking int, now time // BackupCondition is the condition raised when a backup before an apply fails and the module is held on the node (ADR 0539 §16), // naming the node, the module and the backup's words; cleared under the same key. func BackupCondition(node, module, fp, words string, raised bool) oc.Condition { - key := assignment.Module + ".backup." + node + "." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2) + key := assignment.Module() + ".backup." + node + "." + module + "." + strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2) subject := oc.ConditionSubject{Scope: "module", ID: module, Machine: node} if !raised { return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared} diff --git a/modules/mesh-delivery-assigner/internal/assign/asks_test.go b/modules/mesh-delivery-assigner/internal/assign/asks_test.go index ab9a26b9..29772b9b 100644 --- a/modules/mesh-delivery-assigner/internal/assign/asks_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/asks_test.go @@ -306,7 +306,7 @@ func saidConditionsAreFindings(t *testing.T) { t.Fatalf("%s: %v", c.key, err) } got, ok := puts[c.key] - if !ok || got.Severity != c.raised.Severity || got.Headline != c.raised.Headline || got.Observer != assignment.Module || + if !ok || got.Severity != c.raised.Severity || got.Headline != c.raised.Headline || got.Observer != assignment.Module() || got.Subject.Machine != c.raised.Subject.Machine || said != 0 { t.Fatalf("%s: finding %+v (written %v), said %d", c.key, got, puts, said) } diff --git a/modules/mesh-delivery-assigner/internal/assign/assignments.go b/modules/mesh-delivery-assigner/internal/assign/assignments.go index 6455b562..54290a4b 100644 --- a/modules/mesh-delivery-assigner/internal/assign/assignments.go +++ b/modules/mesh-delivery-assigner/internal/assign/assignments.go @@ -11,8 +11,8 @@ import ( // SeatWaitsCondition is the assigner's word on the seat mesh-module-assigner while a state of the seat is not bound (ADR 0567 // §1): raised naming each, cleared when none is left. func SeatWaitsCondition(unbound []string) oc.Condition { - key := assignment.Module + ".seat-states" - subject := oc.ConditionSubject{Scope: "module", ID: assignment.Module} + key := assignment.Module() + ".seat-states" + subject := oc.ConditionSubject{Scope: "module", ID: assignment.Module()} if len(unbound) == 0 { return oc.Condition{Key: key, Subject: subject, State: occ.StateCleared, Resolved: "the seat mesh-module-assigner holds all its states."} } diff --git a/modules/mesh-delivery-assigner/internal/assign/findings.go b/modules/mesh-delivery-assigner/internal/assign/findings.go index de663d5a..260ba6ea 100644 --- a/modules/mesh-delivery-assigner/internal/assign/findings.go +++ b/modules/mesh-delivery-assigner/internal/assign/findings.go @@ -25,7 +25,7 @@ import ( // FindingsState is the assigner's state of its findings, and FindingsBucket its bucket. const FindingsState = "findings" -var FindingsBucket = assignment.Module + "_" + FindingsState +func FindingsBucket() string { return assignment.Module() + "_" + FindingsState } // KeyID is an id as the parts of a key: each dot-separated part made safe for a bus key, as the key of a condition is // (mcc.Key), so a finding's key keeps the shape its condition had. @@ -59,7 +59,7 @@ func (o Observed) Finding(at time.Time) mcseat.Finding { sev = mcseat.FindingSeverityWarning } return mcseat.Finding{Scope: o.Scope, ID: id, Kind: o.Kind, Severity: sev, Summary: o.Summary, Headline: o.Headline, - Explanation: o.Explanation, Needs: o.Needs, Actions: o.Actions, Source: o.Source, Observer: assignment.Module, At: at, + Explanation: o.Explanation, Needs: o.Needs, Actions: o.Actions, Source: o.Source, Observer: assignment.Module(), At: at, Evidence: []mcseat.Evidence{{At: at, Said: said}}, Subject: mcseat.Subject{Scope: o.Scope, ID: id, Machine: o.Machine, Also: o.Also}} } @@ -79,7 +79,7 @@ func CallUnanswered(id, callee, machine, fault, source string, since time.Time) // subject's id when the key names none), its words unchanged but the resolved ones. raised is false for a clear, whose // finding names only its scope, id and kind. func FindingOf(c oc.Condition, at time.Time) (f mcseat.Finding, raised bool, err error) { - rest, ok := strings.CutPrefix(c.Key, assignment.Module+".") + rest, ok := strings.CutPrefix(c.Key, assignment.Module()+".") if !ok || rest == "" { return f, false, fmt.Errorf("the condition %q is not the assigner's", c.Key) } diff --git a/modules/mesh-delivery-assigner/internal/assign/ownmodule_test.go b/modules/mesh-delivery-assigner/internal/assign/ownmodule_test.go new file mode 100644 index 00000000..6c651294 --- /dev/null +++ b/modules/mesh-delivery-assigner/internal/assign/ownmodule_test.go @@ -0,0 +1,6 @@ +package assign + +import plc "mesh-delivery-assigner/internal/assignment" + +// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296). +func init() { plc.SetModule("mesh-delivery-assigner") } diff --git a/modules/mesh-delivery-assigner/internal/assign/policy.go b/modules/mesh-delivery-assigner/internal/assign/policy.go index 1e75c117..248346ae 100644 --- a/modules/mesh-delivery-assigner/internal/assign/policy.go +++ b/modules/mesh-delivery-assigner/internal/assign/policy.go @@ -21,7 +21,7 @@ import ( // catalogue.Upgrade: a person's choice over the module's word, its irreplaceable data and the default, the bus as any module (ADR 0601 §1). // PoliciesBucket is the assigner's state `policies`. -var PoliciesBucket = assignment.Module + "_" + buckets.PoliciesState +func PoliciesBucket() string { return assignment.Module() + "_" + buckets.PoliciesState } // DecodePolicies reads the policies state's values by key (the module); a value that cannot be read is left out, named. func DecodePolicies(values map[string][]byte) (map[string]catalogue.PersonPolicy, []string) { diff --git a/modules/mesh-delivery-assigner/internal/assign/seat.go b/modules/mesh-delivery-assigner/internal/assign/seat.go index df3b604d..46416ed9 100644 --- a/modules/mesh-delivery-assigner/internal/assign/seat.go +++ b/modules/mesh-delivery-assigner/internal/assign/seat.go @@ -182,7 +182,7 @@ const ( // CallClearedCondition clears the condition of a refused controller call a previous process raised, for its pair (node and // module, or node and provision for a pin). func CallClearedCondition(kind, node, module string) oc.Condition { - return oc.Condition{Key: assignment.Module + "." + kind + "." + node + "." + module, + return oc.Condition{Key: assignment.Module() + "." + kind + "." + node + "." + module, Subject: oc.ConditionSubject{Scope: "module", ID: module, Machine: node}, State: occ.StateCleared, Resolved: "the assigner calls no verb of the controller any more: assignments is the one source."} } diff --git a/modules/mesh-delivery-assigner/internal/assign/seat_test.go b/modules/mesh-delivery-assigner/internal/assign/seat_test.go index a7724b9e..77d2c187 100644 --- a/modules/mesh-delivery-assigner/internal/assign/seat_test.go +++ b/modules/mesh-delivery-assigner/internal/assign/seat_test.go @@ -83,10 +83,10 @@ func TestTheAssignmentsSeatDecides(t *testing.T) { } // The seat says it waits while one of its states is not bound, naming it, and clears once all are. - for _, unbound := range [][]string{{plc.PulsesBucket}, nil} { + for _, unbound := range [][]string{{plc.PulsesBucket()}, nil} { c := SeatWaitsCondition(unbound) if _, err := occ.EncodeCondition(c); err != nil || (len(unbound) > 0) != (c.State == occ.StateRaised) || - (len(unbound) > 0 && !strings.Contains(c.Explanation, "the seat waits for its states: "+plc.PulsesBucket)) { + (len(unbound) > 0 && !strings.Contains(c.Explanation, "the seat waits for its states: "+plc.PulsesBucket())) { t.Errorf("waiting for %v: %+v, %v", unbound, c, err) } } diff --git a/modules/mesh-delivery-assigner/internal/assigngraph/ownmodule_test.go b/modules/mesh-delivery-assigner/internal/assigngraph/ownmodule_test.go new file mode 100644 index 00000000..5135c930 --- /dev/null +++ b/modules/mesh-delivery-assigner/internal/assigngraph/ownmodule_test.go @@ -0,0 +1,6 @@ +package assigngraph + +import plc "mesh-delivery-assigner/internal/assignment" + +// The assigner's tests run as the module its manifest names, as a credential's module would say it at its start (design 47 G A-296). +func init() { plc.SetModule("mesh-delivery-assigner") } diff --git a/modules/mesh-delivery-assigner/internal/assignment/main_test.go b/modules/mesh-delivery-assigner/internal/assignment/main_test.go index da545265..95ad23fa 100644 --- a/modules/mesh-delivery-assigner/internal/assignment/main_test.go +++ b/modules/mesh-delivery-assigner/internal/assignment/main_test.go @@ -13,3 +13,5 @@ func TestMain(m *testing.M) { catalogue.UseSeats(catalogue.SeatsFromDefinitions(seatlisttest.Definitions())) os.Exit(m.Run()) } + +func init() { SetModule("mesh-delivery-assigner") } diff --git a/modules/mesh-delivery-assigner/internal/assignment/state.go b/modules/mesh-delivery-assigner/internal/assignment/state.go index 36b44250..1c0cb6a7 100644 --- a/modules/mesh-delivery-assigner/internal/assignment/state.go +++ b/modules/mesh-delivery-assigner/internal/assignment/state.go @@ -1,19 +1,38 @@ package assignment -import seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" +import ( + "sync" -// Module is the module that holds the seat mesh-module-assigner: the states of the seat are named after it, as every -// module state is (a bucket is `_`), and its manifest declares them. -const Module = "mesh-delivery-assigner" + seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" +) + +var ( + moduleMu sync.RWMutex + module string +) + +// SetModule names the module the assigner runs as, from its credential's module, once at its start: its states are named +// after it, as every module state is (a bucket is `_`), and its manifest declares them. No code names the +// assigner's module (design 47 G A-296, §428). +func SetModule(m string) { + moduleMu.Lock() + defer moduleMu.Unlock() + module = m +} + +// Module is the module the assigner runs as, as SetModule said at its start. +func Module() string { + moduleMu.RLock() + defer moduleMu.RUnlock() + return module +} // The buckets of the seat's states, which only the holder writes (ADR 0567 §1). -const ( - AssignmentsBucket = Module + "_" + seat.StateAssignments - ProposalsBucket = Module + "_" + seat.StateProposals - CandidatesBucket = Module + "_" + seat.StateCandidates - PulsesBucket = Module + "_" + seat.StatePulses - SeatHoldersBucket = Module + "_" + seat.StateSeatHolders -) +func AssignmentsBucket() string { return Module() + "_" + seat.StateAssignments } +func ProposalsBucket() string { return Module() + "_" + seat.StateProposals } +func CandidatesBucket() string { return Module() + "_" + seat.StateCandidates } +func PulsesBucket() string { return Module() + "_" + seat.StatePulses } +func SeatHoldersBucket() string { return Module() + "_" + seat.StateSeatHolders } const ( // AssignmentsHistory is the revisions the assignments bucket keeps per key: the bus's most (§6). diff --git a/modules/mesh-delivery-assigner/internal/buckets/buckets.go b/modules/mesh-delivery-assigner/internal/buckets/buckets.go index 2b2674c6..cfe1d2f6 100644 --- a/modules/mesh-delivery-assigner/internal/buckets/buckets.go +++ b/modules/mesh-delivery-assigner/internal/buckets/buckets.go @@ -14,35 +14,40 @@ import ( plc "mesh-delivery-assigner/internal/assignment" ) -// The states the assigner keeps: the wants seat's, and its own asks, puts and policies. +// The states the assigner keeps beside the seat's: its own asks, puts, waits and policies. const ( - Wants = plc.Module + "_" + wants.StateWants - Differences = plc.Module + "_" + wants.StateDifferences - Asks = plc.Module + "_asks" - Puts = plc.Module + "_puts" - // Waits holds the waits on an outside cause, one key `..` (design 47 G §706). + // WaitsState holds the waits on an outside cause, one key `..` (design 47 G §706). WaitsState = "waits" - Waits = plc.Module + "_" + WaitsState - // DeliveredAgain holds each ask of assigner_delivered_again, one key ``, its value the version, who asked and why, and + // DeliveredAgainState holds each ask of assigner_delivered_again, one key ``, its value the version, who asked and why, and // why it does not hold yet (design 47 G §376, A-250). DeliveredAgainState = "delivered-again" - DeliveredAgain = plc.Module + "_" + DeliveredAgainState - // DeliveryWaits holds each dependent's delivery wait the assigner said, one key ``, its value the wait-held said + // DeliveryWaitsState holds each dependent's delivery wait the assigner said, one key ``, its value the wait-held said // (design 47 G §376, §498, A-246). DeliveryWaitsState = "delivery-waits" - DeliveryWaits = plc.Module + "_" + DeliveryWaitsState - // PutWaits holds each put wait the assigner said, one key `.`, its value the wait's id and the put as said + // PutWaitsState holds each put wait the assigner said, one key `.`, its value the wait's id and the put as said // (design 47 G A-204, A-211), so a put that ends while the assigner is stopped gets its wait-ended at the next start. PutWaitsState = "put-waits" - PutWaits = plc.Module + "_" + PutWaitsState - // Inputs holds the inputs of the composition steps, `