Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/catalog.go
T
2026-11-03 23:11:21 +01:00

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
}