Merge pull request 'view_reach answers one module's delivery reach' (#136) from fix/view-reach-filter into main

This commit was merged in pull request #136.
This commit is contained in:
2026-11-02 09:33:51 +00:00
3 changed files with 74 additions and 5 deletions
@@ -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.<module>), with times: each value it " +
@@ -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.<module>'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)
@@ -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")
}