From 4d597eb9dcdac7684c46d53d5ad5cf0ebf214129 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 12:23:37 +0100 Subject: [PATCH] CAT-RD1..RD10: the readers use mesh-sdk's views.Reader, say an unanswered first view in their own findings (mesh-conditions observes them) and record a re-read through mesh-hand-acts --- .../cmd/artifact-store-tools/door.go | 2 +- .../cmd/artifact-store-tools/holder.go | 2 +- .../cmd/artifact-store-tools/main.go | 14 ++---------- .../cmd/artifact-store-tools/store_test.go | 2 +- modules/gitea/module.json | 6 +++-- .../cmd/mesh-build-go/announce.go | 2 ++ .../cmd/mesh-build-go/compose.go | 22 +++++++++++++++++-- .../cmd/mesh-build-go/compose_test.go | 22 +++++++++++++++++++ .../cmd/mesh-build-go/manifest_test.go | 2 +- modules/mesh-build-go/module.json | 2 +- .../cmd/mesh-conditions/keeper.go | 5 ++++- .../cmd/mesh-conditions/main.go | 5 +++-- .../cmd/mesh-conditions/manifest_test.go | 2 +- modules/mesh-conditions/module.json | 8 ++++++- .../cmd/mesh-provisioner/main.go | 17 +++----------- .../cmd/mesh-provisioner/provisioner_test.go | 4 ++-- modules/mesh-provisioner/module.json | 3 ++- .../cmd/mesh-self-check/main.go | 14 +++--------- .../cmd/mesh-self-check/probes.go | 5 ++++- modules/mesh-self-check/module.json | 3 ++- modules/mesh-watcher/cmd/mesh-watcher/main.go | 5 +++-- .../cmd/mesh-watcher/manifest_test.go | 4 ++-- modules/mesh-watcher/module.json | 6 ++++- modules/minio/cmd/object-store/main.go | 5 +++-- .../minio/cmd/object-store/manifest_test.go | 2 +- modules/minio/module.json | 6 ++++- modules/nfs-server/cmd/nfs-server/main.go | 5 +++-- .../cmd/nfs-server/manifest_test.go | 2 +- modules/nfs-server/module.json | 6 +++-- 29 files changed, 113 insertions(+), 70 deletions(-) diff --git a/modules/artifact-store-tools/cmd/artifact-store-tools/door.go b/modules/artifact-store-tools/cmd/artifact-store-tools/door.go index f93d51763..c34b3bcbc 100644 --- a/modules/artifact-store-tools/cmd/artifact-store-tools/door.go +++ b/modules/artifact-store-tools/cmd/artifact-store-tools/door.go @@ -13,7 +13,7 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/views" "artifactstoretools/internal/registry" ) diff --git a/modules/artifact-store-tools/cmd/artifact-store-tools/holder.go b/modules/artifact-store-tools/cmd/artifact-store-tools/holder.go index e2c3308ae..ef7422419 100644 --- a/modules/artifact-store-tools/cmd/artifact-store-tools/holder.go +++ b/modules/artifact-store-tools/cmd/artifact-store-tools/holder.go @@ -29,7 +29,7 @@ import ( "git.novox.be/novox/mesh-sdk/go/handacts" "git.novox.be/novox/mesh-sdk/go/health" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/views" "artifactstoretools/internal/registry" ) diff --git a/modules/artifact-store-tools/cmd/artifact-store-tools/main.go b/modules/artifact-store-tools/cmd/artifact-store-tools/main.go index 3ef95556b..630b78b78 100644 --- a/modules/artifact-store-tools/cmd/artifact-store-tools/main.go +++ b/modules/artifact-store-tools/cmd/artifact-store-tools/main.go @@ -11,7 +11,6 @@ package main import ( "context" - "encoding/json" "fmt" "os" "time" @@ -20,7 +19,7 @@ import ( "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/handacts" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/views" ) func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) } @@ -31,7 +30,7 @@ func main() { Run: ExecRunner, UID: os.Getuid(), } - v := &views.Reader{Module: "artifact-store-tools", Logf: logf, Findings: viewFindings{}} + v := &views.Reader{Module: "artifact-store-tools", Logf: logf, Findings: views.OwnFindings{}, Acts: handacts.Recorder{Ask: stdio.Ask}} v.Follow(delivery.AssignerModule, "wants", WantsTool) v.Follow(delivery.IndexerModule, "manifests", ManifestsTool) h := &Holder{Bus: runtimeBus{views: v}, Door: DoorAt(os.Getenv("MESH_ARTIFACT_STORE_ADDRESS_FILE")), Listed: s.View, @@ -51,12 +50,3 @@ const ( WantsTool = delivery.AssignerModule + ".assigner_read_wants" ManifestsTool = delivery.IndexerModule + ".indexer_read_manifests" ) - -// viewFindings writes the holder's finding of a first view unanswered into its own state findings. -type viewFindings struct{} - -func (viewFindings) Put(key string, value []byte) error { - _, err := stdio.State(FindingsState).Put(key, json.RawMessage(value)) - return err -} -func (viewFindings) Delete(key string) error { return stdio.State(FindingsState).Delete(key) } diff --git a/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go b/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go index 325b522b1..618543de5 100644 --- a/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go +++ b/modules/artifact-store-tools/cmd/artifact-store-tools/store_test.go @@ -1,8 +1,8 @@ package main import ( - "git.novox.be/novox/mesh-delivery/sdk/views" "git.novox.be/novox/mesh-sdk/go/catalogue" + "git.novox.be/novox/mesh-sdk/go/views" "context" "encoding/json" diff --git a/modules/gitea/module.json b/modules/gitea/module.json index 9d0a0a0e9..9348fe3bc 100644 --- a/modules/gitea/module.json +++ b/modules/gitea/module.json @@ -562,7 +562,8 @@ ], "state": [ "bound", - "announced" + "announced", + "findings" ], "reads": [ "mesh-provisioner.plans", @@ -575,7 +576,8 @@ "mesh-delivery-gate.source" ], "invokes": [ - "mesh-delivery-indexer.indexer_read_modules" + "mesh-delivery-indexer.indexer_read_modules", + "seat:mesh-hand-acts.hand-act" ], "consumes": [ "mesh-delivery-indexer.views.modules" diff --git a/modules/mesh-build-go/cmd/mesh-build-go/announce.go b/modules/mesh-build-go/cmd/mesh-build-go/announce.go index 2163bca16..3550c87d1 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/announce.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/announce.go @@ -15,6 +15,8 @@ var Kept = []announce.State{ {Name: "keys", Readable: true}, {Name: "steps", Readable: true}, {Name: "pauses", Readable: true}, + // The condition keeper raises a first view of the indexer's manifests it could not get (ADR 0615 §6) from this. + {Name: "findings", Readable: true}, } // keepStates announces Kept as this module's (stdio.Keeps): main calls it before it serves. diff --git a/modules/mesh-build-go/cmd/mesh-build-go/compose.go b/modules/mesh-build-go/cmd/mesh-build-go/compose.go index f8b397402..5a5de08a5 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/compose.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/compose.go @@ -22,12 +22,14 @@ import ( "path/filepath" "strings" "sync" + "time" "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" stdio "git.novox.be/novox/mesh-sdk/go" sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/composer" "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/views" ) // ManifestsTool is the delivery indexer's read tool of its manifests by digest, which a compose ask's request names (the @@ -71,12 +73,28 @@ func (m *Module) compose(ctx context.Context, fetch buildstep.StepFunc, a delive return delivery.ComposeAnswer{Built: built, Key: key, Verdict: verdict}, nil } -// askManifest asks the indexer's read tool for one manifest by its key: nil when it keeps none. +// askManifest asks the indexer's read tool for one manifest by its key: nil when it keeps none. A call the indexer does not +// answer is a finding of this module's own, which the condition keeper raises (ADR 0615 §6) and the next answer clears. func askManifest(key string) (json.RawMessage, error) { - raw, err := stdio.Ask(ManifestsTool, map[string]any{"key": key}) + return askManifestVia(stdio.Ask, views.OwnFindings{}, time.Now, key) +} + +func askManifestVia(ask func(string, any) (json.RawMessage, error), findings interface { + Put(string, []byte) error + Delete(string) error +}, now func() time.Time, key string) (json.RawMessage, error) { + st := views.Status{Owner: delivery.IndexerModule, Tool: ManifestsTool, State: "manifests", Asked: now().UTC()} + raw, err := ask(ManifestsTool, map[string]any{"key": key}) if err != nil { + st.Error = err.Error() + if k, v, ferr := views.UnansweredFinding("mesh-build-go", st, now()); ferr == nil { + _ = findings.Put(k, v) + } return nil, err } + if k, _, ferr := views.UnansweredFinding("mesh-build-go", st, now()); ferr == nil { + _ = findings.Delete(k) + } var a struct { Found bool `json:"found"` Value json.RawMessage `json:"value"` diff --git a/modules/mesh-build-go/cmd/mesh-build-go/compose_test.go b/modules/mesh-build-go/cmd/mesh-build-go/compose_test.go index bfe808f12..e38e5a856 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/compose_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/compose_test.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "errors" "os" "path/filepath" "slices" @@ -130,6 +131,27 @@ func TestTheModuleReadsTheIndexersManifests(t *testing.T) { } } +// ADR 0615 §6: an indexer that does not answer is a finding of the module's own, and its next answer clears it. +func TestAnIndexerThatDoesNotAnswerIsAFinding(t *testing.T) { + held := map[string][]byte{} + f := mapFindings(held) + at := func() time.Time { return time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC) } + if _, err := askManifestVia(func(string, any) (json.RawMessage, error) { return nil, errors.New("no responders") }, f, at, "k"); err == nil || len(held) != 1 { + t.Fatalf("err %v, findings %v", err, held) + } + raw, err := askManifestVia(func(string, any) (json.RawMessage, error) { + return json.RawMessage(`{"found":true,"value":{"x":1}}`), nil + }, f, at, "k") + if err != nil || string(raw) != `{"x":1}` || len(held) != 0 { + t.Errorf("raw %s, err %v, findings %v", raw, err, held) + } +} + +type mapFindings map[string][]byte + +func (m mapFindings) Put(k string, v []byte) error { m[k] = v; return nil } +func (m mapFindings) Delete(k string) error { delete(m, k); return nil } + // ADR 0593 §7 (C9): a compose ask naming the node-engine's repository builds its composer command in the Go module at the // repository's root, under a key of that repository, commit and package, and runs the program over the request with its // manifests filled; the module's answer is the program's verdict whole. diff --git a/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go b/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go index 8e3790b95..f8c5e1589 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go @@ -106,7 +106,7 @@ func TestManifestKeepsItsStatesClaimsItsBuildSeatAndDeclaresItsHealthTool(t *tes } } slices.Sort(states) - if want := []string{"keys", "pauses", "steps", "toolchains"}; !slices.Equal(states, want) { + if want := []string{"findings", "keys", "pauses", "steps", "toolchains"}; !slices.Equal(states, want) { t.Errorf("state = %v, want %v", states, want) } if len(m.Claims) != 1 || m.Claims[0].Name != Seat { diff --git a/modules/mesh-build-go/module.json b/modules/mesh-build-go/module.json index f4c2ab549..cf557092e 100644 --- a/modules/mesh-build-go/module.json +++ b/modules/mesh-build-go/module.json @@ -10,7 +10,7 @@ "state": [ "toolchains", "keys", - {"name": "steps", "ttl-seconds": 172800}, "pauses" + {"name": "steps", "ttl-seconds": 172800}, "pauses", "findings" ], "claims": [ { diff --git a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go index 34c4c93c0..9e87d9f4d 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/keeper.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/keeper.go @@ -40,7 +40,10 @@ const HistoryKeptFor = 90 * 24 * time.Hour // 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{health.WatchmanModule, health.SelfCheckModule, health.HealersModule, delivery.AssignerModule, - "mesh-provisioner", "messenger", artifactStore, configModule} + "mesh-provisioner", "messenger", artifactStore, configModule, + // 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 other states it reads, as `.`. const ( diff --git a/modules/mesh-conditions/cmd/mesh-conditions/main.go b/modules/mesh-conditions/cmd/mesh-conditions/main.go index 304652ba9..aad52055e 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/main.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/main.go @@ -17,7 +17,8 @@ import ( "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/health" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/handacts" + "git.novox.be/novox/mesh-sdk/go/views" ) func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) } @@ -147,7 +148,7 @@ const ( // NewViews is the keeper's pictures of the assigner's wants, placement and 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}} + 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) diff --git a/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go b/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go index 74cba26e2..21cae475d 100644 --- a/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go +++ b/modules/mesh-conditions/cmd/mesh-conditions/manifest_test.go @@ -38,7 +38,7 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) { reads = append(reads, o+"."+health.FindingsState) } } - if !slices.Equal(m.Invokes, []string{WantsTool, PlacementTool, FindingsTool}) || + 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"}) { t.Errorf("invokes %v, consumes %v", m.Invokes, m.Consumes) } diff --git a/modules/mesh-conditions/module.json b/modules/mesh-conditions/module.json index 45608010b..3f047f524 100644 --- a/modules/mesh-conditions/module.json +++ b/modules/mesh-conditions/module.json @@ -21,13 +21,19 @@ "mesh-provisioner.findings", "artifact-store-tools.findings", "mesh-config.findings", + "mesh-watcher.findings", + "minio.findings", + "nfs-server.findings", + "gitea.findings", + "mesh-build-go.findings", "mesh-healers.attempts", "node-engine.have" ], "invokes": [ "mesh-delivery-assigner.assigner_read_wants", "mesh-delivery-assigner.assigner_read_placement", - "mesh-delivery-assigner.assigner_read_findings" + "mesh-delivery-assigner.assigner_read_findings", + "seat:mesh-hand-acts.hand-act" ], "consumes": [ "mesh-delivery-assigner.views.wants", diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/main.go b/modules/mesh-provisioner/cmd/mesh-provisioner/main.go index 8575bdccc..9220412d2 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/main.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/main.go @@ -8,7 +8,6 @@ package main import ( "context" - "encoding/json" "fmt" "os" "strings" @@ -19,9 +18,9 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/asks" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/health" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/handacts" + "git.novox.be/novox/mesh-sdk/go/views" ) // Seat is the role this module holds, once for the mesh, and Verb its one verb. @@ -182,20 +181,10 @@ const ( ManifestsTool = delivery.IndexerModule + ".indexer_read_manifests" ) -// viewFindings writes the provisioner's finding of a first view unanswered into its own state findings, which the -// condition keeper raises the condition from. -type viewFindings struct{} - -func (viewFindings) Put(key string, value []byte) error { - _, err := stdio.State(health.FindingsState).Put(key, json.RawMessage(value)) - return err -} -func (viewFindings) Delete(key string) error { return stdio.State(health.FindingsState).Delete(key) } - // newViews is the provisioner's pictures of the assigner's placement and wants and the indexer's modules and manifests: // each asked once of its owner's read tool, then followed by the owner's events (ADR 0615). func newViews() *views.Reader { - v := &views.Reader{Module: Module, Logf: logf, Findings: viewFindings{}} + v := &views.Reader{Module: Module, Logf: logf, Findings: views.OwnFindings{}, Acts: handacts.Recorder{Ask: stdio.Ask}} v.Follow(delivery.AssignerModule, "placement", PlacementTool) v.Follow(delivery.AssignerModule, "wants", WantsTool) v.Follow(delivery.IndexerModule, "modules", ModulesTool) diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go index 6f85415dc..4bccbcebe 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go @@ -9,8 +9,8 @@ import ( "sync" "testing" - "git.novox.be/novox/mesh-delivery/sdk/views" "git.novox.be/novox/mesh-sdk/go/health" + "git.novox.be/novox/mesh-sdk/go/views" ) // memStore is the bus as mesh-provisioner reaches it, in memory: the states it keeps. @@ -345,7 +345,7 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) { t.Errorf("the manifest does not read %s, which the holder reads", r) } } - if !slices.Equal(m.Invokes, []string{PlacementTool, WantsTool, ModulesTool, ManifestsTool}) || + if !slices.Equal(m.Invokes, []string{PlacementTool, WantsTool, ModulesTool, ManifestsTool, "seat:mesh-hand-acts.hand-act"}) || !slices.Equal(m.Consumes, []string{"mesh-delivery-assigner.views.placement", "mesh-delivery-assigner.views.wants", "mesh-delivery-indexer.views.modules", "mesh-delivery-indexer.views.manifests"}) { t.Errorf("invokes %v, consumes %v", m.Invokes, m.Consumes) diff --git a/modules/mesh-provisioner/module.json b/modules/mesh-provisioner/module.json index b947478ff..db0a66b75 100644 --- a/modules/mesh-provisioner/module.json +++ b/modules/mesh-provisioner/module.json @@ -30,7 +30,8 @@ "mesh-delivery-assigner.assigner_read_placement", "mesh-delivery-assigner.assigner_read_wants", "mesh-delivery-indexer.indexer_read_modules", - "mesh-delivery-indexer.indexer_read_manifests" + "mesh-delivery-indexer.indexer_read_manifests", + "seat:mesh-hand-acts.hand-act" ], "consumes": [ "mesh-delivery-assigner.views.placement", diff --git a/modules/mesh-self-check/cmd/mesh-self-check/main.go b/modules/mesh-self-check/cmd/mesh-self-check/main.go index 288bb79bb..e8617cf5b 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/main.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/main.go @@ -15,7 +15,8 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/handacts" + "git.novox.be/novox/mesh-sdk/go/views" ) func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) } @@ -73,7 +74,7 @@ func NewChecker() *Checker { // its owner's read tool, then followed by the owner's events. A first view unanswered past its bound is a finding of the // self-check's own (first-view-unanswered), written into its state findings. func newViews() *views.Reader { - r := &views.Reader{Module: Module, Logf: logf, Findings: findings{}} + r := &views.Reader{Module: Module, Logf: logf, Findings: views.OwnFindings{}, Acts: handacts.Recorder{Ask: stdio.Ask}} r.Follow(delivery.AssignerModule, delivery.PlacementState, PlacementTool) r.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool) r.Follow(delivery.IndexerModule, "modules", ModulesTool) @@ -81,15 +82,6 @@ func newViews() *views.Reader { return r } -// findings writes the self-check's own state findings for the reader. -type findings struct{} - -func (findings) Put(key string, value []byte) error { - _, err := stdio.State(FindingsState).Put(key, json.RawMessage(value)) - return err -} -func (findings) Delete(key string) error { return stdio.State(FindingsState).Delete(key) } - func main() { c := NewChecker() go func() { diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes.go b/modules/mesh-self-check/cmd/mesh-self-check/probes.go index 54d5a2382..71c5d5d5b 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes.go @@ -33,7 +33,7 @@ const ( // Invokes are the tools and seat verbs the probes ask, and Reads the states they read: the manifest's `invokes` and // `reads`, which its test holds to these. var ( - Invokes = []string{StreamsTool, ClosedTool, SignalsVerb, BannedVerb, PlacementTool, SeatHoldersTool, ModulesTool, ManifestsTool} + Invokes = []string{StreamsTool, ClosedTool, SignalsVerb, BannedVerb, PlacementTool, SeatHoldersTool, ModulesTool, ManifestsTool, HandActVerb} Reads = []string{PresenceRead, HealthRead, ReadingsRead, GoodRead} // Consumes are the views families the probes follow (ADR 0615): the indexer's and the assigner's states are asked // once, by their owners' read tools, and then followed by their events, never read. @@ -41,6 +41,9 @@ var ( "mesh-delivery-indexer.views.modules", "mesh-delivery-indexer.views.manifests"} ) +// HandActVerb records a re-read of a first view by hand (ADR 0615 §6). +const HandActVerb = "seat:mesh-hand-acts.hand-act" + // The owners' read tools of the states the probes follow, which the manifest invokes. const ( PlacementTool = "mesh-delivery-assigner.assigner_read_placement" diff --git a/modules/mesh-self-check/module.json b/modules/mesh-self-check/module.json index e40b4b628..d4ed50772 100644 --- a/modules/mesh-self-check/module.json +++ b/modules/mesh-self-check/module.json @@ -20,7 +20,8 @@ "mesh-delivery-assigner.assigner_read_placement", "mesh-delivery-assigner.assigner_read_seat_holders", "mesh-delivery-indexer.indexer_read_modules", - "mesh-delivery-indexer.indexer_read_manifests" + "mesh-delivery-indexer.indexer_read_manifests", + "seat:mesh-hand-acts.hand-act" ], "reads": [ "mesh-delivery-watchman.presence", diff --git a/modules/mesh-watcher/cmd/mesh-watcher/main.go b/modules/mesh-watcher/cmd/mesh-watcher/main.go index 89b8796f1..cc101b5cd 100644 --- a/modules/mesh-watcher/cmd/mesh-watcher/main.go +++ b/modules/mesh-watcher/cmd/mesh-watcher/main.go @@ -17,9 +17,10 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/handacts" "git.novox.be/novox/mesh-sdk/go/health" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/views" ) func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) } @@ -73,7 +74,7 @@ func main() { node := os.Getenv("MESH_NODE") w := NewWatcher(tg, time.Now, logf, node) l := &listening{now: "not yet: starting"} - placement := &views.Reader{Module: "mesh-watcher", Logf: logf} + placement := &views.Reader{Module: "mesh-watcher", Logf: logf, Findings: views.OwnFindings{}, Acts: handacts.Recorder{Ask: stdio.Ask}} placement.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool) go run(w, l, node, placement) if err := stdio.Serve("", append(tools(w, l), placement.Tools("watcher")...)); err != nil { diff --git a/modules/mesh-watcher/cmd/mesh-watcher/manifest_test.go b/modules/mesh-watcher/cmd/mesh-watcher/manifest_test.go index 4ef52ac0f..cc004b07a 100644 --- a/modules/mesh-watcher/cmd/mesh-watcher/manifest_test.go +++ b/modules/mesh-watcher/cmd/mesh-watcher/manifest_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/views" ) // The manifest says what the code does: it consumes the heartbeat and nothing else, calls its own @@ -41,7 +41,7 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) { if !reflect.DeepEqual(m.Consumes, []string{"mesh-self-check.checked", "mesh-delivery-assigner.views.seat-holders"}) { t.Fatalf("consumes %v", m.Consumes) } - if !reflect.DeepEqual(m.Invokes, []string{SeatHoldersTool, m.Module + ".watcher_ping"}) || len(m.Reads) != 0 { + if !reflect.DeepEqual(m.Invokes, []string{SeatHoldersTool, m.Module + ".watcher_ping", "seat:mesh-hand-acts.hand-act"}) || len(m.Reads) != 0 { t.Fatalf("invokes %v, reads %v", m.Invokes, m.Reads) } if len(m.Claims) != 0 { diff --git a/modules/mesh-watcher/module.json b/modules/mesh-watcher/module.json index 39cb6c2a3..e4555e822 100644 --- a/modules/mesh-watcher/module.json +++ b/modules/mesh-watcher/module.json @@ -8,7 +8,11 @@ ], "invokes": [ "mesh-delivery-assigner.assigner_read_seat_holders", - "mesh-watcher.watcher_ping" + "mesh-watcher.watcher_ping", + "seat:mesh-hand-acts.hand-act" + ], + "state": [ + "findings" ], "own-secrets": { "telegram-token": "${dir:state}/telegram-token" diff --git a/modules/minio/cmd/object-store/main.go b/modules/minio/cmd/object-store/main.go index 3ff388615..9ec5152ec 100644 --- a/modules/minio/cmd/object-store/main.go +++ b/modules/minio/cmd/object-store/main.go @@ -17,7 +17,8 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/handacts" + "git.novox.be/novox/mesh-sdk/go/views" ) // Words the manifest gives this bundle (modules/minio/module.json). @@ -28,7 +29,7 @@ const ( ) func main() { - seats := &views.Reader{Module: "minio", Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[object-store] "+f+"\n", a...) }} + seats := &views.Reader{Module: "minio", Findings: views.OwnFindings{}, Acts: handacts.Recorder{Ask: stdio.Ask}, Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, "[object-store] "+f+"\n", a...) }} seats.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool) s, err := newServer(os.Getenv(ConfigWord), os.Getenv(LinksWord), os.Getenv(ReceivesWord), func() (map[string][]byte, error) { return seats.Values(delivery.SeatHoldersRead) }) diff --git a/modules/minio/cmd/object-store/manifest_test.go b/modules/minio/cmd/object-store/manifest_test.go index acf2d470c..996713a5f 100644 --- a/modules/minio/cmd/object-store/manifest_test.go +++ b/modules/minio/cmd/object-store/manifest_test.go @@ -40,7 +40,7 @@ func TestManifestClaimsTheSeat(t *testing.T) { } invokes, _ := m["invokes"].([]any) consumes, _ := m["consumes"].([]any) - if _, reads := m["reads"]; reads || len(invokes) != 1 || invokes[0] != SeatHoldersTool || len(consumes) != 1 || consumes[0] != "mesh-delivery-assigner.views.seat-holders" { + if _, reads := m["reads"]; reads || len(invokes) != 2 || invokes[0] != SeatHoldersTool || invokes[1] != "seat:mesh-hand-acts.hand-act" || len(consumes) != 1 || consumes[0] != "mesh-delivery-assigner.views.seat-holders" { t.Fatalf("reads %v, invokes %v, consumes %v: it asks the assigner's seat-holders once and follows its events (ADR 0615)", m["reads"], invokes, consumes) } var goBundle map[string]any diff --git a/modules/minio/module.json b/modules/minio/module.json index b52fba286..0df4e3615 100644 --- a/modules/minio/module.json +++ b/modules/minio/module.json @@ -40,11 +40,15 @@ } ], "invokes": [ - "mesh-delivery-assigner.assigner_read_seat_holders" + "mesh-delivery-assigner.assigner_read_seat_holders", + "seat:mesh-hand-acts.hand-act" ], "consumes": [ "mesh-delivery-assigner.views.seat-holders" ], + "state": [ + "findings" + ], "contributes": { "route": { "api": { diff --git a/modules/nfs-server/cmd/nfs-server/main.go b/modules/nfs-server/cmd/nfs-server/main.go index 8c86cf908..7447fafd4 100644 --- a/modules/nfs-server/cmd/nfs-server/main.go +++ b/modules/nfs-server/cmd/nfs-server/main.go @@ -22,9 +22,10 @@ import ( stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/handacts" "git.novox.be/novox/mesh-sdk/go/health" - "git.novox.be/novox/mesh-delivery/sdk/views" + "git.novox.be/novox/mesh-sdk/go/views" ) // ExportsState is the state the exports are kept in on the bus; MountsState the `mounts` modules' state, @@ -126,7 +127,7 @@ const SeatHoldersTool = delivery.AssignerModule + ".assigner_read_seat_holders" // seats is the picture of the placement seat's state of who holds which seat: asked of the assigner once and followed by // its events, never read from its state (ADR 0615). -var seats = &views.Reader{Module: "nfs-server", Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, "nfs-server: "+f+"\n", a...) }} +var seats = &views.Reader{Module: "nfs-server", Findings: views.OwnFindings{}, Acts: handacts.Recorder{Ask: stdio.Ask}, Logf: func(f string, a ...any) { fmt.Fprintf(os.Stderr, "nfs-server: "+f+"\n", a...) }} // seatHolderKeys is the placement seat's state of who holds which seat, every key. func seatHolderKeys() (map[string][]byte, error) { return seats.Values(delivery.SeatHoldersRead) } diff --git a/modules/nfs-server/cmd/nfs-server/manifest_test.go b/modules/nfs-server/cmd/nfs-server/manifest_test.go index 4bd93e8e4..d9d745022 100644 --- a/modules/nfs-server/cmd/nfs-server/manifest_test.go +++ b/modules/nfs-server/cmd/nfs-server/manifest_test.go @@ -144,7 +144,7 @@ func TestTheManifestCarriesGrantsTheRosterTheWantsAndTheRemovalOrder(t *testing. if len(full.Reads) != 2 || full.Reads[0] != health.ReadingsRead || full.Reads[1] != MountsState { t.Errorf("reads: %v", full.Reads) } - if len(full.Invokes) != 1 || full.Invokes[0] != SeatHoldersTool || len(full.Consumes) != 1 || full.Consumes[0] != "mesh-delivery-assigner.views.seat-holders" { + if len(full.Invokes) != 2 || full.Invokes[0] != SeatHoldersTool || full.Invokes[1] != "seat:mesh-hand-acts.hand-act" || len(full.Consumes) != 1 || full.Consumes[0] != "mesh-delivery-assigner.views.seat-holders" { t.Errorf("invokes %v, consumes %v", full.Invokes, full.Consumes) } roster := full.Facts["machines"] diff --git a/modules/nfs-server/module.json b/modules/nfs-server/module.json index 4b66a6c92..8beca270d 100644 --- a/modules/nfs-server/module.json +++ b/modules/nfs-server/module.json @@ -45,7 +45,8 @@ "name": "exports", "ttl-seconds": 120, "per-machine": true - } + }, + "findings" ], "reads": [ "disk-load.readings", @@ -189,7 +190,8 @@ ] }, "invokes": [ - "mesh-delivery-assigner.assigner_read_seat_holders" + "mesh-delivery-assigner.assigner_read_seat_holders", + "seat:mesh-hand-acts.hand-act" ], "consumes": [ "mesh-delivery-assigner.views.seat-holders"