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:
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user