View reads the indexer's published packages with indexer_packages

A module's fingerprint takes the digests of the packages its last build
imported, so the published packages say why a merge moved a module or not;
the state needs a read tool like every other (ADR 0593 §2; D2).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 18:38:58 +01:00
parent 2e23b0b736
commit 61b2b718e6
5 changed files with 47 additions and 2 deletions
@@ -124,6 +124,24 @@ func Tools(r view.Reader, u view.UsageReader) []stdio.Tool {
key, _ := args["key"].(string)
return view.ControlRecord(r.ConfigCandidatesState, delivery.IndexerConfigCandidatesState, key)
}},
{Name: view.PackagesReadTool,
Description: "The mesh packages as the indexer publishes them (novox/hq ADR 0593 §2): per mesh Go module (its module " +
"path, such as git.novox.be/novox/mesh-sdk/go), the trunk merge that published it (the version a build pins), the " +
"digest of its go.mod and go.sum, and the content digest of each package in it. A module's fingerprint takes the " +
"digests of the packages its last build imported, so this says why a package merge moved a module or not. Give " +
"module for one. Read-only.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"module": map[string]any{"type": "string", "description": "one mesh Go module's path"}}},
Run: func(args map[string]any) (any, error) {
key := ""
if module, _ := args["module"].(string); strings.TrimSpace(module) != "" {
var err error
if key, err = delivery.PackagesKey(strings.TrimSpace(module)); err != nil {
return nil, err
}
}
return view.ControlRecord(r.PackagesState, delivery.PackagesState, key)
}},
{Name: view.CandidatesReadTool,
Description: "Every candidate the indexer holds, by group, each as stored under candidates.<group>; with module, only those with a " +
"pull request touching that module. Keys that cannot be read are listed as unreadable. Read-only.",
@@ -288,6 +306,7 @@ func main() {
PresenceState: read(stdio.State(view.StatusPresenceState)), StuckState: read(stdio.State(view.StatusStuckState)),
PredefinedState: read(stdio.State(delivery.IndexerModule + "." + delivery.IndexerPredefinedState)),
ConfigCandidatesState: read(stdio.State(delivery.IndexerModule + "." + delivery.IndexerConfigCandidatesState)),
PackagesState: read(stdio.State(delivery.IndexerModule + "." + delivery.PackagesState)),
BuildLog: stdio.Log(delivery.BuildLogRead).Since}
u := view.UsageReader{Haves: r.HavesState, Resolved: read(stdio.State("mesh-config.resolved")), Tries: read(stdio.State("mesh-delivery-checker.tries")),
Steps: map[string]view.StateRead{}, Pauses: map[string]view.StateRead{}, Backlog: backlogOf(socket())}
@@ -548,3 +548,23 @@ func TestAssignerDifferencesAndNodeHavesToolsAnswerAsStored(t *testing.T) {
t.Errorf("the values were read one at a time (at most %d in flight)", slow.maxFlight.Load())
}
}
// ADR 0593 §2 (D2): indexer_packages answers the indexer's published packages, one Go module by its path or all.
func TestPackagesToolAnswersThePublishedModules(t *testing.T) {
key, err := delivery.PackagesKey("git.novox.be/novox/mesh-sdk/go")
if err != nil {
t.Fatal(err)
}
const value = `{"v":1,"module":"git.novox.be/novox/mesh-sdk/go","commit":"c1"}`
r := view.Reader{PackagesState: fake{key: value}.reader().State}
out, err := run(t, r, view.PackagesReadTool, map[string]any{"module": "git.novox.be/novox/mesh-sdk/go"})
if err != nil || string(out.(json.RawMessage)) != value {
t.Fatalf("one module: %v %v", out, err)
}
if all, err := run(t, r, view.PackagesReadTool, nil); err != nil || all.(map[string]any)["holds"] != 1 {
t.Errorf("every module: %v %v", all, err)
}
if _, err := run(t, r, view.PackagesReadTool, map[string]any{"module": "github.com/x/y"}); err == nil {
t.Error("a path that is no mesh Go module's was read")
}
}
@@ -53,6 +53,8 @@ type Reader struct {
ConditionsState, AttemptsState, PresenceState, StuckState StateRead
// PredefinedState and ConfigCandidatesState are the indexer's control records (ADR 0565 §2, §15); unset reads none.
PredefinedState, ConfigCandidatesState StateRead
// PackagesState is the indexer's published packages (delivery.PackagesBucket, ADR 0593 §2); unset reads none.
PackagesState StateRead
// BuildLog reads the builder's log from a time (stdio.Log(delivery.BuildLogRead).Since); unset reads none.
BuildLog LogSince
}
@@ -64,6 +66,8 @@ type LogSince func(at time.Time, keys string, limit int) (*stdio.LogPage, error)
const (
PredefinedReadTool = "indexer_predefined"
ConfigCandidatesReadTool = "indexer_config_candidates"
// PackagesReadTool reads the indexer's published packages (ADR 0593 §2): what a fingerprint takes of a mesh package.
PackagesReadTool = "indexer_packages"
)
// ControlRecord is every key of one of the indexer's control records as stored, or one key, with how many it holds.
+2 -2
View File
@@ -87,7 +87,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7
if m.Module != "mesh-delivery-view" {
t.Errorf("module = %q", m.Module)
}
want := []string{"mesh-build-go.pauses", "mesh-build-go.steps", "mesh-build-image.pauses", "mesh-build-image.steps", "mesh-build-node.pauses", "mesh-build-node.steps", "mesh-conditions.conditions", "mesh-config.resolved", "mesh-delivery-assigner.asks", "mesh-delivery-assigner.differences", "mesh-delivery-assigner.placement", "mesh-delivery-assigner.wants", "mesh-delivery-builder.build-log", "mesh-delivery-builder.builds", "mesh-delivery-checker.tries", "mesh-delivery-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.config-candidates", "mesh-delivery-indexer.heard", "mesh-delivery-indexer.modules", "mesh-delivery-indexer.predefined", "mesh-delivery-indexer.taken", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "mesh-healers.attempts", "mesh-vault.signers", "node-engine.have"} // view_status reads the keeper's conditions and the healers' attempts (1952/V1); build_usage reads the build modules' steps and pauses, mesh-config's capacities and the checker's tries; 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9
want := []string{"mesh-build-go.pauses", "mesh-build-go.steps", "mesh-build-image.pauses", "mesh-build-image.steps", "mesh-build-node.pauses", "mesh-build-node.steps", "mesh-conditions.conditions", "mesh-config.resolved", "mesh-delivery-assigner.asks", "mesh-delivery-assigner.differences", "mesh-delivery-assigner.placement", "mesh-delivery-assigner.wants", "mesh-delivery-builder.build-log", "mesh-delivery-builder.builds", "mesh-delivery-checker.tries", "mesh-delivery-gate.asks", "mesh-delivery-gate.source", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.config-candidates", "mesh-delivery-indexer.heard", "mesh-delivery-indexer.modules", "mesh-delivery-indexer.packages", "mesh-delivery-indexer.predefined", "mesh-delivery-indexer.taken", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "mesh-healers.attempts", "mesh-vault.signers", "node-engine.have"} // view_status reads the keeper's conditions and the healers' attempts (1952/V1); build_usage reads the build modules' steps and pauses, mesh-config's capacities and the checker's tries; 1168/AC1, 1241/AC4, 1345/AC3, 1297/B8, B9
got := slices.Clone(m.Reads)
slices.Sort(got)
if !reflect.DeepEqual(got, want) {
@@ -119,7 +119,7 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7
}
// The two read tools of the indexer's modules (issue 1562), carried by a bundle the runtime launches; the indexer
// itself serves no tool (ADR 0437 §4). Carrying them gives the tool runner read access to every bucket the view reads.
if want := []string{"view_status", "indexer_module", "indexer_modules", "indexer_candidate", "indexer_candidates", "indexer_predefined", "indexer_config_candidates", "builder_build", "builder_builds", "builder_history", "assigner_wants", "assigner_want", "assigner_differences", "node_haves", "node_trusts", "build_usage", "view_reach", "indexer_module_history", "indexer_heard"}; !reflect.DeepEqual(m.Tools, want) {
if want := []string{"view_status", "indexer_module", "indexer_modules", "indexer_candidate", "indexer_candidates", "indexer_predefined", "indexer_config_candidates", "indexer_packages", "builder_build", "builder_builds", "builder_history", "assigner_wants", "assigner_want", "assigner_differences", "node_haves", "node_trusts", "build_usage", "view_reach", "indexer_module_history", "indexer_heard"}; !reflect.DeepEqual(m.Tools, want) {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
for _, a := range m.Build.Artifacts {
+2
View File
@@ -11,6 +11,7 @@
"indexer_candidates",
"indexer_predefined",
"indexer_config_candidates",
"indexer_packages",
"builder_build",
"builder_builds",
"builder_history",
@@ -55,6 +56,7 @@
"mesh-delivery-indexer.config-candidates",
"mesh-delivery-indexer.heard",
"mesh-delivery-indexer.modules",
"mesh-delivery-indexer.packages",
"mesh-delivery-indexer.predefined",
"mesh-delivery-indexer.taken",
"mesh-delivery-teller.answers",