From c6de76740f5301439485aa0e7161b43227ce869e Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 01:12:43 +0100 Subject: [PATCH] Fix 9.4e: the four readers use the branch follower's Entry, Ready, entries rows, entry-not-found finding and entry_again tool; their copies are deleted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G A-103, A-15, §614 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../cmd/claude-code/catalogfeed.go | 42 ---------------- .../cmd/claude-code/catalogfeed_test.go | 2 +- .../cmd/claude-code/insteadof_test.go | 7 +-- modules/claude-code/cmd/claude-code/main.go | 2 +- modules/claude-code/cmd/claude-code/mesh.go | 5 +- .../claude-code/replace_registered_test.go | 5 +- .../cmd/mesh-config/catalogfeed.go | 48 +------------------ .../cmd/mesh-config/catalogfeed_test.go | 2 +- .../cmd/mesh-config/config_test.go | 11 +++-- .../mesh-config/cmd/mesh-config/delivery.go | 3 +- modules/mesh-config/cmd/mesh-config/main.go | 10 ++-- .../cmd/mesh-config/manifest_test.go | 3 ++ .../mesh-config/cmd/mesh-config/ownerviews.go | 11 ++++- modules/mesh-config/cmd/mesh-config/pulls.go | 3 +- .../mesh-config/cmd/mesh-config/pulls_test.go | 3 +- .../mesh-config/cmd/mesh-config/resolve.go | 3 +- .../mesh-config/cmd/mesh-config/sources.go | 7 +-- .../mesh-config/cmd/mesh-config/takeover.go | 3 +- .../cmd/mesh-config/takeover_test.go | 3 +- .../cmd/mesh-config/whole_integration_test.go | 15 +++--- modules/mesh-config/module.json | 3 +- .../cmd/mesh-provisioner/catalogfake_test.go | 2 +- .../cmd/mesh-provisioner/catalogfeed.go | 48 +------------------ .../cmd/mesh-provisioner/catalogfeed_test.go | 2 +- .../cmd/mesh-provisioner/composed_test.go | 9 ++-- .../cmd/mesh-provisioner/holder.go | 13 ++--- .../holder_integration_test.go | 5 +- .../cmd/mesh-provisioner/holder_test.go | 5 +- .../cmd/mesh-provisioner/main.go | 19 ++++++-- .../cmd/mesh-provisioner/provisioner_test.go | 3 ++ modules/mesh-provisioner/module.json | 3 +- .../cmd/mesh-self-check/catalogfeed.go | 20 ++------ 32 files changed, 109 insertions(+), 211 deletions(-) diff --git a/modules/claude-code/cmd/claude-code/catalogfeed.go b/modules/claude-code/cmd/claude-code/catalogfeed.go index 6191ad672..cd4114c5f 100644 --- a/modules/claude-code/cmd/claude-code/catalogfeed.go +++ b/modules/claude-code/cmd/claude-code/catalogfeed.go @@ -6,10 +6,6 @@ package main // `manifests`. import ( - "fmt" - "strings" - - "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" ) @@ -20,41 +16,3 @@ type CatalogHalf interface { // Ready is nil once every watched branch has its first view (none-yet counting as one), else says what it waits for. Ready() error } - -// branchesHalf is the CatalogHalf over the follower of the watched branches. -type branchesHalf struct{ b *meshcatalogclient.Branches } - -func (h branchesHalf) Ready() error { - followed := h.b.Followed() - if len(followed) == 0 { - return fmt.Errorf("the catalog half names no watched branch yet (the seat mesh-catalog's registrations are asked until they answer)") - } - var waiting []string - for _, branch := range followed { - if c, ok := h.b.Catalog(branch); ok { - select { - case <-c.Ready(): - default: - waiting = append(waiting, branch) - } - } - } - if len(waiting) > 0 { - return fmt.Errorf("the catalog half has no first view of %s yet", strings.Join(waiting, ", ")) - } - return nil -} - -func (h branchesHalf) Entry(module string) (catalog.CatalogEntry, bool, error) { - if err := h.Ready(); err != nil { - return catalog.CatalogEntry{}, false, err - } - for _, branch := range h.b.Followed() { // the branch followed first names a module more than one hold - if c, ok := h.b.Catalog(branch); ok { - if e, ok := c.Entry(module); ok { - return e, true, nil - } - } - } - return catalog.CatalogEntry{}, false, nil -} diff --git a/modules/claude-code/cmd/claude-code/catalogfeed_test.go b/modules/claude-code/cmd/claude-code/catalogfeed_test.go index 3b70104f3..6597f6693 100644 --- a/modules/claude-code/cmd/claude-code/catalogfeed_test.go +++ b/modules/claude-code/cmd/claude-code/catalogfeed_test.go @@ -58,7 +58,7 @@ func TestCatalogHalfIsTheWatchedBranchesNewestRevision(t *testing.T) { if err != nil { t.Fatal(err) } - h := branchesHalf{b} + h := b if _, _, err := h.Entry("pg"); err == nil || !strings.Contains(err.Error(), "no watched branch") { t.Fatalf("before the registrations: %v, want an error naming the missing branch", err) } diff --git a/modules/claude-code/cmd/claude-code/insteadof_test.go b/modules/claude-code/cmd/claude-code/insteadof_test.go index 94fa9a03d..eeec9e608 100644 --- a/modules/claude-code/cmd/claude-code/insteadof_test.go +++ b/modules/claude-code/cmd/claude-code/insteadof_test.go @@ -4,9 +4,6 @@ import ( "context" "encoding/json" "errors" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - mesh "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" - catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "os" "reflect" "slices" @@ -14,6 +11,10 @@ import ( "testing" "time" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + mesh "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-sdk/go/seatdef" "git.novox.be/novox/mesh-sdk/go/seatlist" "git.novox.be/novox/mesh-sdk/go/views" diff --git a/modules/claude-code/cmd/claude-code/main.go b/modules/claude-code/cmd/claude-code/main.go index ebc69e76d..36022c465 100644 --- a/modules/claude-code/cmd/claude-code/main.go +++ b/modules/claude-code/cmd/claude-code/main.go @@ -488,7 +488,7 @@ func main() { default: } } - meshCatalog = branchesHalf{branches} + meshCatalog = branches if launched { fsys = FilesFor(p) } diff --git a/modules/claude-code/cmd/claude-code/mesh.go b/modules/claude-code/cmd/claude-code/mesh.go index fed9b0664..e8cd63359 100644 --- a/modules/claude-code/cmd/claude-code/mesh.go +++ b/modules/claude-code/cmd/claude-code/mesh.go @@ -16,8 +16,6 @@ import ( "encoding/json" "errors" "fmt" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - mesh "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "net" "os" "path/filepath" @@ -26,6 +24,9 @@ import ( "sync" "time" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + mesh "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/seatdef" "git.novox.be/novox/mesh-sdk/go/seatlist" diff --git a/modules/claude-code/cmd/claude-code/replace_registered_test.go b/modules/claude-code/cmd/claude-code/replace_registered_test.go index d61fb1060..57f8784c4 100644 --- a/modules/claude-code/cmd/claude-code/replace_registered_test.go +++ b/modules/claude-code/cmd/claude-code/replace_registered_test.go @@ -2,11 +2,12 @@ package main import ( "encoding/json" - occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" - oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" "strings" "testing" "time" + + occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" + oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" ) // A section registered for the mesh before the controller's terminal took that over, or set at the terminal, is diff --git a/modules/mesh-config/cmd/mesh-config/catalogfeed.go b/modules/mesh-config/cmd/mesh-config/catalogfeed.go index 3a16563d5..86b2619ce 100644 --- a/modules/mesh-config/cmd/mesh-config/catalogfeed.go +++ b/modules/mesh-config/cmd/mesh-config/catalogfeed.go @@ -7,8 +7,6 @@ package main import ( "context" - "fmt" - "strings" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" @@ -20,49 +18,7 @@ type CatalogHalf interface { Entry(module string) (catalog.CatalogEntry, bool, error) // Ready is nil once every watched branch has its first view (none-yet counting as one), else says what it waits for. Ready() error - // ByFingerprint reads an entry by the fingerprint of a build: from a branch's half when one lists it, else asked of the + // Resolve reads an entry by the fingerprint of a build: from a branch's half when one lists it, else asked of the // seat's verb `entry` once and kept (asked, answered or not found). - ByFingerprint(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState) -} - -// branchesHalf is the CatalogHalf over the follower of the watched branches. -type branchesHalf struct{ b *meshcatalogclient.Branches } - -func (h branchesHalf) Ready() error { - followed := h.b.Followed() - if len(followed) == 0 { - return fmt.Errorf("the catalog half names no watched branch yet (the seat mesh-catalog's registrations are asked until they answer)") - } - var waiting []string - for _, branch := range followed { - if c, ok := h.b.Catalog(branch); ok { - select { - case <-c.Ready(): - default: - waiting = append(waiting, branch) - } - } - } - if len(waiting) > 0 { - return fmt.Errorf("the catalog half has no first view of %s yet", strings.Join(waiting, ", ")) - } - return nil -} - -func (h branchesHalf) Entry(module string) (catalog.CatalogEntry, bool, error) { - if err := h.Ready(); err != nil { - return catalog.CatalogEntry{}, false, err - } - for _, branch := range h.b.Followed() { // the branch followed first names a module more than one hold - if c, ok := h.b.Catalog(branch); ok { - if e, ok := c.Entry(module); ok { - return e, true, nil - } - } - } - return catalog.CatalogEntry{}, false, nil -} - -func (h branchesHalf) ByFingerprint(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState) { - return h.b.Resolve(ctx, fingerprint) + Resolve(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState) } diff --git a/modules/mesh-config/cmd/mesh-config/catalogfeed_test.go b/modules/mesh-config/cmd/mesh-config/catalogfeed_test.go index 3b70104f3..6597f6693 100644 --- a/modules/mesh-config/cmd/mesh-config/catalogfeed_test.go +++ b/modules/mesh-config/cmd/mesh-config/catalogfeed_test.go @@ -58,7 +58,7 @@ func TestCatalogHalfIsTheWatchedBranchesNewestRevision(t *testing.T) { if err != nil { t.Fatal(err) } - h := branchesHalf{b} + h := b if _, _, err := h.Entry("pg"); err == nil || !strings.Contains(err.Error(), "no watched branch") { t.Fatalf("before the registrations: %v, want an error naming the missing branch", err) } diff --git a/modules/mesh-config/cmd/mesh-config/config_test.go b/modules/mesh-config/cmd/mesh-config/config_test.go index 5d3d7247a..cc28d31ed 100644 --- a/modules/mesh-config/cmd/mesh-config/config_test.go +++ b/modules/mesh-config/cmd/mesh-config/config_test.go @@ -5,15 +5,16 @@ import ( "encoding/json" "errors" "fmt" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" - catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" - catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "sort" "strings" "sync" "testing" "time" + + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" ) // memStore is the bus as mesh-config reaches it, in memory: its states and the mirrors it reads, its history log, @@ -43,7 +44,7 @@ func (m *memStore) Entry(module string) (catalog.CatalogEntry, bool, error) { func (m *memStore) Ready() error { return m.notYet } -func (m *memStore) ByFingerprint(_ context.Context, fp string) (catalog.CatalogEntry, catalogclient.EntryState) { +func (m *memStore) Resolve(_ context.Context, fp string) (catalog.CatalogEntry, catalogclient.EntryState) { m.mu.Lock() defer m.mu.Unlock() if e, ok := m.byFp[fp]; ok { diff --git a/modules/mesh-config/cmd/mesh-config/delivery.go b/modules/mesh-config/cmd/mesh-config/delivery.go index 9aa2b9bc0..2ab1babe1 100644 --- a/modules/mesh-config/cmd/mesh-config/delivery.go +++ b/modules/mesh-config/cmd/mesh-config/delivery.go @@ -6,9 +6,10 @@ package main import ( "encoding/json" "fmt" - engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" "strings" + engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient" ) diff --git a/modules/mesh-config/cmd/mesh-config/main.go b/modules/mesh-config/cmd/mesh-config/main.go index 1900ba8cf..b3848f0ca 100644 --- a/modules/mesh-config/cmd/mesh-config/main.go +++ b/modules/mesh-config/cmd/mesh-config/main.go @@ -152,13 +152,15 @@ func main() { default: } } - branches.Entries.Changed = func(catalogclient.EntryRow) { + // An entry the catalog holds none of is a finding `entry-not-found`, and a new pass starts at each answer (design 47 G A-103). + branches.WriteEntryFindings(w, func(catalogclient.EntryRow) { select { case moved <- struct{}{}: default: } - } - c.Catalog = branchesHalf{branches} + }) + theViews.entries = func() any { return branches.EntryStatuses() } + c.Catalog = branches theViews.catalog = func() []*views.Follower { out := []*views.Follower{branches.Registrations.Follower} for _, b := range branches.Followed() { @@ -180,7 +182,7 @@ func main() { go resolving(c, moved) go hearing(c) go hearingDelivery(c) - if err := stdio.Serve("", Tools(c)); err != nil { + if err := stdio.Serve("", append(Tools(c), branches.EntryAgainTool("config", c.Store.HandAct))); err != nil { logf("%v", err) os.Exit(1) } diff --git a/modules/mesh-config/cmd/mesh-config/manifest_test.go b/modules/mesh-config/cmd/mesh-config/manifest_test.go index eca3d64fc..0780921c0 100644 --- a/modules/mesh-config/cmd/mesh-config/manifest_test.go +++ b/modules/mesh-config/cmd/mesh-config/manifest_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "os" "path/filepath" "slices" @@ -62,6 +63,8 @@ func TestEveryStateAndActHasItsTool(t *testing.T) { for _, tool := range Tools(&Config{}) { served[tool.Name] = true } + // the catalog half's follower serves the part's `entry_again` (design 47 G A-103), beside the module's own tools + served[(&catalogclient.Branches{}).EntryAgainTool("config", nil).Name] = true if len(m.Claims) != 1 || m.Claims[0].Name != Seat || strings.Join(m.Claims[0].Serves, ",") != strings.Join(SeatVerbs, ",") { t.Errorf("claims %+v, want %s serving %v", m.Claims, Seat, SeatVerbs) } diff --git a/modules/mesh-config/cmd/mesh-config/ownerviews.go b/modules/mesh-config/cmd/mesh-config/ownerviews.go index 3cbad6f99..8c201b241 100644 --- a/modules/mesh-config/cmd/mesh-config/ownerviews.go +++ b/modules/mesh-config/cmd/mesh-config/ownerviews.go @@ -16,10 +16,11 @@ import ( "encoding/json" "errors" "fmt" - catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "sort" "strings" + catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/views" ) @@ -53,6 +54,8 @@ type ownerViews struct { // catalog is the followers of the catalog half (the registrations and each watched branch's revision view), which the // first-view tools list and ask again beside the owners' (design 47 G §614); nil before they are followed. catalog func() []*views.Follower + // entries lists the entries by fingerprint the catalog half's follower keeps (design 47 G A-103). + entries func() any } // newOwnerViews makes a follower for each state; changed is told when any picture changes. findings is the module's writer, @@ -194,7 +197,11 @@ func viewTools(c *Config, o func() *ownerViews) []stdio.Tool { if ov == nil { return nil, errors.New("mesh-config reads its owners through views only when it runs as the module") } - return map[string]any{"first-views": ov.statuses()}, nil + out := map[string]any{"first-views": ov.statuses()} + if ov.entries != nil { + out["entries"] = ov.entries() + } + return out, nil }}, {Name: FirstViewAgainTool, Description: "Ask a first view once more by hand, `.` (all when not given), recorded as a hand act with " + diff --git a/modules/mesh-config/cmd/mesh-config/pulls.go b/modules/mesh-config/cmd/mesh-config/pulls.go index 0051d8781..6fda9171a 100644 --- a/modules/mesh-config/cmd/mesh-config/pulls.go +++ b/modules/mesh-config/cmd/mesh-config/pulls.go @@ -6,11 +6,12 @@ import ( "encoding/json" "errors" "fmt" - "git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient" "regexp" "strconv" "strings" "time" + + "git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient" ) // A config value held for the open pull request that introduces its key (novox/hq ADR 0502, issue 1826). diff --git a/modules/mesh-config/cmd/mesh-config/pulls_test.go b/modules/mesh-config/cmd/mesh-config/pulls_test.go index 45b16a5a4..23f0ea9b8 100644 --- a/modules/mesh-config/cmd/mesh-config/pulls_test.go +++ b/modules/mesh-config/cmd/mesh-config/pulls_test.go @@ -3,11 +3,12 @@ package main import ( "encoding/json" "errors" - "git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient" "strings" "testing" "time" + "git.novox.be/novox/mesh-contracts/go/clients/meshconfigclient" + occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" ) diff --git a/modules/mesh-config/cmd/mesh-config/resolve.go b/modules/mesh-config/cmd/mesh-config/resolve.go index eaf6277eb..93cd8191d 100644 --- a/modules/mesh-config/cmd/mesh-config/resolve.go +++ b/modules/mesh-config/cmd/mesh-config/resolve.go @@ -3,10 +3,11 @@ package main import ( "encoding/json" "fmt" - meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "sort" "strings" "time" + + meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" ) // The four sources (novox/hq ADR 0348 §2, §3, §9): a module's value on a node resolves, the most specific winning, diff --git a/modules/mesh-config/cmd/mesh-config/sources.go b/modules/mesh-config/cmd/mesh-config/sources.go index 1e1d7cf1c..32769638e 100644 --- a/modules/mesh-config/cmd/mesh-config/sources.go +++ b/modules/mesh-config/cmd/mesh-config/sources.go @@ -5,11 +5,12 @@ import ( "encoding/json" "errors" "fmt" + "sort" + "strings" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" - "sort" - "strings" ) // Reading the sources, writing the resolved values, and publishing the list of predefined keys. @@ -212,7 +213,7 @@ func (c *Config) trunkEntry(module string) (catalog.CatalogEntry, error) { // builtDeclarations is a module's declarations at a build: the manifest of the catalog entry of its fingerprint (design 47 // G §594, §614); an entry still asked, or the seat's word that it holds none, is an error naming it. func (c *Config) builtDeclarations(module, fp string) (ModuleDeclarations, error) { - e, state := c.Catalog.ByFingerprint(context.Background(), fp) + e, state := c.Catalog.Resolve(context.Background(), fp) switch state { case catalogclient.EntryAnswered: if e.Module != module { diff --git a/modules/mesh-config/cmd/mesh-config/takeover.go b/modules/mesh-config/cmd/mesh-config/takeover.go index fb9ab907e..0c30f80fe 100644 --- a/modules/mesh-config/cmd/mesh-config/takeover.go +++ b/modules/mesh-config/cmd/mesh-config/takeover.go @@ -13,8 +13,9 @@ import ( "encoding/json" "errors" "fmt" - catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "time" + + catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" ) // The states and reads. diff --git a/modules/mesh-config/cmd/mesh-config/takeover_test.go b/modules/mesh-config/cmd/mesh-config/takeover_test.go index 223488219..5b934337b 100644 --- a/modules/mesh-config/cmd/mesh-config/takeover_test.go +++ b/modules/mesh-config/cmd/mesh-config/takeover_test.go @@ -2,9 +2,10 @@ package main import ( "encoding/json" - catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "strings" "testing" + + catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" ) // The trunk's manifests aMesh declares. diff --git a/modules/mesh-config/cmd/mesh-config/whole_integration_test.go b/modules/mesh-config/cmd/mesh-config/whole_integration_test.go index 973a12abf..a40159190 100644 --- a/modules/mesh-config/cmd/mesh-config/whole_integration_test.go +++ b/modules/mesh-config/cmd/mesh-config/whole_integration_test.go @@ -18,13 +18,6 @@ import ( "encoding/json" "errors" "fmt" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" - meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" - nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" - catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" - catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" - delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "io" "maps" "os" @@ -36,6 +29,14 @@ import ( "testing" "time" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + delivery "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" diff --git a/modules/mesh-config/module.json b/modules/mesh-config/module.json index 5d6d09e6f..b57f98b56 100644 --- a/modules/mesh-config/module.json +++ b/modules/mesh-config/module.json @@ -59,7 +59,8 @@ "config_rename_close", "config_retiring", "config_first_views", - "config_first_view_again" + "config_first_view_again", + "config_entry_again" ], "state": [ "values", diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfake_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfake_test.go index 0904913a6..f4bf1b0ec 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfake_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfake_test.go @@ -69,7 +69,7 @@ func (f *fakeCatalog) Ready() error { return f.notYet } -func (f *fakeCatalog) ByFingerprint(_ context.Context, fp string) (catalog.CatalogEntry, catalogclient.EntryState) { +func (f *fakeCatalog) Resolve(_ context.Context, fp string) (catalog.CatalogEntry, catalogclient.EntryState) { f.mu.Lock() defer f.mu.Unlock() if e, ok := f.byFp[fp]; ok { diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed.go b/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed.go index e2014b736..513ed0be2 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed.go @@ -7,8 +7,6 @@ package main import ( "context" - "fmt" - "strings" "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" @@ -20,49 +18,7 @@ type CatalogHalf interface { Entry(module string) (catalog.CatalogEntry, bool, error) // Ready is nil once every watched branch has its first view (none-yet counting as one), else says what it waits for. Ready() error - // ByFingerprint reads an entry by the fingerprint of a build: from a branch's half when one lists it, else asked of the + // Resolve reads an entry by the fingerprint of a build: from a branch's half when one lists it, else asked of the // seat's verb `entry` once and kept (asked, answered or not found). - ByFingerprint(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState) -} - -// branchesHalf is the CatalogHalf over the follower of the watched branches. -type branchesHalf struct{ b *meshcatalogclient.Branches } - -func (h branchesHalf) Ready() error { - followed := h.b.Followed() - if len(followed) == 0 { - return fmt.Errorf("the catalog half names no watched branch yet (the seat mesh-catalog's registrations are asked until they answer)") - } - var waiting []string - for _, branch := range followed { - if c, ok := h.b.Catalog(branch); ok { - select { - case <-c.Ready(): - default: - waiting = append(waiting, branch) - } - } - } - if len(waiting) > 0 { - return fmt.Errorf("the catalog half has no first view of %s yet", strings.Join(waiting, ", ")) - } - return nil -} - -func (h branchesHalf) Entry(module string) (catalog.CatalogEntry, bool, error) { - if err := h.Ready(); err != nil { - return catalog.CatalogEntry{}, false, err - } - for _, branch := range h.b.Followed() { // the branch followed first names a module more than one hold - if c, ok := h.b.Catalog(branch); ok { - if e, ok := c.Entry(module); ok { - return e, true, nil - } - } - } - return catalog.CatalogEntry{}, false, nil -} - -func (h branchesHalf) ByFingerprint(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState) { - return h.b.Resolve(ctx, fingerprint) + Resolve(ctx context.Context, fingerprint string) (catalog.CatalogEntry, meshcatalogclient.EntryState) } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed_test.go index 3b70104f3..6597f6693 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/catalogfeed_test.go @@ -58,7 +58,7 @@ func TestCatalogHalfIsTheWatchedBranchesNewestRevision(t *testing.T) { if err != nil { t.Fatal(err) } - h := branchesHalf{b} + h := b if _, _, err := h.Entry("pg"); err == nil || !strings.Contains(err.Error(), "no watched branch") { t.Fatalf("before the registrations: %v, want an error naming the missing branch", err) } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/composed_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/composed_test.go index 2dd8e468e..88f10a6d5 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/composed_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/composed_test.go @@ -12,16 +12,17 @@ package main import ( "context" "encoding/json" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" - meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" - "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey" "os" "slices" "strings" "testing" "time" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey" + "git.novox.be/novox/mesh-sdk/go/catalogue" ) diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go index 454a74bdf..bb6b48e3d 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go @@ -31,17 +31,18 @@ import ( "encoding/json" "errors" "fmt" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" - meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" - catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" - "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey" "slices" "sort" "strings" "sync" "time" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey" + occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient" @@ -903,7 +904,7 @@ func (h *Holder) haveOutcome(p *meshprovisionerclient.Provision) { // read from the catalog entry of that fingerprint (design 47 G §594, §614); known is false while the entry is asked or when // the seat holds none (the caller then does not refuse). func (h *Holder) buildOffers(module, fingerprint, provision string) (offers, known bool) { - entry, state := h.Catalog.ByFingerprint(context.Background(), fingerprint) + entry, state := h.Catalog.Resolve(context.Background(), fingerprint) if state != catalogclient.EntryAnswered || entry.Module != module { return false, false } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go index d6c58f7a7..5c529a0eb 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_integration_test.go @@ -14,12 +14,13 @@ import ( "context" "encoding/json" "errors" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "strings" "testing" "time" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go index 9508a235b..dc18625ef 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go @@ -4,8 +4,6 @@ import ( "context" "encoding/json" "fmt" - assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" - meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" "go/ast" "go/parser" "go/token" @@ -15,6 +13,9 @@ import ( "testing" "time" + assignerclient "git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient" + meshseat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner" + occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient" diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/main.go b/modules/mesh-provisioner/cmd/mesh-provisioner/main.go index 39d520415..517ce4126 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/main.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/main.go @@ -10,13 +10,14 @@ import ( "context" "encoding/json" "fmt" - catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "os" "strings" "sync" "sync/atomic" "time" + catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + seat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" stdio "git.novox.be/novox/mesh-sdk/go" @@ -249,7 +250,7 @@ func main() { os.Exit(1) } branches.Logf = logf - cat := branchesHalf{branches} + cat := branches findings, err := newFindingsWriter() if err != nil { logf("%v", err) @@ -295,11 +296,12 @@ func main() { w.add(h.NodesOfModule(module)...) w.add(loads.heldBack(h)...) } - branches.Entries.Changed = func(row catalogclient.EntryRow) { + // An entry the catalog holds none of is a finding `entry-not-found` (design 47 G A-103). + branches.WriteEntryFindings(findings, func(row catalogclient.EntryRow) { if row.Entry != nil { w.add(h.NodesOfModule(row.Entry.Module)...) } - } + }) v.Start(context.Background()) go w.run(h) go starting(h) @@ -350,7 +352,8 @@ func main() { time.Sleep(wait) } }() - if err := stdio.Serve("", append(append(Tools(s), holderTools(h)...), v.Tools("provisioner")...)); err != nil { + if err := stdio.Serve("", append(append(append(Tools(s), holderTools(h)...), v.Tools("provisioner")...), + branches.EntryAgainTool("provisioner", handAct))); err != nil { logf("%v", err) os.Exit(1) } @@ -405,3 +408,9 @@ func Tools(s Store) []stdio.Tool { }, }} } + +// handAct records a hand act in the log of the seat mesh-hand-acts. +func handAct(what, why, cause string) error { + _, err := meshhandactsclient.Recorder{Ask: stdio.Ask}.Record(meshhandactsclient.Request{What: what, Why: why, Cause: cause}) + return err +} diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go index 34dca945f..71328c5a8 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/provisioner_test.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "os" "regexp" "slices" @@ -348,6 +349,8 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) { for _, tool := range append(append(Tools(newStore()), holderTools(&Holder{})...), (&views.Reader{}).Tools("provisioner")...) { served[tool.Name] = true } + // the catalog half's follower serves the part's `entry_again` (design 47 G A-103), beside the module's own tools + served[(&catalogclient.Branches{}).EntryAgainTool("provisioner", nil).Name] = true for what, tool := range map[string]string{Registry: Seat + "." + Verb, Needs: Seat + "." + Verb, Applied: Seat + "." + Verb, Collect: Seat + "." + Verb, ProvisionsState: ProvisionsTool, PlansState: PlansTool, "a composition on a change": ComposeTool, "a decision on an answer": DecideTool} { diff --git a/modules/mesh-provisioner/module.json b/modules/mesh-provisioner/module.json index 2c83c5e79..15028d356 100644 --- a/modules/mesh-provisioner/module.json +++ b/modules/mesh-provisioner/module.json @@ -49,7 +49,8 @@ "provisioner_decide", "provisioner_health", "provisioner_first_views", - "provisioner_first_view_again" + "provisioner_first_view_again", + "provisioner_entry_again" ], "state": [ "registry", diff --git a/modules/mesh-self-check/cmd/mesh-self-check/catalogfeed.go b/modules/mesh-self-check/cmd/mesh-self-check/catalogfeed.go index 43f4777cb..719845716 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/catalogfeed.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/catalogfeed.go @@ -5,9 +5,6 @@ package main // catalog graph entry carries whole (§443), never the indexer's state `modules` or `manifests`. import ( - "fmt" - "strings" - "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" ) @@ -22,23 +19,15 @@ type CatalogHalf interface { type branchesHalf struct{ b *meshcatalogclient.Branches } func (h branchesHalf) Modules() (map[string]catalog.CatalogEntry, error) { - followed := h.b.Followed() - if len(followed) == 0 { - return nil, fmt.Errorf("the catalog half names no watched branch yet (the seat mesh-catalog's registrations are asked until they answer)") + if err := h.b.Ready(); err != nil { + return nil, err } out := map[string]catalog.CatalogEntry{} - var waiting []string - for _, branch := range followed { + for _, branch := range h.b.Followed() { c, ok := h.b.Catalog(branch) if !ok { continue } - select { - case <-c.Ready(): - default: - waiting = append(waiting, branch) - continue - } half, _ := c.View() // a branch at none-yet is ready and empty (§614) for m, e := range half.Entries { if _, ok := out[m]; !ok { // the branch followed first names a module both hold @@ -46,8 +35,5 @@ func (h branchesHalf) Modules() (map[string]catalog.CatalogEntry, error) { } } } - if len(waiting) > 0 { - return nil, fmt.Errorf("the catalog half has no first view of %s yet", strings.Join(waiting, ", ")) - } return out, nil }