DLV-I8: indexer_package_users lists each package's users and the series each accepts

So a user left on an older series is seen (design 32): per package its series with each one's newest published
version, and its trunk users with the series they accept, that series' newest and the version they are pinned to.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 13:51:43 +01:00
parent eedd3271ce
commit 51829a6b14
6 changed files with 249 additions and 4 deletions
@@ -194,7 +194,7 @@ func heldCounts(s stdio.KeptState) func() (index.OldSectionsCount, error) {
}
func main() {
if err := stdio.Serve("", append(Tools(askTakeIn(stdio.State(index.TakeInsState), 25*time.Second), time.Now, stdio.State(index.VerdictsState), heldCounts(stdio.State(index.OldSectionsState))), append(append(catalogTools(), viewTools(os.Getenv, keptSource, time.Now)...), configReadersTool(stdio.NewWatchedStates()), busStateTool(keptSource), requirementsTool(keptSource))...)); err != nil {
if err := stdio.Serve("", append(Tools(askTakeIn(stdio.State(index.TakeInsState), 25*time.Second), time.Now, stdio.State(index.VerdictsState), heldCounts(stdio.State(index.OldSectionsState))), append(append(catalogTools(), viewTools(os.Getenv, keptSource, time.Now)...), configReadersTool(stdio.NewWatchedStates()), busStateTool(keptSource), requirementsTool(keptSource), packageUsersTool(keptSource))...)); err != nil {
fmt.Fprintf(os.Stderr, "[mesh-delivery-indexer-tools] %v\n", err)
os.Exit(1)
}
@@ -125,3 +125,23 @@ func requirementsTool(state func(string) views.Source) stdio.Tool {
return map[string]any{"package": pkg, "requirements": versions}, nil
}}
}
// packageUsersTool is indexer_package_users (design 32 *A package, its series, and the series its users accept*; DLV-I8):
// per package, its users and the series each accepts, read from the indexer's own states.
func packageUsersTool(state func(string) views.Source) stdio.Tool {
return stdio.Tool{Name: index.PackageUsersTool,
Description: "Who uses each mesh package and which package series each accepts (design 32): per package its series with " +
"the newest published version of each, and its trunk users, each with the series it accepts (named in its manifest's " +
"packages, or the default), that series' newest published version and the version its fingerprint is pinned to, so a " +
"user left on an older series is seen. Give package (such as mesh-sdk/go) for one.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"package": map[string]any{"type": "string", "description": "one package by the builder's name, <module>/<artifact>; all when absent"}}},
Run: func(args map[string]any) (any, error) {
only, _ := args["package"].(string)
got, err := index.PackageUsersOf(state("modules"), state("manifests"), state(delivery.PackagesState), only)
if err != nil {
return nil, err
}
return map[string]any{"packages": got}, nil
}}
}
@@ -3,6 +3,7 @@ package index
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
@@ -138,6 +139,65 @@ func TestTheControlRecords(t *testing.T) {
t.Errorf("the per-module digests read a series key: %v", got)
}
})
// DLV-I8 (design 32 *A package, its series, and the series its users accept*): per package its series with the newest
// published of each, and its trunk users with the series each accepts: one naming 0.2 in packages, one reaching the Go
// module by go.sum on the default series, one by an import; a sibling of the package's own repository is no user, and a
// package only a manifest names is listed with no series.
t.Run("indexer_package_users", func(t *testing.T) {
sdkGo := "git.novox.be/novox/mesh-sdk/go"
modules, manifests, packages := memSource{}, memSource{}, memSource{}
putJSON := func(s memSource, key string, v any) {
raw, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
s[key] = &views.Entry{Value: raw, Revision: 1}
}
putJSON(packages, SeriesKey("mesh-sdk/go", "0.1"), SeriesPackage{V: 1, Package: "mesh-sdk/go", Series: "0.1", GoModule: sdkGo,
Newest: &SeriesVersion{Version: "0.1.15"}})
putJSON(packages, SeriesKey("mesh-sdk/go", "0.2"), SeriesPackage{V: 1, Package: "mesh-sdk/go", Series: "0.2", GoModule: sdkGo})
for name, u := range map[string]struct {
repo, manifest string
pkgs, imports []string
pinned string
}{
"new": {"novox/mesh-catalog", `{"module":"new","version":"1","packages":{"mesh-sdk/go":"0.2","web/ui":"1.0"}}`, nil, nil, ""},
"old": {"novox/mesh-catalog", `{"module":"old","version":"1"}`, []string{sdkGo}, nil, "v0.1.15"},
"imports": {"novox/mesh-tools", `{"module":"imports","version":"1"}`, nil, []string{sdkGo + "/views"}, ""},
"sibling": {"novox/mesh-sdk", `{"module":"sibling","version":"1"}`, []string{sdkGo}, nil, ""},
"none": {"novox/mesh-catalog", `{"module":"none","version":"1"}`, nil, nil, ""},
} {
digest := fmt.Sprintf("%064x", len(name)*16+int(name[0]))
putJSON(manifests, digest, delivery.Manifest{V: delivery.Version, Module: name, JSON: u.manifest})
mod := delivery.Module{FP2: "src2:" + strings.Repeat("a", 64), Manifest: digest, Repository: u.repo, Commit: "c",
Inputs2: &delivery.KeptInputs2{Packages: u.pkgs, Imports: u.imports}}
if u.pinned != "" {
mod.Packages = delivery.PackageVersions{sdkGo: u.pinned}
}
raw, err := delivery.EncodeModule(mod)
if err != nil {
t.Fatal(err)
}
modules[name] = &views.Entry{Value: raw, Revision: 1}
}
got, err := PackageUsersOf(modules, manifests, packages, "")
want := []PackageUses{
{Package: "mesh-sdk/go", GoModule: sdkGo, Series: map[string]string{"0.1": "0.1.15", "0.2": ""}, Users: []PackageUser{
{Module: "imports", Accepts: "0.1", Newest: "0.1.15"},
{Module: "new", Accepts: "0.2", Named: true},
{Module: "old", Accepts: "0.1", Newest: "0.1.15", Pinned: "v0.1.15"}}},
{Package: "web/ui", Series: map[string]string{}, Users: []PackageUser{{Module: "new", Accepts: "1.0", Named: true}}},
}
if err != nil || !reflect.DeepEqual(got, want) {
t.Errorf("indexer_package_users = %+v (%v), want %+v", got, err, want)
}
if one, err := PackageUsersOf(modules, manifests, packages, "web/ui"); err != nil || len(one) != 1 || one[0].Package != "web/ui" {
t.Errorf("one package = %+v (%v)", one, err)
}
if _, err := PackageUsersOf(modules, manifests, packages, "nobody/x"); err == nil {
t.Error("a package nothing knows was answered")
}
})
ctx := context.Background()
at := time.Date(2026, 10, 29, 22, 0, 0, 0, time.UTC)
t.Run("the predefined list, never replaced by an older merge", func(t *testing.T) {
@@ -0,0 +1,164 @@
package index
import (
"encoding/json"
"fmt"
"maps"
"slices"
"strings"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/views"
)
// PackageUsersTool lists, per mesh package, its users and the series each accepts (DLV-I8; ADR 0606 §4; design 32 *A
// package, its series, and the series its users accept*), so a user left on an older series is seen.
const PackageUsersTool = "indexer_package_users"
// PackageUser is one trunk module using a package: the series it accepts (Named when its manifest's `packages` names it,
// else the default), that series' newest published version ("" while none is), and the version its fingerprint pins.
type PackageUser struct {
Module string `json:"module"`
Accepts string `json:"accepts"`
Named bool `json:"named"`
Newest string `json:"newest"`
Pinned string `json:"pinned,omitempty"`
}
// PackageUses is one package of indexer_package_users: its series held in the packages state, the newest each published
// (by series), its Go module path for a Go package, and its users in module order.
type PackageUses struct {
Package string `json:"package"`
GoModule string `json:"go-module,omitempty"`
Series map[string]string `json:"series"`
Users []PackageUser `json:"users"`
}
// PackageUsersOf answers indexer_package_users from the indexer's own states (modules, manifests, packages), for one
// package or, with only empty, every package. A package is known by a series key of the packages state or by a name some
// trunk manifest's `packages` names. A trunk module uses it when its manifest names it, or, for a Go package, when its
// go.sum or its build's imports reach the package's Go module from another repository (a sibling by path is no user).
// A value that cannot be read is an error, never a user silently left out.
func PackageUsersOf(modules, manifests, packages views.Source, only string) ([]PackageUses, error) {
known := map[string]*PackageUses{}
get := func(name string) *PackageUses {
if known[name] == nil {
known[name] = &PackageUses{Package: name, Series: map[string]string{}, Users: []PackageUser{}}
}
return known[name]
}
keys, err := packages.Keys()
if err != nil {
return nil, fmt.Errorf("the packages state cannot be listed: %w", err)
}
for _, key := range keys {
if !IsSeriesKey(key) {
continue
}
e, ok, err := packages.Get(key)
if err != nil {
return nil, fmt.Errorf("packages.%s cannot be read: %w", key, err)
}
if !ok {
continue
}
var s SeriesPackage
if err := json.Unmarshal(e.Value, &s); err != nil {
return nil, fmt.Errorf("packages.%s holds what is not a series: %w", key, err)
}
p := get(s.Package)
if s.GoModule != "" {
p.GoModule = s.GoModule
}
p.Series[s.Series] = ""
if s.Newest != nil {
p.Series[s.Series] = s.Newest.Version
}
}
type trunk struct {
mod delivery.Module
manifest catalogue.Manifest
}
var mods []string
held := map[string]trunk{}
names, err := modules.Keys()
if err != nil {
return nil, fmt.Errorf("the modules state cannot be listed: %w", err)
}
for _, name := range names {
e, ok, err := modules.Get(name)
if err != nil {
return nil, fmt.Errorf("modules.%s cannot be read: %w", name, err)
}
if !ok {
continue
}
mod, err := delivery.DecodeModule(e.Value)
if err != nil || mod.Manifest == "" {
continue // no manifest held for it
}
me, ok, err := manifests.Get(mod.Manifest)
if err != nil {
return nil, fmt.Errorf("the manifest of %s cannot be read: %w", name, err)
}
if !ok {
continue
}
kept, err := delivery.DecodeManifest(me.Value)
if err != nil {
return nil, fmt.Errorf("the manifest of %s: %w", name, err)
}
m, err := catalogue.Decode([]byte(kept.JSON))
if err != nil {
return nil, fmt.Errorf("the manifest of %s does not read: %w", name, err)
}
for pkg := range m.Packages {
get(pkg)
}
held[name], mods = trunk{mod, m}, append(mods, name)
}
slices.Sort(mods)
reaches := func(mod delivery.Module, goModule string) bool {
if goModule == "" || mod.Inputs2 == nil {
return false
}
if repo, _, ok := delivery.PackageRepository(goModule); !ok || delivery.SameRepository(repo, mod.Repository) {
return false
}
for _, p := range append(slices.Clone(mod.Inputs2.Packages), mod.Inputs2.Imports...) {
if p == goModule || strings.HasPrefix(p, goModule+"/") {
return true
}
}
return false
}
var out []PackageUses
for _, name := range slices.Sorted(maps.Keys(known)) {
if only != "" && name != only {
continue
}
p := known[name]
for _, m := range mods {
t := held[m]
_, named := t.manifest.Packages[name]
if !named && !reaches(t.mod, p.GoModule) {
continue
}
accepts := t.manifest.AcceptedSeries(name)
u := PackageUser{Module: m, Accepts: accepts, Named: named, Newest: p.Series[accepts]}
if p.GoModule != "" {
u.Pinned = t.mod.Packages[p.GoModule]
}
p.Users = append(p.Users, u)
}
out = append(out, *p)
}
if only != "" && len(out) == 0 {
return nil, fmt.Errorf("no series of %s is held and no trunk manifest names it", only)
}
if out == nil {
out = []PackageUses{}
}
return out, nil
}
@@ -152,8 +152,8 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
t.Errorf("%s answers the state %s, which the views family does not say", v.Tool, v.State)
}
}
want = append(want, "indexer_first_views", "indexer_first_view_again", index.ConfigReadersTool, index.BusStateTool, index.RequirementsTool) // DLV-F3r, DLV-I10, DLV-I11, DLV-I7
if !reflect.DeepEqual(m.Tools, want) || len(want) != 22 {
want = append(want, "indexer_first_views", "indexer_first_view_again", index.ConfigReadersTool, index.BusStateTool, index.RequirementsTool, index.PackageUsersTool) // DLV-F3r, DLV-I10, DLV-I11, DLV-I7, DLV-I8
if !reflect.DeepEqual(m.Tools, want) || len(want) != 23 {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
if m.RunsAs != m.Module || m.SecretsOwner != m.RunsAs {
+2 -1
View File
@@ -37,7 +37,8 @@
"indexer_first_view_again",
"indexer_config_readers",
"indexer_bus_state",
"indexer_requirements"
"indexer_requirements",
"indexer_package_users"
],
"consumes": [
"go-package-registry.published",