diff --git a/modules/mesh-conditions/cmd/mesh-conditions/catalog.go b/modules/mesh-conditions/cmd/mesh-conditions/catalog.go new file mode 100644 index 000000000..2b176aa2d --- /dev/null +++ b/modules/mesh-conditions/cmd/mesh-conditions/catalog.go @@ -0,0 +1,231 @@ +package main + +// The keeper finds its sources in the catalog (design 47 G §365): the modules whose catalog entry declares the finding +// events of §383, in any watched branch's newest revision. A module in no branch's revision has its conditions cleared with +// `module left the catalog`; a module's conditions on a node are cleared with `module left the node` once the node-engine +// says the module stopped there and the newest assignment graph revision no longer places it. No module is named here (§7). + +import ( + "encoding/json" + "slices" + "sort" + "strings" + + catseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + wants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" + + engine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" +) + +// The reasons a catalog or an assignment takes a module's conditions away (design 47 G §365). +const ( + whyLeftCatalog = "module left the catalog" + whyLeftNode = "module left the node" +) + +// declaresFindings says whether the entry declares the finding events of §383 in its streams. +func declaresFindings(e catseat.CatalogEntry) bool { + if slices.ContainsFunc(e.Streams, func(s string) bool { return strings.Contains(s, EventFindingWritten) }) { + return true + } + streams, _ := e.Manifest["streams"].([]any) + for _, s := range streams { + m, _ := s.(map[string]any) + events, _ := m["events"].([]any) + for _, ev := range events { + if name, _ := ev.(string); strings.HasPrefix(name, EventFindingWritten) { + return true + } + } + } + return false +} + +// CatalogHeld takes the newest catalog revision of one watched branch: the sources are the modules declaring the finding +// events in any branch, and the conditions of a module in no branch are cleared. +func (k *Keeper) CatalogHeld(branch string, entries map[string]catseat.CatalogEntry) error { + k.mu.Lock() + k.init() + k.halves[branch] = entries + var gone []string + for observer := range k.findings { + if observer == Module { + continue + } + if _, ok := k.entry(observer); !ok { + gone = append(gone, observer) + } + } + k.mu.Unlock() + sort.Strings(gone) + var first error + for _, observer := range gone { + k.clearObserver(observer, "", whyLeftCatalog, &first) + } + return first +} + +// entry is the observer's entry in the first branch (in name order) that holds one. Called with mu held. +func (k *Keeper) entry(observer string) (catseat.CatalogEntry, bool) { + branches := make([]string, 0, len(k.halves)) + for b := range k.halves { + branches = append(branches, b) + } + sort.Strings(branches) + for _, b := range branches { + if e, ok := k.halves[b][observer]; ok { + return e, true + } + } + return catseat.CatalogEntry{}, false +} + +// isSource says whether the keeper takes findings from the module: its catalog entry declares the finding events. +func (k *Keeper) isSource(observer string) bool { + k.mu.Lock() + defer k.mu.Unlock() + k.init() + branches := make([]string, 0, len(k.halves)) + for b := range k.halves { + branches = append(branches, b) + } + for _, b := range branches { + if e, ok := k.halves[b][observer]; ok && declaresFindings(e) { + return true + } + } + return false +} + +// catalogHolder is Keeper.Holder: an observer whose catalog entry claims only seats held once for the mesh is held on the +// node the assignments name as the holder of one of them (design 47 G §384); unknown, it is taken from every node. +func (k *Keeper) catalogHolder(observer string) (string, bool) { + k.mu.Lock() + defer k.mu.Unlock() + k.init() + e, ok := k.entry(observer) + if !ok { + return "", false + } + claims, _ := e.Manifest["claims"].([]any) + if len(claims) == 0 { + return "", false + } + for _, c := range claims { + m, _ := c.(map[string]any) + if scope, _ := m["scope"].(string); scope != "mesh" { + return "", false + } + } + nodes := make([]string, 0, 1) + for hk, module := range k.holders { + if module == observer { + node, _, _ := strings.Cut(hk, ".") + nodes = append(nodes, node) + } + } + if len(nodes) == 0 { + return "", false + } + sort.Strings(nodes) + return nodes[0], true +} + +// AssignmentHeld takes the newest assignment graph revision: the modules it places on each node. +func (k *Keeper) AssignmentHeld(rev wants.AssignmentRevision) error { + k.mu.Lock() + k.init() + k.placed = map[string]bool{} + for _, e := range rev.Entries { + k.placed[e.Node+"."+e.Module] = true + } + k.placedHeld = true + k.mu.Unlock() + return k.sweepStopped() +} + +// EngineRunning takes a node-engine's `running` for a module on a node: stopped, it is kept until the assignment graph +// places the module there no longer. +func (k *Keeper) EngineRunning(node string, body []byte) error { + var r engine.Running + if err := json.Unmarshal(body, &r); err != nil || r.Module == "" || r.Node == "" { + k.Logf("the node-engine's running on %s is not readable: %v", node, err) + return nil + } + if node != "" && r.Node != node { + k.Logf("the node-engine's running on %s names the node %s: refused", node, r.Node) + return nil + } + k.mu.Lock() + k.init() + if r.Stopped { + k.stopped[r.Node+"."+r.Module] = true + } else { + delete(k.stopped, r.Node+"."+r.Module) + } + k.mu.Unlock() + return k.sweepStopped() +} + +// sweepStopped clears the conditions of each module stopped on a node that the held assignment revision does not place +// there; before an assignment revision is held, nothing is cleared. +func (k *Keeper) sweepStopped() error { + k.mu.Lock() + k.init() + var left []string + if k.placedHeld { + for pair := range k.stopped { + if !k.placed[pair] { + left = append(left, pair) + } + } + } + for _, pair := range left { + delete(k.stopped, pair) + } + k.mu.Unlock() + sort.Strings(left) + var first error + for _, pair := range left { + node, module, _ := strings.Cut(pair, ".") + k.clearObserver(module, node, whyLeftNode, &first) + } + return first +} + +// clearObserver clears every condition the module has, or has from the node when node is not empty, with the reason, and +// forgets what it heard of the module there. +func (k *Keeper) clearObserver(observer, node, why string, first *error) { + k.mu.Lock() + var keys []string + for key := range k.findings[observer] { + if node == "" || k.keyNode[observer][key] == node { + keys = append(keys, key) + } + } + for name, v := range k.views { + if v.observer == observer && (node == "" || v.node == node) { + for key := range v.Pending { + _ = k.Store.Delete(BoundsState, pendingKey(observer, v.node, key)) + } + for ask := range v.Asks { + _ = k.Store.Delete(BoundsState, askedKey(observer, v.node, ask)) + } + delete(k.views, name) + } + } + k.mu.Unlock() + sort.Strings(keys) + for _, key := range keys { + k.mu.Lock() + k.whys[key] = why + k.mu.Unlock() + if err := k.Finding(observer, key, nil, true); err != nil && *first == nil { + *first = err + } + k.mu.Lock() + delete(k.whys, key) + delete(k.keyNode[observer], key) + k.mu.Unlock() + } +} diff --git a/modules/mesh-conditions/cmd/mesh-conditions/events.go b/modules/mesh-conditions/cmd/mesh-conditions/events.go index 4520787dc..ec7abe5d8 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/events.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/events.go @@ -14,6 +14,7 @@ import ( "strings" "time" + engine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" cc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" seat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" watchdogs "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs" @@ -47,15 +48,15 @@ func (k *Keeper) FindingEvent(e stdio.Envelope) error { if _, ok := emitterOf(e.Key, watchdogs.EventMachineBack); ok && strings.HasPrefix(e.Key, watchdogs.Seat+".") { return k.MachinePresence(e.Body, false) } + if e.Key == engine.Seat+"."+engine.EventRunning { + return k.EngineRunning(e.Node, e.Body) + } if observer, ok := emitterOf(e.Key, EventFindingsStated); ok { return k.FindingsStated(observer, e.Node, e.Body) } return nil } -// isSource says whether the keeper takes findings from the module. -func (k *Keeper) isSource(observer string) bool { return slices.Contains(Observers, observer) } - // FindingWritten raises or changes the condition of the finding an observer says on a node. A body that is not a finding // the keeper can raise, or names another observer, is said in the journal and changes nothing. func (k *Keeper) FindingWritten(observer, node string, body []byte) error { @@ -199,7 +200,8 @@ func (k *Keeper) MachinePresence(body []byte, away bool) error { // hear subscribes to the observers' events once the runtime serves, asked again until it takes each. func hear(k *Keeper) { patterns := []string{"*." + EventFindingWritten, "*." + EventFindingCleared, "*." + EventFindingsStated, - watchdogs.Seat + "." + watchdogs.EventMachineAway, watchdogs.Seat + "." + watchdogs.EventMachineBack} + watchdogs.Seat + "." + watchdogs.EventMachineAway, watchdogs.Seat + "." + watchdogs.EventMachineBack, + engine.Seat + "." + engine.EventRunning} for _, pattern := range patterns { go func() { <-stdio.Served() diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go index ae523a835..6753dffc2 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go @@ -25,6 +25,7 @@ import ( cc "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" seat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" meshhealers "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhealers" + catseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" ) // The modules whose states the keeper reads by name: the holders of the health seats (their seats' names are the @@ -35,8 +36,6 @@ const ( healersModule = "mesh-healers" // The holders of the assignments and the catalog, whose views and findings the keeper follows (the manifest test holds // these equal to the manifest's `consumes` and `invokes`). - // engineModule is the node-engine, an observer of §387 (design 47 G §387 names mesh-host). - engineModule = "mesh-host" assignerModule = "mesh-delivery-assigner" indexerModule = "mesh-delivery-indexer" ) @@ -55,23 +54,6 @@ const ( // HistoryKeptFor is how long a transition is kept (clause 3). const HistoryKeptFor = 90 * 24 * time.Hour -// Observers are the modules whose `findings` the keeper reads (clause 2; ADR 0565 §8 for the assigner's; the lead's -// table of 2026-10-29 for the provisioner's and the router's; ADR 0571 clause 2 for the seat artifact-store's holder, -// which observes S25, image-missing). -var Observers = []string{watchmanModule, selfCheckModule, healersModule, assignerModule, - "mesh-provisioner", "messenger", artifactStore, configModule, vaultModule, - // The readers of the indexer's and the assigner's state say a first view unanswered past its bound in their own findings - // (ADR 0615 §6). - "mesh-watcher", "minio", "nfs-server", gitea, "mesh-build-go", - // The builder's, the indexer's (asked of its read tool and followed by its views events, ADR 0615 §1) and the other - // readers' of the indexer's and the assigner's state (the registry milestone's join). - builder, indexer, "mesh-delivery-checker", "mesh-delivery-teller", "mesh-delivery-view", enrolment, - // The tool runner's and claude-code's own first views (views.UnansweredFinding in their findings, ADR 0615 §6). - "node-tools", "claude-code", - // The node-engine's: a node that has read no seat list five minutes after its start, seat-list-unread (ADR 0636 §1; - // issue 1966: written on ace and raised by nobody). - engineModule} - // The other states it reads, as `.`. const ( AttemptsRead = healersModule + "." + meshhealers.StateAttempts @@ -158,6 +140,13 @@ type Keeper struct { Record func(what, why, verb string) error // away are the machines the watchman says are away (design 47 G §389). away map[string]bool + // halves are the newest catalog revisions of the watched branches, by branch and module (catalog.go); placed are the + // `.` the newest assignment graph revision places, placedHeld whether one is held; stopped are the + // `.` the node-engines say stopped. + halves map[string]map[string]catseat.CatalogEntry + placed map[string]bool + placedHeld bool + stopped map[string]bool } func (k *Keeper) init() { @@ -168,6 +157,7 @@ func (k *Keeper) init() { map[string]string{}, map[string]held{}, map[string]clearedMemo{} k.passed, k.outOfHold, k.holders = map[string]string{}, map[string]string{}, map[string]string{} k.assignments = assignerclient.NewKeys() + k.halves, k.placed, k.stopped = map[string]map[string]catseat.CatalogEntry{}, map[string]bool{}, map[string]bool{} k.views, k.keyNode, k.whys = map[string]*sourceView{}, map[string]map[string]string{}, map[string]string{} } if k.Now == nil { diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go index f8206d4d4..446b155fd 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go @@ -3,10 +3,9 @@ package main import ( "encoding/json" "fmt" - "io/fs" - "os" - "path/filepath" - "regexp" + nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + catseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + meshwants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "slices" "sort" "strings" @@ -112,9 +111,27 @@ func newRig() *rig { r.said = append(r.said, e) return nil }} + // The catalog half of the watched branch: the modules the tests hear findings from declare the finding events (§365); + // the self-check's holder claims a seat held once for the mesh. + r.half(watchmanModule, "node") + r.half(selfCheckModule, "mesh") return r } +// half puts a module in the watched branch's catalog half, declaring the finding events and claiming one seat of scope. +func (r *rig) half(module, scope string) { + r.k.mu.Lock() + r.k.init() + if r.k.halves["main"] == nil { + r.k.halves["main"] = map[string]catseat.CatalogEntry{} + } + r.k.halves["main"][module] = catseat.CatalogEntry{Module: module, Manifest: map[string]any{ + "claims": []any{map[string]any{"name": module, "scope": scope}}, + "streams": []any{map[string]any{"name": module + "-findings", "events": []any{"finding-written.*", "finding-cleared.*", "findings-stated.*"}}}, + }} + r.k.mu.Unlock() +} + func (r *rig) changes() string { var out []string for _, e := range r.said { @@ -346,10 +363,6 @@ func TestEveryControllerKindHasItsHome(t *testing.T) { if h, ok := Homes[kind]; !ok || h.Observer != observer || h.Carried { t.Errorf("%s's home is %+v", kind, h) } - // The keeper reads every observer's findings that a kind's home names (the registry milestone's join). - if !slices.Contains(Observers, observer) { - t.Errorf("%s's observer %s is not read by the keeper", kind, observer) - } } for _, kind := range []string{"resolver-wrong", "holder-silent", "archives-missing", "archives-unheld", "binding-kept", "binding-moved", "binding-moving", "grant-composes-nothing", "root-not-free", "cleanup-waiting", "data-held-twice"} { @@ -370,18 +383,6 @@ func TestEveryControllerKindHasItsHome(t *testing.T) { t.Errorf("%s's home is %+v", kind, h) } } - // Each module that writes a reader's unanswered first view (ADR 0615 §6), or that a delivery part's consumer stopped - // hearing, is read: the delivery parts and the tool runner by name (other repositories), and every catalogue module - // whose code writes a views finding, found in the catalogue's tree, so a new writer cannot go unobserved. - // And the node-engine, whose node writes seat-list-unread when it has read no seat list (ADR 0636 §1, issue 1966). - readers := []string{watchman, "mesh-delivery-teller", enrolment, builder, "mesh-delivery-checker", "mesh-delivery-view", - indexer, assignerModule, configModule, vaultModule, gitea, "node-tools", "mesh-host"} - readers = append(readers, viewFindingWriters(t, "../../..")...) - for _, reader := range readers { - if !slices.Contains(Observers, reader) { - t.Errorf("the reader %s's findings are not read by the keeper", reader) - } - } // 19 of ADR 0564, the 63 of the table, plan-failed and stalled, mesh-config's four (design 32), the readers' unanswered // first view, the owners' view too large, the builder's four, the indexer's four and the package seats' token refused. // And the builder's three publishing kinds and the delivery parts' family not heard: 103. @@ -393,40 +394,6 @@ func TestEveryControllerKindHasItsHome(t *testing.T) { } } -// viewFindingWriters are the catalogue modules (under modules) whose non-test Go code writes a views finding into its own -// findings: views.UnansweredFinding, views.TooLargeFinding, views.UnsaidFinding or views.OwnFindings. -func viewFindingWriters(t *testing.T, modules string) []string { - t.Helper() - writes := regexp.MustCompile(`(views\.(UnansweredFinding|TooLargeFinding|UnsaidFinding)|meshconditionsclient\.FindingWriter)\b`) - var out []string - entries, err := os.ReadDir(modules) - if err != nil { - t.Fatal(err) - } - for _, e := range entries { - if !e.IsDir() || e.Name() == Module { - continue - } - found := false - _ = filepath.WalkDir(filepath.Join(modules, e.Name()), func(path string, d fs.DirEntry, err error) error { - if err != nil || found || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { - return nil - } - if src, err := os.ReadFile(path); err == nil && writes.Match(src) { - found = true - } - return nil - }) - if found { - out = append(out, e.Name()) - } - } - if len(out) < 5 { - t.Fatalf("found %d catalogue modules writing views findings in %s: the tree was not read (%v)", len(out), modules, out) - } - return out -} - // ADR 0634 §1, §4 (issue 1966): the keeper reads a condition written before the seat named its layout (the controller's // body: `silenced` null, `actions` empty, the times with an offset) and writes it back in the seat's layout. func TestTheKeeperReadsTheOldLayoutAndWritesTheSeatsLayout(t *testing.T) { @@ -569,6 +536,50 @@ func TestTheKeeperSetsItsViewFromWhatAnObserverStates(t *testing.T) { } return nil }, []string{"machine.d.silent", "machine.g.silent"}, ""}, + // Design 47 G §365: the sources are the catalog's, a module that left the catalog or the node loses its conditions. + {"a module not declaring the finding events is no source, one the catalog half declares is heard with no code change", func() error { + if err := r.k.FindingWritten("fixture", "ace", mustJSON(t, f("fixture", "z"))); err != nil { + return err + } + if _, ok := r.open("machine.z.silent"); ok { + t.Errorf("a module in no catalog half raised a condition") + } + r.half("fixture", "node") + return r.k.FindingWritten("fixture", "ace", mustJSON(t, f("fixture", "z"))) + }, []string{"machine.d.silent", "machine.g.silent", "machine.z.silent"}, ""}, + {"an observer claiming only mesh seats is held where the assignments place it, with no fixed name", func() error { + r.k.holders["g14.mesh-self-check"] = selfCheck + if node, once := r.k.catalogHolder(selfCheck); node != "g14" || !once { + t.Errorf("the holder of %s is %q, %v", selfCheck, node, once) + } + if _, once := r.k.catalogHolder("fixture"); once { + t.Errorf("a node seat's module is held once") + } + return nil + }, []string{"machine.d.silent", "machine.g.silent", "machine.z.silent"}, ""}, + {"stopped but still placed, or placed by no revision held, clears nothing", func() error { + body := mustJSON(t, nodeengine.Running{Module: "fixture", Node: "ace", Stopped: true, At: r.now}) + if err := r.k.EngineRunning("ace", body); err != nil { + return err + } + return r.k.AssignmentHeld(meshwants.AssignmentRevision{Entries: []meshwants.NodeEntryRef{{Module: "fixture", Node: "ace"}}}) + }, []string{"machine.d.silent", "machine.g.silent", "machine.z.silent"}, ""}, + {"stopped and no longer placed is cleared as left the node", func() error { + return r.k.AssignmentHeld(meshwants.AssignmentRevision{Revision: 2}) + }, []string{"machine.d.silent", "machine.g.silent"}, whyLeftNode}, + {"an assignment revision alone clears nothing, and a module that left the catalog is cleared", func() error { + if err := r.k.FindingWritten("fixture", "ace", mustJSON(t, f("fixture", "z"))); err != nil { + return err + } + r.said = nil + if err := r.k.AssignmentHeld(meshwants.AssignmentRevision{Revision: 3}); err != nil { + return err + } + if _, ok := r.open("machine.z.silent"); !ok { + t.Errorf("an assignment revision alone cleared a module's condition") + } + return r.k.CatalogHeld("main", map[string]catseat.CatalogEntry{watchmanModule: {}, selfCheckModule: {}}) + }, []string{"machine.d.silent", "machine.g.silent"}, whyLeftCatalog}, } for _, s := range steps { if err := s.do(); err != nil { diff --git a/modules/mesh-conditions/cmd/mesh-conditions/main.go b/modules/mesh-conditions/cmd/mesh-conditions/main.go index cbec7f8c2..9b5a81ddc 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/main.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/main.go @@ -18,6 +18,8 @@ import ( "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient" + meshwants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants" "git.novox.be/novox/mesh-sdk/go/views" ) @@ -59,7 +61,9 @@ func sayOnSeat(e Event) error { // NewKeeper is the keeper over the runtime. func NewKeeper() *Keeper { - return &Keeper{Store: runtimeStore{}, Say: sayOnSeat, Now: time.Now, Logf: logf} + k := &Keeper{Store: runtimeStore{}, Say: sayOnSeat, Now: time.Now, Logf: logf} + k.Holder = k.catalogHolder + return k } // watch keeps a watch of one state, said blind while it cannot be read: a state not on the bus yet (an observer not @@ -146,6 +150,10 @@ func NewViews(k *Keeper) *views.Reader { v := &views.Reader{Module: Module, Logf: logf, Findings: ownFindings{k}, Acts: meshhandactsclient.Recorder{Ask: stdio.Ask}} v.Follow(assignerModule, "wants", WantsTool) v.Follow(assignerModule, "assignments", AssignmentsTool) + // The newest assignment graph revision, which says where a module that stopped is placed no more (design 47 G §365). + if _, err := meshwantsclient.WireAssignment(v, stdio.Ask, func(rev meshwants.AssignmentRevision) { _ = k.AssignmentHeld(rev) }); err != nil { + logf("the assignment half is not followed: %v", err) + } return v } diff --git a/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go b/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go index 066f9b6b9..6dc3c9aa5 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go @@ -36,9 +36,9 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) { // The assigner's wants and assignments are asked once of their read tools and followed by their events (ADR 0615 §1); // the observers' findings are heard as events and no `findings` is read (design 47 G §383). reads := []string{HavesRead, AttemptsRead} - if !slices.Equal(m.Invokes, []string{WantsTool, AssignmentsTool, "seat:mesh-hand-acts.hand-act"}) || + if !slices.Equal(m.Invokes, []string{WantsTool, AssignmentsTool, "seat:mesh-hand-acts.hand-act", "seat:mesh-wants.revision"}) || !slices.Equal(m.Consumes, []string{"mesh-delivery-assigner.views.wants", "mesh-delivery-assigner.views.assignments", - "*.finding-written", "*.finding-cleared", "*.findings-stated", "mesh-watchdogs.machine-away", "mesh-watchdogs.machine-back"}) { + "*.finding-written", "*.finding-cleared", "*.findings-stated", "mesh-watchdogs.machine-away", "mesh-watchdogs.machine-back", "mesh-wants.assignment-revision", "node-engine.running"}) { t.Errorf("invokes %v, consumes %v", m.Invokes, m.Consumes) } for _, r := range m.Reads { diff --git a/modules/mesh-conditions/go.mod b/modules/mesh-conditions/go.mod index 717626fd6..7ce3ace27 100644 --- a/modules/mesh-conditions/go.mod +++ b/modules/mesh-conditions/go.mod @@ -12,6 +12,8 @@ require ( git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhealers v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000 github.com/nats-io/nats.go v1.54.0 diff --git a/modules/mesh-conditions/module.json b/modules/mesh-conditions/module.json index 3414ae96b..b0431616d 100644 --- a/modules/mesh-conditions/module.json +++ b/modules/mesh-conditions/module.json @@ -20,7 +20,8 @@ "invokes": [ "mesh-delivery-assigner.assigner_read_wants", "mesh-delivery-assigner.assigner_read_assignments", - "seat:mesh-hand-acts.hand-act" + "seat:mesh-hand-acts.hand-act", + "seat:mesh-wants.revision" ], "consumes": [ "mesh-delivery-assigner.views.wants", @@ -29,7 +30,9 @@ "*.finding-cleared", "*.findings-stated", "mesh-watchdogs.machine-away", - "mesh-watchdogs.machine-back" + "mesh-watchdogs.machine-back", + "mesh-wants.assignment-revision", + "node-engine.running" ], "state": [ "conditions",