From 8850a5673e61adec82790b60e777d50998b95f74 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 2 Nov 2026 10:24:54 +0100 Subject: [PATCH] view_reach takes a module, so one module's reach is asked without reading the whole repository Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL --- .../cmd/mesh-delivery-view-tools/main.go | 9 ++- .../cmd/mesh-delivery-view-tools/main_test.go | 13 +++++ .../mesh-delivery-view/internal/view/read.go | 57 ++++++++++++++++++- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go index 2af9092..adc6d5d 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main.go @@ -33,12 +33,15 @@ func Tools(r view.Reader, u view.UsageReader) []stdio.Tool { "containing, as the view derives it from the indexer's modules, the wants and the haves; a merge at or before it is on the " + "machines), what holds the next one (a module on a node, the indexer's lag), and per module its fingerprint, the nodes " + "that want it, its changes (fingerprint, the merge it first appeared at, its source revision), its floor and the nodes " + - "where it is not settled or not healthy (novox/hq ADR 0556). Give a repository (owner/name) for one, none for every one. Read-only.", + "where it is not settled or not healthy (novox/hq ADR 0556). Give a repository (owner/name) for one, none for every one; give a module to see only that module of each " + + "repository and the holds that name it. Answers the delivery reach of a repository and of a module. Read-only.", Input: map[string]any{"type": "object", "properties": map[string]any{ - "repository": map[string]any{"type": "string", "description": "the repository, owner/name; none for every repository"}}}, + "repository": map[string]any{"type": "string", "description": "the repository, owner/name; none for every repository"}, + "module": map[string]any{"type": "string", "description": "the module name; none for every module"}}}, Run: func(args map[string]any) (any, error) { repository, _ := args["repository"].(string) - return r.Reach(repository) + module, _ := args["module"].(string) + return r.Reach(repository, module) }}, {Name: view.ModuleHistoryTool, Description: "The history of one module's value in the indexer's modules state (modules.), with times: each value it " + diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go index 0e230ce..bca6fc8 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view-tools/main_test.go @@ -89,6 +89,19 @@ func TestModuleToolAnswersTheStoredValue(t *testing.T) { if _, err := run(t, r, view.ReachReadTool, map[string]any{"repository": "novox/gone"}); err == nil || !strings.Contains(err.Error(), "no reach of novox/gone") { t.Errorf("a repository with no reach: %v", err) } + // The module argument keeps only that module of each repository, and only the holds that name it. + const reach2 = `{"v":1,"repository":"novox/web","reached":"c1","reached_src":12,"modules":{"web":{"nodes":1},"api":{"nodes":2}},"holds":["web on g14 runs 1","api on g14 runs 2","the indexer has not yet taken in x"]}` + r.ReachState = fake{"novox/web": reach2, "novox/other": `{"v":1,"repository":"novox/other","modules":{"o":{"nodes":1}}}`}.reader().State + if out, err := run(t, r, view.ReachReadTool, map[string]any{"repository": "novox/web", "module": "web"}); err != nil || + !strings.Contains(string(out.(json.RawMessage)), `"web"`) || strings.Contains(string(out.(json.RawMessage)), `"api"`) || strings.Contains(string(out.(json.RawMessage)), "api on") || !strings.Contains(string(out.(json.RawMessage)), "web on g14") { + t.Errorf("view_reach novox/web module web = %s, %v", out, err) + } + if out, err := run(t, r, view.ReachReadTool, map[string]any{"module": "api"}); err != nil || !strings.Contains(fmt.Sprint(out), "api") || strings.Contains(fmt.Sprint(out), "novox/other") { + t.Errorf("view_reach module api over every repository = %v, %v", out, err) + } + if _, err := run(t, r, view.ReachReadTool, map[string]any{"repository": "novox/web", "module": "nope"}); err == nil || !strings.Contains(err.Error(), "no module nope") { + t.Errorf("a module the repository does not hold: %v", err) + } // Issue 1126 note 76: indexer_module_history answers modules.'s kept values newest first, each with its // revision, time, commit, source revision and fp2; indexer_heard the FORGE messages the indexer handled, newest first. at := time.Date(2026, 10, 28, 13, 7, 46, 0, time.UTC) diff --git a/modules/mesh-delivery-view/internal/view/read.go b/modules/mesh-delivery-view/internal/view/read.go index fc9ac7c..ca3956e 100644 --- a/modules/mesh-delivery-view/internal/view/read.go +++ b/modules/mesh-delivery-view/internal/view/read.go @@ -102,8 +102,61 @@ func (r Reader) Status(now time.Time) Status { // ReachReadTool reads the view's reach: per repository, how far each module reached and the reached head (ADR 0556). const ReachReadTool = "view_reach" -// Reach is the stored reach of one repository, or of every repository when none is named, as the view wrote it. -func (r Reader) Reach(repository string) (any, error) { +// Reach is the stored reach of one repository, or of every repository when none is named, as the view wrote it. With a +// module, each repository's value holds only that module and the holds that name it, and a repository without it is left out. +func (r Reader) Reach(repository, module string) (any, error) { + out, err := r.reach(repository) + if module = strings.TrimSpace(module); module == "" || err != nil { + return out, err + } + if raw, ok := out.(json.RawMessage); ok { + one, err := onlyModule(raw, module) + if err != nil { + return nil, err + } + if one == nil { + return nil, fmt.Errorf("no module %s in the reach of %s", module, repository) + } + return one, nil + } + var kept []json.RawMessage + for _, raw := range out.(map[string]any)["reach"].([]json.RawMessage) { + one, err := onlyModule(raw, module) + if err != nil { + return nil, err + } + if one != nil { + kept = append(kept, one) + } + } + return map[string]any{"reach": kept}, nil +} + +// onlyModule is a reach value with only module among its modules and only the holds that name it, nil when it holds none. +func onlyModule(raw json.RawMessage, module string) (json.RawMessage, error) { + if len(raw) == 0 { + return nil, nil + } + rc, err := delivery.DecodeReach(raw) + if err != nil { + return nil, err + } + mr, ok := rc.Modules[module] + if !ok { + return nil, nil + } + rc.Modules = map[string]delivery.ModuleReach{module: mr} + var holds []string + for _, h := range rc.Holds { + if strings.HasPrefix(h, module+" ") { + holds = append(holds, h) + } + } + rc.Holds = holds + return json.Marshal(rc) +} + +func (r Reader) reach(repository string) (any, error) { if r.ReachState.Get == nil || r.ReachState.Keys == nil { return nil, fmt.Errorf("the reach state cannot be read here") }