diff --git a/modules/mesh-conditions/cmd/mesh-conditions/homes.go b/modules/mesh-conditions/cmd/mesh-conditions/homes.go index 96671fb72..1bac682cb 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/homes.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/homes.go @@ -102,6 +102,23 @@ var Homes = map[string]Home{ // A reader whose owner does not answer its first view past two minutes (ADR 0615 §6): written by each reader in its own // findings (mesh-config, mesh-vault), so the kind has no one observer. "first-view-unanswered": {Source: "views", Why: "every reader of the indexer's or the assigner's state writes its own"}, + // An owner's views event too large to say (ADR 0615 §5): written by each owner of a views family in its own findings + // (the indexer, the assigner), so the kind has no one observer. + "view-too-large": {Source: "views", Why: "every owner of a views family writes its own"}, + // The builder's own (design 51): a build that ended failed, a floor whose holder said nothing past its bound, an old + // form held seven days by a counted node-engine, a tag's removal asked and not done. + "build-failed": {Source: "builder", Observer: builder}, + "floor-silent": {Source: "builder", Observer: builder}, + "old-form-held": {Source: "builder", Observer: builder}, + "tag-removal-not-done": {Source: "builder", Observer: builder}, + // The indexer's own (design 26, ADR 0625): a seat definition that does not parse, generated types out of step with + // their definition, one seat defined by two repositories' trunks, a manifest accepted and not said. + "seat-definition-unparsed": {Source: "indexer", Observer: indexer}, + "generated-types-out-of-step": {Source: "indexer", Observer: indexer}, + "seat-defined-twice": {Source: "indexer", Observer: indexer}, + "accepted-unsaid": {Source: "indexer", Observer: indexer}, + // The package seats' holder (design 32): the forge refused the packages account's write token. + "packages-token-refused": {Source: "gitea", Observer: gitea}, // The kinds that end with the controller and are carried until its lease write (clauses 4, 9). "controller-deaf": {Source: "S4", Controller: true, Carried: true}, "lease-lost": {Source: "S12", Controller: true, Carried: true}, @@ -166,6 +183,10 @@ const ( artifactStore = "artifact-store-tools" configModule = "mesh-config" vaultModule = "mesh-vault" + builder = "mesh-delivery-builder" + indexer = "mesh-delivery-indexer" + gitea = "gitea" + enrolment = "mesh-enrolment" ) // homeOf is the home of a condition's kind, and whether the table has one. diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go index b8fa3e7f7..4ea21c90d 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go @@ -43,7 +43,10 @@ var Observers = []string{health.WatchmanModule, health.SelfCheckModule, health.H "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"} + "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 other states it reads, as `.`. const ( diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go index b84caac28..3c52b87a4 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper_test.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "slices" "sort" "strings" "sync" @@ -276,10 +277,16 @@ func TestEveryControllerKindHasItsHome(t *testing.T) { } for kind, observer := range map[string]string{"needs-operator": watchman, "machine-network": watchman, "lease-split": selfCheck, "data-shrank": selfCheck, "retire-waiting": provisioner, "asks-undelivered": messenger, - "image-missing": artifactStore} { + "image-missing": artifactStore, "build-failed": builder, "floor-silent": builder, "old-form-held": builder, + "tag-removal-not-done": builder, "seat-definition-unparsed": indexer, "generated-types-out-of-step": indexer, + "seat-defined-twice": indexer, "accepted-unsaid": indexer, "packages-token-refused": gitea} { 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"} { @@ -294,7 +301,22 @@ func TestEveryControllerKindHasItsHome(t *testing.T) { t.Errorf("the ended probe's kind %s is %+v", kind, h) } } - if len(Homes) != 89 || carried != 37 { // 19 of ADR 0564, the 63 of the table, plan-failed and stalled, mesh-config's four (design 32) and the readers' unanswered first view + // The readers' unanswered first view and the owners' views too large to say are every reader's and owner's own. + for _, kind := range []string{"first-view-unanswered", "view-too-large"} { + if h := Homes[kind]; h.Observer != "" || h.Carried || h.Controller { + t.Errorf("%s's home is %+v", kind, h) + } + } + // Each module that writes a reader's unanswered first view (ADR 0615 §6) is read. + for _, reader := range []string{watchman, "mesh-delivery-teller", enrolment, builder, "mesh-delivery-checker", + "mesh-delivery-view", indexer, configModule, vaultModule, gitea} { + 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. + if len(Homes) != 99 || carried != 37 { t.Errorf("the table holds %d kinds, %d carried", len(Homes), carried) } } diff --git a/modules/mesh-conditions/cmd/mesh-conditions/main.go b/modules/mesh-conditions/cmd/mesh-conditions/main.go index aad52055e..ad8beae7e 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/main.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/main.go @@ -69,7 +69,7 @@ func watch(k *Keeper, v *views.Reader, state, observer string, handle func(stdio for wait := 5 * time.Second; ; wait = min(wait*2, 5*time.Minute) { var err error if v.Follows(state) { - // The assigner's states are asked once of its read tool and followed by its events (ADR 0615). + // The assigner's and the indexer's states are asked once of their read tools and followed by their events (ADR 0615). err = v.Watch(state, func(key string, value []byte, gone bool) { op := stdio.OpPut if gone { @@ -143,15 +143,18 @@ const ( WantsTool = delivery.AssignerModule + ".assigner_read_wants" PlacementTool = delivery.AssignerModule + ".assigner_read_placement" FindingsTool = delivery.AssignerModule + ".assigner_read_findings" + // IndexerFindingsTool is the indexer's read tool of its findings: the indexer's state, too, is asked of its owner. + IndexerFindingsTool = delivery.IndexerModule + ".indexer_read_findings" ) -// NewViews is the keeper's pictures of the assigner's wants, placement and findings. A first view it cannot get past its +// NewViews is the keeper's pictures of the assigner's wants, placement and findings and of the indexer's findings. A first view it cannot get past its // bound is a finding of its own, taken in as any observer's: the keeper raises the condition itself. func NewViews(k *Keeper) *views.Reader { v := &views.Reader{Module: Module, Logf: logf, Findings: ownFindings{k}, Acts: handacts.Recorder{Ask: stdio.Ask}} v.Follow(delivery.AssignerModule, "wants", WantsTool) v.Follow(delivery.AssignerModule, "placement", PlacementTool) v.Follow(delivery.AssignerModule, health.FindingsState, FindingsTool) + v.Follow(delivery.IndexerModule, health.FindingsState, IndexerFindingsTool) 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 21cae475d..1bac348bf 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go @@ -30,16 +30,17 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) { !slices.Equal(Verbs, catalogue.VerbNames(seat.Serves)) { t.Errorf("module %s runs as %s and claims %+v; the seat serves %v", m.Module, m.RunsAs, m.Claims, catalogue.VerbNames(seat.Serves)) } - // The assigner's wants, placement and findings are asked once of its read tools and followed by its events, not read - // (ADR 0615 §1). + // The assigner's wants, placement and findings, and the indexer's findings, are asked once of their read tools and + // followed by their events, not read (ADR 0615 §1). reads := []string{HavesRead, AttemptsRead} for _, o := range Observers { - if o != delivery.AssignerModule { + if o != delivery.AssignerModule && o != delivery.IndexerModule { reads = append(reads, o+"."+health.FindingsState) } } - if !slices.Equal(m.Invokes, []string{WantsTool, PlacementTool, FindingsTool, "seat:mesh-hand-acts.hand-act"}) || - !slices.Equal(m.Consumes, []string{"mesh-delivery-assigner.views.wants", "mesh-delivery-assigner.views.placement", "mesh-delivery-assigner.views.findings"}) { + if !slices.Equal(m.Invokes, []string{WantsTool, PlacementTool, FindingsTool, IndexerFindingsTool, "seat:mesh-hand-acts.hand-act"}) || + !slices.Equal(m.Consumes, []string{"mesh-delivery-assigner.views.wants", "mesh-delivery-assigner.views.placement", + "mesh-delivery-assigner.views.findings", "mesh-delivery-indexer.views.findings"}) { t.Errorf("invokes %v, consumes %v", m.Invokes, m.Consumes) } slices.Sort(reads) diff --git a/modules/mesh-conditions/module.json b/modules/mesh-conditions/module.json index 41c97b37f..b40fea473 100644 --- a/modules/mesh-conditions/module.json +++ b/modules/mesh-conditions/module.json @@ -27,6 +27,11 @@ "nfs-server.findings", "gitea.findings", "mesh-build-go.findings", + "mesh-delivery-builder.findings", + "mesh-delivery-checker.findings", + "mesh-delivery-teller.findings", + "mesh-delivery-view.findings", + "mesh-enrolment.findings", "mesh-healers.attempts", "node-engine.have" ], @@ -34,12 +39,14 @@ "mesh-delivery-assigner.assigner_read_wants", "mesh-delivery-assigner.assigner_read_placement", "mesh-delivery-assigner.assigner_read_findings", + "mesh-delivery-indexer.indexer_read_findings", "seat:mesh-hand-acts.hand-act" ], "consumes": [ "mesh-delivery-assigner.views.wants", "mesh-delivery-assigner.views.placement", - "mesh-delivery-assigner.views.findings" + "mesh-delivery-assigner.views.findings", + "mesh-delivery-indexer.views.findings" ], "state": [ "conditions",