The engine's Composed keeps the catalogue's Plan and the declaration types and goes through nodeengineclient's codec; the fingerprint key part is meshcatalogclient's. The candidate answer is nodeengine.CandidateAnswer; the have still holds it through an interim JSON bridge (link.interimAnswers) until the Have group moves. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
85 lines
2.7 KiB
Go
85 lines
2.7 KiB
Go
package compositions
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
|
|
seat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
|
"git.novox.be/novox/mesh-sdk/go/declaration"
|
|
)
|
|
|
|
// PackagesBucket is the node-engine's state packages (the seat's state of the same name), one key per node.
|
|
const PackagesBucket = "mesh-host_" + seat.StatePackages
|
|
|
|
// DeclaredPackages is a node's key of `packages` from the compositions its engine composed for the node's wants:
|
|
// each resource of type package, by its name and whether it is declared absent, with the modules that declare it so,
|
|
// in the mirror's layout and order (names sorted, absent after present, modules sorted). A resource's module is the
|
|
// one it names, else its composition's, else the guess from its id (declaration.ModuleGuess); a package of no module
|
|
// lists none.
|
|
func DeclaredPackages(node string, comps []Composed) (seat.PackagesByNodeValue, error) {
|
|
by := map[packageKey]map[string]bool{}
|
|
for _, c := range comps {
|
|
for _, raw := range c.Resources {
|
|
var r struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Package string `json:"package"`
|
|
Absent bool `json:"absent"`
|
|
Module string `json:"module"`
|
|
}
|
|
if err := json.Unmarshal(raw, &r); err != nil {
|
|
return seat.PackagesByNodeValue{}, fmt.Errorf("compositions: a resource of %s's composition %q is not readable: %w", node, c.Module, err)
|
|
}
|
|
if r.Type != "package" || r.Package == "" {
|
|
continue
|
|
}
|
|
k := packageKey{r.Package, r.Absent}
|
|
if by[k] == nil {
|
|
by[k] = map[string]bool{}
|
|
}
|
|
module := r.Module
|
|
if module == "" {
|
|
module = c.Module
|
|
}
|
|
if module == "" {
|
|
module, _ = declaration.ModuleGuess(r.ID)
|
|
}
|
|
if module != "" {
|
|
by[k][module] = true
|
|
}
|
|
}
|
|
}
|
|
v := seat.PackagesByNodeValue{Node: node, Packages: []seat.PackagesByNodeValuePackagesItem{}}
|
|
for _, k := range sortedPackageKeys(by) {
|
|
modules := make([]string, 0, len(by[k]))
|
|
for m := range by[k] {
|
|
modules = append(modules, m)
|
|
}
|
|
sort.Strings(modules)
|
|
v.Packages = append(v.Packages, seat.PackagesByNodeValuePackagesItem{Package: k.name, Modules: modules, Absent: k.absent})
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// packageKey is a package as the layout tells it apart: its name, and whether it is declared absent.
|
|
type packageKey struct {
|
|
name string
|
|
absent bool
|
|
}
|
|
|
|
// sortedPackageKeys is the keys of m by name, absent after present.
|
|
func sortedPackageKeys[V any](m map[packageKey]V) []packageKey {
|
|
keys := make([]packageKey, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
if keys[i].name != keys[j].name {
|
|
return keys[i].name < keys[j].name
|
|
}
|
|
return !keys[i].absent && keys[j].absent
|
|
})
|
|
return keys
|
|
}
|