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 23cdfc37..0dd18e75 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go @@ -29,6 +29,7 @@ import ( oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" "git.novox.be/novox/mesh-sdk/go/announce" "git.novox.be/novox/mesh-sdk/go/process" + "git.novox.be/novox/mesh-sdk/go/seatlist" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads" "git.novox.be/novox/mesh-sdk/go/views" @@ -415,6 +416,12 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, // The assigner's one pull of its durable on the shared events stream: the indexer's views and the engines' reads changed // are handlers of it, started once both are registered (process.EventHub). hub := process.NewEventHub(logger.Printf) + // The seat list (seatlist.go), read over the connection and heard on the hub, given to catalogue at every change, and a + // pass made then: registered before the hub starts. + gate := &seatGate{} + if bus != nil { + gate.follow(seatlist.ProcessBus(bus.Call, hub), wake, logger.Printf) + } // The indexer's manifests, modules and module graph, through its first views and views family, never its buckets // (DLV-F2r, ADR 0631): the first pass waits for the three answers, as it waits for every watch to hand what stands. if iv != nil { @@ -465,12 +472,12 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, } } p := &passer{kvs: kvs, w: w, logf: logger.Printf, say: say, findings: findings, said: map[string]bool{}, answeredAt: map[string]uint64{}, - reads: reads} + reads: reads, seats: gate} // The node-engine seat's manifest-reads-changed, for one node (ADR 0618 §4): that node is asked again, with no timer. // Heard on the assigner's durable consumer on EVENTS, which the grants part makes from the manifest's consumes; where it // is not there yet, a node is asked again only when it comes back online. if js != nil { - hearReadsChanged(ctx, js, hub, reads, findings, logger.Printf) + hearReadsChanged(ctx, js, hub, reads, findings, gate.eventsLive, logger.Printf) } if bus != nil && kvs[buckets.Puts] != nil { p.acts = newPutActs(bus, wake, logger.Printf) @@ -540,7 +547,7 @@ func sayVia(ctx context.Context, js jetstream.JetStream) func(oc.Condition) erro // hearReadsChanged hears the node-engine seat's event manifest-reads-changed on the assigner's one hub of its durable // 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, logf func(string, ...any)) { +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} w := notHeardWriter(findings, logf) handle := func(m jetstream.Msg) { @@ -566,6 +573,9 @@ func hearReadsChanged(ctx context.Context, js jetstream.JetStream, hub *process. return } w.Heard(process.EventsStream) + if live != nil { + live() // the seat list, unread while the seat was silent, is read now (seatGate.eventsLive) + } go func() { <-ctx.Done() hub.Stop() 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 eac3a2b1..b3982c86 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/pass.go @@ -71,7 +71,10 @@ type passer struct { callsCleared bool // The seat mesh-module-assigner's holder (seat.go, ADR 0567): its acts (nil: none is made), how it says // the seat's events (nil: none is said), the proposals it said, and what Intended computed by proposal and base. - seat *seatActs + seat *seatActs + // seats is the seat list the process reads (seatlist.go): until it is read, seat-holders is not written and a proposal + // laid again is not ended on a seat it does not name (ADR 0636 §1). Nil reads nothing. + seats *seatGate publish publisher saidProposed map[string]bool // copied says the retired state was copied into `assignments` by this process, or found empty (copyRetired). @@ -1119,6 +1122,14 @@ func (p *passer) seatHolders(ctx context.Context) { if len(assignments) == 0 { return // no assignments read yet: nothing is known of who holds what, and nothing is deleted } + if !p.seats.read() { + // ADR 0636 §1: which claim names which seat is the list's, so nothing is written from the bootstrap alone; the + // reader's change makes a pass. + if p.seats != nil { + p.seats.waitSaid.Do(func() { p.logf("seat-holders is not written until the seat list is read: %s", p.seats.health()) }) + } + return + } want := plc.SeatHoldersOf(assignments) have := p.w.read(plc.SeatHoldersBucket) for key, sh := range want { 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 f9f47447..dfb5d04d 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat.go @@ -440,7 +440,12 @@ func (p *passer) mustAnswer(ctx context.Context, prop mma.Proposal, rev uint64, func (p *passer) layAgain(ctx context.Context, prop mma.Proposal, rev uint64, current map[string]mma.Assignments, revs map[string]uint64, why string) { held, recorded := p.lays(ctx) again, laid, findings := assign.Propose(assign.ProposeIn{ID: prop.ID, By: prop.By, Why: prop.Why, At: prop.At, Changes: prop.Changes, - Current: current, Revisions: revs, Held: held, Recorded: recorded}) + Current: current, Revisions: revs, Held: held, Recorded: recorded, SeatListRead: p.seats.read()}) + if slices.ContainsFunc(findings, plc.WaitsForSeatList) { + // ADR 0636 §1: nothing is ended on a list not read; the reader's change makes a pass, which lays it again. + p.logf("the proposal %s is not laid again yet: %s", prop.ID, p.seats.health()) + return + } if len(findings) > 0 { p.endProposal(ctx, prop, why+", and laid over the keys now it is refused: "+strings.Join(findings, "; ")) return diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go index 3673a302..6d63a9f9 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seat_tools.go @@ -84,6 +84,9 @@ type seatTools struct { st seatStore ask func(key string, body any) (json.RawMessage, error) now func() time.Time + // seats is the seat list the bundle reads (seatlist.go): until it is read, propose judges no mesh seat the bootstrapped + // definitions do not name as held once (ADR 0636 §1). Nil reads nothing. + seatList *seatGate } func newSeatTools(st seatStore, ask func(string, any) (json.RawMessage, error)) *seatTools { @@ -262,7 +265,7 @@ func (s *seatTools) doPropose(args map[string]any, by string) (any, error) { enrollers := plc.EnrollersOf(current) now := s.now() p, laid, findings := assign.Propose(assign.ProposeIn{ID: plc.NewProposalID(now), By: by, Why: arg(args, "why"), At: now, - Changes: changes, Current: current, Revisions: revs, Held: held, Recorded: recorded, Warrant: warrant}) + Changes: changes, Current: current, Revisions: revs, Held: held, Recorded: recorded, Warrant: warrant, SeatListRead: s.seatList.read()}) if refusal := plc.AddNodeRefusal(p, moduleOfCaller(by, enrollers), enrollers); refusal != "" { findings = append(findings, refusal) } 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 6d4e1310..cb3d0468 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 @@ -225,6 +225,33 @@ func TestTheSeatsVerbs(t *testing.T) { }) } indexer.refused = "" + // Issue 1966: the replicated mesh-dns-resolver held on two nodes is proposed through the verb as the bundle serves it, + // over the seat list it reads (ADR 0636 §1): unread, the check says it waits for the list and writes nothing; read, the + // proposal is laid. + for _, c := range []struct { + name string + silent bool + want string + }{ + {"a replicated seat on two nodes waits for an unread seat list", true, "the seat list is not read yet, so whether the seat mesh-dns-resolver may be held on anchor and laptop waits for it"}, + {"a replicated seat on two nodes, the seat list read", false, ""}, + } { + t.Run("1966: "+c.name, func(t *testing.T) { + st[mma.StateProposals].values, st[mma.StateCandidates].values = map[string][]byte{}, map[string][]byte{} + s.seatList = gateOver(t, c.silent) + _, err := s.doPropose(map[string]any{"why": "a second resolver", "changes": []any{ + map[string]any{"op": "hold", "node": "anchor", "module": "web", "claim": "mesh-dns-resolver", "scope": "mesh"}, + map[string]any{"op": "place", "node": "laptop", "module": "web"}, + map[string]any{"op": "hold", "node": "laptop", "module": "web", "claim": "mesh-dns-resolver", "scope": "mesh"}}}, "op") + switch { + case c.want == "" && err != nil: + t.Errorf("propose: %v", err) + case c.want != "" && (err == nil || !strings.Contains(err.Error(), c.want) || strings.Contains(err.Error(), "held once") || len(st[mma.StateProposals].values) != 0): + t.Errorf("propose: %v, want a refusal saying %q, never held once, and nothing written", err, c.want) + } + }) + } + s.seatList = nil t.Run("1953/D4-D6: the seat mesh-wants' upgrade and bus", wantsVerbs) t.Run("1955/L7 (ADR 0571 §6): rule 7 reads the node's own composition; an unplaced node is said", func(t *testing.T) { w := newWorld() @@ -258,14 +285,24 @@ func TestTheSeatsVerbs(t *testing.T) { t.Run("1955/L7 (ADR 0571 §4): seat-holders is written from assignments, only the keys a change moves, and answered by assignments seats", func(t *testing.T) { writes := 0 kv := &countingKV{memKV: memKV{values: map[string][]byte{}}, writes: &writes} - p := &passer{w: newWorld(), logf: t.Logf, kvs: kvSet{plc.SeatHoldersBucket: kv}} + // 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)} 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}) } place("ace", mma.Holding{Claim: "mesh-broker", Scope: "mesh", Module: "nats"}, mma.Holding{Claim: "node-shell", Scope: "node", Module: "web"}) place("g14", mma.Holding{Claim: "node-shell", Scope: "node", Module: "web"}) ctx := context.Background() + if p.seatHolders(ctx); writes != 0 { + t.Fatalf("seat-holders was written from an unread seat list: %v", kv.values) + } + p.seats = gateOver(t, false) p.seatHolders(ctx) + var broker mma.SeatHolders + if err := json.Unmarshal(kv.values["mesh-broker"], &broker); err != nil || broker.Outside { + t.Errorf("mesh-broker's holders %+v, %v: a seat the list names is no seat outside it", broker, err) + } if writes != 3 || kv.values["mesh-broker"] == nil || kv.values["node-shell.ace"] == nil || kv.values["node-shell.g14"] == nil { t.Fatalf("%d writes: %v", writes, kv.values) } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seatlist.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seatlist.go new file mode 100644 index 00000000..e39c554a --- /dev/null +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seatlist.go @@ -0,0 +1,101 @@ +package main + +import ( + "errors" + "sync" + "sync/atomic" + + stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/catalogue" + "git.novox.be/novox/mesh-sdk/go/seatlist" +) + +// The assigner reads the seat list as every part does (novox/hq ADR 0635 §1, ADR 0636 §1, §7; issue 1966): through the seat +// mesh-catalog's verb seat-definitions and its two events, from the bootstrapped definitions until the seat has answered. What +// the reader holds is given to catalogue at every change, which the check of a proposal and the seat-holders state read: which +// mesh seat is replicated, and which name is a seat's. Both of its runtimes hold one: the process (the proposals it lays again, +// the state seat-holders) over its connection and the event hub, and the tools' bundle (the seat's verb propose) over the +// runtime. Until the list is read, a mesh seat the bootstrapped definitions do not name is never judged held once +// (plc.CheckAssignments), and seat-holders is not written. + +// Seat list tools (ADR 0636 §7): what the reader holds, and reading it again now, which every operator's session and the +// healers may ask. +const ( + seatListTool = "assigner_seat_list" + seatListAgainTool = "assigner_seat_list_again" +) + +var seatListAgainCallers = []string{stdio.AnyPerson, "mesh-healers"} + +// seatGate is the reader a runtime of the assigner judges by, set once it is started; before, the list counts as unread. +type seatGate struct { + reader atomic.Pointer[seatlist.Reader] + // waitSaid says the journal was told that seat-holders waits for the list, so it is told once per wait. + waitSaid sync.Once +} + +// read says the seat mesh-catalog has answered the reader: the list is read (seatlist.Status's Heard). A nil gate, or one +// whose reader has not started, has read nothing. +func (g *seatGate) read() bool { + if g == nil { + return false + } + r := g.reader.Load() + return r != nil && r.Status().Heard +} + +// health is why the list is not read, or empty. +func (g *seatGate) health() string { + if g == nil { + return "the assigner follows no seat list" + } + r := g.reader.Load() + if r == nil { + return "the seat list reader has not started yet" + } + return r.Status().Health() +} + +// follow starts the reader over bus: what it holds is given to catalogue at once and at every change, and changed is called +// then (the process makes a pass, which lays again what waited). +func (g *seatGate) follow(bus seatlist.Bus, changed func(), logf func(string, ...any)) *seatlist.Reader { + reader, err := seatlist.Start(seatlist.Config{Bus: bus, Bootstrap: seatlist.Bootstrapped(), + OnChange: func(seats []catalogue.Seat) { + catalogue.UseSeats(seats) + if changed != nil { + changed() + } + }}) + if err != nil { + logf("the assigner cannot follow its seat list, so a mesh seat the bootstrapped definitions do not name waits for it: %v", err) + } + if reader != nil { + catalogue.UseSeats(reader.Seats()) + g.reader.Store(reader) + } + return reader +} + +// eventsLive is called once the process's consumer on EVENTS is heard: the seat may have said it speaks before that consumer +// existed, whose events start at the stream's end, so while the list is unread it is read now (ADR 0636 §1). +func (g *seatGate) eventsLive() { + if r := g.reader.Load(); r != nil { + r.ReloadIfUnread() + } +} + +// tools are the reader's two tools over whatever reader the gate holds at the call. +func (g *seatGate) tools() []stdio.Tool { + shape := seatlist.Tools(&seatlist.Reader{}, seatListTool, seatListAgainTool, seatListAgainCallers) + for i := range shape { + run := i + shape[i].Run = func(args map[string]any) (any, error) { + r := g.reader.Load() + if r == nil { + return nil, errors.New("the seat list reader has not started yet: the runtime has not served this module") + } + return seatlist.Tools(r, seatListTool, seatListAgainTool, seatListAgainCallers)[run].Run(args) + } + } + return shape +} diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seatlist_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seatlist_test.go new file mode 100644 index 00000000..bd7e485d --- /dev/null +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/seatlist_test.go @@ -0,0 +1,63 @@ +package main + +import ( + "encoding/json" + "errors" + "sync" + "testing" + + "git.novox.be/novox/mesh-sdk/go/seatdef" + "git.novox.be/novox/mesh-sdk/go/seatlist" + "git.novox.be/novox/mesh-sdk/go/seatlist/seatlisttest" +) + +// testDefinitions are seatlisttest's, parsed once. +var testDefinitions = sync.OnceValue(func() []seatdef.Definition { return seatlisttest.Definitions() }) + +// standInCatalog is the seat mesh-catalog as a test's reader asks it (ADR 0636 §4): every definition of seatlisttest, or, +// silent, no answer at all. +type standInCatalog struct{ silent bool } + +func (c standInCatalog) Ask(key string, body any) (json.RawMessage, error) { + if c.silent || key != seatlist.Key() { + return nil, errors.New("no responders") + } + defs := testDefinitions() + seat, _ := body.(map[string]any)["seat"].(string) + if seat == "" { + l := seatlist.Listing{Revision: 1} + for _, d := range defs { + l.Seats = append(l.Seats, seatlist.Entry{Seat: d.Seat, Source: seatlist.SourceTrunk, Digest: "d-" + d.Seat}) + } + return json.Marshal(l) + } + for _, d := range defs { + if d.Seat == seat { + raw, err := json.Marshal(d) + if err != nil { + return nil, err + } + return json.Marshal(seatlist.Answer{Seat: seat, Found: true, Revision: 1, + Entry: &seatlist.Entry{Seat: seat, Source: seatlist.SourceTrunk, Digest: "d-" + seat, Definition: raw}}) + } + } + return json.Marshal(seatlist.Answer{Seat: seat, Revision: 1}) +} + +func (standInCatalog) Subscribe(string, func(string, json.RawMessage) error) error { return nil } + +// gateOver is a seat gate following the stand-in, once its first read has ended. A read one gives catalogue the test list +// (catalogue.UseSeats), as a host's does, for the tests after it too: the list is the same everywhere. +func gateOver(t *testing.T, silent bool) *seatGate { + t.Helper() + g := &seatGate{} + r := g.follow(standInCatalog{silent: silent}, nil, t.Logf) + if r == nil { + t.Fatal("the reader did not start") + } + <-r.Ready() + if g.read() == silent { + t.Fatalf("the gate read %v over a stand-in silent %v: %s", g.read(), silent, g.health()) + } + return g +} 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 1d2a8313..74c56486 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/tools.go @@ -17,6 +17,7 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/seatlist" "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient/enginereads" ) @@ -363,7 +364,16 @@ func serveTools() error { time.Sleep(wait) } }() - tools := append(s.tools(), newSeatTools(runtimeStore{}, stdio.Ask).tools()...) + // The seat list (seatlist.go), read through the runtime once Serve has its channel: the seat's verbs judge by it. + gate := &seatGate{} + go func() { + <-stdio.Served() + gate.follow(seatlist.StdioBus(), nil, func(f string, a ...any) { fmt.Fprintf(os.Stderr, "mesh-delivery-assigner: "+f+"\n", a...) }) + }() + seatVerbs := newSeatTools(runtimeStore{}, stdio.Ask) + seatVerbs.seatList = gate + tools := append(s.tools(), seatVerbs.tools()...) + tools = append(tools, gate.tools()...) wants := newWantsTools(runtimeStore{}, stdio.Ask, func(e, id string, b any) error { // The seat's event, said on the seat: a claimed seat's event is never the module's own. return stdio.SeatPublish(wantsEventSubject(e), b, id) diff --git a/modules/mesh-delivery-assigner/internal/assignment/check.go b/modules/mesh-delivery-assigner/internal/assignment/check.go index fdd4a9cb..3e6a6a3a 100644 --- a/modules/mesh-delivery-assigner/internal/assignment/check.go +++ b/modules/mesh-delivery-assigner/internal/assignment/check.go @@ -138,6 +138,11 @@ func CheckAssignments(assignments map[string]seat.Assignments, known func(module const SeatListWaits = "the seat list is not read yet, so whether the seat %s may be held on %s and %s waits for it: " + "the assigner judges it once it has read the list from the seat mesh-catalog (novox/hq ADR 0636 §1)" +// WaitsForSeatList says a refusal is SeatListWaits': it ends nothing, and is judged again once the list is read. +func WaitsForSeatList(refusal string) bool { + return strings.HasPrefix(refusal, SeatListWaits[:strings.Index(SeatListWaits, "%")]) +} + // Change is what `assignments-changed` says of one node (ADR 0567 §6): the node, the revision of its key before (0: it had // none) and after (0: the key was deleted), and its modules added, removed or changed against the assignments before. type Change struct { diff --git a/modules/mesh-delivery-assigner/manifest_test.go b/modules/mesh-delivery-assigner/manifest_test.go index a396d3a3..d8794144 100644 --- a/modules/mesh-delivery-assigner/manifest_test.go +++ b/modules/mesh-delivery-assigner/manifest_test.go @@ -95,8 +95,8 @@ func TestManifest(t *testing.T) { // which lists and asks again what each node's engine reads. if !slices.Equal(m.Tools, []string{"assigner_put", "assigner_release", "assigner_puts", "assignments", "seat-holders", "wants", "assigner_read_assignments", "assigner_read_seat_holders", "assigner_read_wants", "assigner_read_findings", "assigner_read_asks", "assigner_read_differences", "assigner_read_copies", - "assigner_first_views", "assigner_first_view_again", "assigner_engine_reads"}) { - t.Errorf("the assigner serves tools %v, want its three put tools, three first views, six read tools and assigner_engine_reads", m.Tools) + "assigner_first_views", "assigner_first_view_again", "assigner_engine_reads", "assigner_seat_list", "assigner_seat_list_again"}) { + t.Errorf("the assigner serves tools %v, want its three put tools, three first views, six read tools, assigner_engine_reads and the seat list's two (ADR 0636 §7)", m.Tools) } if m.RunsAs != m.Module || m.SecretsOwner != m.RunsAs { t.Errorf("runs-as = %q: the bundle runs on the assigner's own account (ADR 0621 §1)", m.RunsAs) @@ -120,11 +120,12 @@ func TestManifest(t *testing.T) { t.Errorf("emits lists %s, an event the seat says", e) } } - // ADR 0618 §4: and it hears each node's engine say its reads changed. - if !slices.Equal(m.Consumes, []string{"mesh-delivery-indexer.views.graph", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", "node-engine.manifest-reads-changed"}) { + // ADR 0618 §4: and it hears each node's engine say its reads changed; ADR 0636 §4 (issue 1966): and the seat list's two events. + if !slices.Equal(m.Consumes, []string{"mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-delivery-indexer.views.graph", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", "node-engine.manifest-reads-changed"}) { t.Errorf("consumes = %v, want the indexer's views of graph, manifests and modules and the engines' reads changed", m.Consumes) } if want := []string{"mesh-delivery-indexer.graph", "mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules", + "seat:mesh-catalog.seat-definitions", // ADR 0636 §4: the seat list "seat:mesh-hand-acts.hand-act", "seat:mesh-hand-acts.hand-acts", "seat:mesh-hand-acts.warranted", // ADR 0565 §9, §11: the seat alone "seat:node-backup.backed-up", "seat:node-backup.now", "seat:node-engine.manifest-reads"}; !slices.Equal(slices.Sorted(slices.Values(m.Invokes)), want) { // ADR 0618 §3 diff --git a/modules/mesh-delivery-assigner/module.json b/modules/mesh-delivery-assigner/module.json index 343388ba..f2cea462 100644 --- a/modules/mesh-delivery-assigner/module.json +++ b/modules/mesh-delivery-assigner/module.json @@ -39,6 +39,8 @@ "operator-channel" ], "consumes": [ + "mesh-catalog.seat-definition-changed", + "mesh-catalog.seat-definition-removed", "mesh-delivery-indexer.views.graph", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", @@ -48,6 +50,7 @@ "mesh-delivery-indexer.graph", "mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules", + "seat:mesh-catalog.seat-definitions", "seat:mesh-hand-acts.hand-act", "seat:mesh-hand-acts.hand-acts", "seat:mesh-hand-acts.warranted", @@ -82,7 +85,9 @@ "assigner_read_copies", "assigner_first_views", "assigner_first_view_again", - "assigner_engine_reads" + "assigner_engine_reads", + "assigner_seat_list", + "assigner_seat_list_again" ], "state": [ { "name": "assignments", "history": 64 },