CatalogTools moves into the index package so its row sits in the existing catalog test. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
175 lines
6.8 KiB
Go
175 lines
6.8 KiB
Go
package index
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
|
|
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
|
)
|
|
|
|
// The seat mesh-catalog's verbs, answered from the indexer's own states (novox/hq ADR 0604 §1, §2): modules, module,
|
|
// provides and dependents read the modules state, the manifests state and the module graph, and write nothing. The
|
|
// indexer serves no request subject (ADR 0437 §4): its tools bundle serves them as <seat>.<verb>. Each answer keeps the
|
|
// field names of the old catalog module's tool it replaces (catalog_modules, catalog_module, catalog_provides,
|
|
// catalog_dependents): the answers are the seat's contract (catalogue.CatalogModules and the rest), built here.
|
|
|
|
// CatalogRead reads the states the seat's verbs answer from.
|
|
type CatalogRead struct {
|
|
// Modules is every value of the modules state, by module.
|
|
Modules func() (map[string]delivery.Module, error)
|
|
// Manifest is the manifest kept under a digest in the manifests state, as its JSON.
|
|
Manifest func(digest string) (string, error)
|
|
// Graph is the module graph, by module.
|
|
Graph func() (graph.Graph, error)
|
|
}
|
|
|
|
func entryOf(name string, m delivery.Module) catalogue.CatalogEntry {
|
|
return catalogue.CatalogEntry{Module: name, Repository: m.Repository, Path: m.Path, Commit: m.Commit, FP: m.FP2, None: m.None2}
|
|
}
|
|
|
|
// CatalogModulesAnswer is the verb modules: every module the modules state holds, by name.
|
|
func (c CatalogRead) CatalogModulesAnswer() (catalogue.CatalogModules, error) {
|
|
mods, err := c.Modules()
|
|
if err != nil {
|
|
return catalogue.CatalogModules{}, fmt.Errorf("the indexer's modules cannot be read: %w", err)
|
|
}
|
|
out := []catalogue.CatalogEntry{}
|
|
for _, name := range slices.Sorted(maps.Keys(mods)) {
|
|
out = append(out, entryOf(name, mods[name]))
|
|
}
|
|
return catalogue.CatalogModules{Modules: out}, nil
|
|
}
|
|
|
|
// CatalogModuleAnswer is the verb module: one module whole, its manifest included.
|
|
func (c CatalogRead) CatalogModuleAnswer(name string) (catalogue.CatalogOne, error) {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return catalogue.CatalogOne{}, fmt.Errorf("name the module")
|
|
}
|
|
mods, err := c.Modules()
|
|
if err != nil {
|
|
return catalogue.CatalogOne{}, fmt.Errorf("the indexer's modules cannot be read: %w", err)
|
|
}
|
|
m, ok := mods[name]
|
|
if !ok {
|
|
return catalogue.CatalogOne{}, fmt.Errorf("the trunk holds no module %s", name)
|
|
}
|
|
one := catalogue.CatalogModule{CatalogEntry: entryOf(name, m)}
|
|
if m.Manifest == "" {
|
|
return catalogue.CatalogOne{}, fmt.Errorf("the indexer keeps no manifest of %s", name)
|
|
}
|
|
raw, err := c.Manifest(m.Manifest)
|
|
if err != nil {
|
|
return catalogue.CatalogOne{}, fmt.Errorf("the manifest %s of %s cannot be read: %w", m.Manifest, name, err)
|
|
}
|
|
one.Manifest = json.RawMessage(raw)
|
|
return catalogue.CatalogOne{Module: one}, nil
|
|
}
|
|
|
|
// CatalogProvidesAnswer is the verb provides: the modules that provide a provision, each with its scope (a module provides
|
|
// its own name, at node scope), and the modules that require it, from the module graph's provision edges.
|
|
func (c CatalogRead) CatalogProvidesAnswer(provision string) (catalogue.CatalogProvides, error) {
|
|
provision = strings.TrimSpace(provision)
|
|
if provision == "" {
|
|
return catalogue.CatalogProvides{}, fmt.Errorf("name the provision")
|
|
}
|
|
g, mods, err := c.graphAndModules()
|
|
if err != nil {
|
|
return catalogue.CatalogProvides{}, err
|
|
}
|
|
provides, requires := []catalogue.CatalogEnd{}, []catalogue.CatalogEnd{}
|
|
for _, name := range g.Providers(provision) {
|
|
end := catalogue.CatalogEnd{Module: name, Commit: mods[name].Commit, Scope: catalogue.ScopeNode}
|
|
if name != provision {
|
|
end.Scope = c.scopeOf(mods[name], provision)
|
|
}
|
|
provides = append(provides, end)
|
|
}
|
|
for _, name := range g.Requirers(provision) {
|
|
requires = append(requires, catalogue.CatalogEnd{Module: name, Commit: mods[name].Commit})
|
|
}
|
|
return catalogue.CatalogProvides{Provision: provision, Provides: provides, Requires: requires}, nil
|
|
}
|
|
|
|
// scopeOf is the scope the module's manifest offers provision at, "" when its manifest cannot be read.
|
|
func (c CatalogRead) scopeOf(m delivery.Module, provision string) string {
|
|
if m.Manifest == "" {
|
|
return ""
|
|
}
|
|
raw, err := c.Manifest(m.Manifest)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
man, err := catalogue.Decode([]byte(raw))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, o := range man.Provides {
|
|
if o.Name == provision {
|
|
return o.At()
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// CatalogDependentsAnswer is the verb dependents: the modules whose build edges reach the module, directly or through
|
|
// others, from the module graph.
|
|
func (c CatalogRead) CatalogDependentsAnswer(module string) (catalogue.CatalogDependents, error) {
|
|
module = strings.TrimSpace(module)
|
|
if module == "" {
|
|
return catalogue.CatalogDependents{}, fmt.Errorf("name the module")
|
|
}
|
|
g, mods, err := c.graphAndModules()
|
|
if err != nil {
|
|
return catalogue.CatalogDependents{}, err
|
|
}
|
|
out := []catalogue.CatalogEnd{}
|
|
for _, name := range g.Dependents(module) {
|
|
out = append(out, catalogue.CatalogEnd{Module: name, Commit: mods[name].Commit})
|
|
}
|
|
return catalogue.CatalogDependents{Module: module, Dependents: out}, nil
|
|
}
|
|
|
|
func (c CatalogRead) graphAndModules() (graph.Graph, map[string]delivery.Module, error) {
|
|
g, err := c.Graph()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("the module graph cannot be read: %w", err)
|
|
}
|
|
mods, err := c.Modules()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("the indexer's modules cannot be read: %w", err)
|
|
}
|
|
return g, mods, nil
|
|
}
|
|
|
|
// CatalogTools are the seat mesh-catalog's four verbs as tools named <seat>.<verb>, with the descriptions and inputs the
|
|
// seat's contract gives them (novox/hq ADR 0604 §1, §2), answered from the indexer's own states. They are served once the
|
|
// indexer's manifest claims the seat, in the release after L3's (the lead's decision): until then main serves them not.
|
|
func CatalogTools(c CatalogRead) []stdio.Tool {
|
|
seat, ok := catalogue.SeatNamed(catalogue.CatalogSeat)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
str := func(args map[string]any, k string) string { s, _ := args[k].(string); return s }
|
|
runs := map[string]func(map[string]any) (any, error){
|
|
"modules": func(map[string]any) (any, error) { return c.CatalogModulesAnswer() },
|
|
"module": func(a map[string]any) (any, error) { return c.CatalogModuleAnswer(str(a, "module")) },
|
|
"provides": func(a map[string]any) (any, error) { return c.CatalogProvidesAnswer(str(a, "provision")) },
|
|
"dependents": func(a map[string]any) (any, error) { return c.CatalogDependentsAnswer(str(a, "module")) },
|
|
}
|
|
var out []stdio.Tool
|
|
for _, v := range seat.Serves {
|
|
if run, ok := runs[v.Name]; ok {
|
|
out = append(out, stdio.Tool{Name: catalogue.CatalogSeat + "." + v.Name, Description: v.Description, Input: v.Input, Run: run})
|
|
}
|
|
}
|
|
return out
|
|
}
|