Files
mesh-host/internal/agent/parts.go
T
jschoubben cf378f9a25 G §421, §683: the node-engine reads the entry its want names by fingerprint and keeps its entries and wants
The node's own composer reads the catalog half through meshcatalogclient's Entries cache over its own calls of
mesh-catalog's verb entry, and no longer watches or reads the indexer's modules, manifests and verdicts or the
builder's builds: a module's manifest is its entry's, each artifact's reference is made from the digest its want's
part names (artifact-store://<module>/<artifact>/blobs/<digest>, A-50). A want whose entry is asked or not found is
held, its have the phase held with a detail opening entry-asked: or entry-missing: naming the fingerprint. The
entries and wants it composes from are kept in composed-<node> (entry.<digits> and want.<module>), written before
the composition that uses them, read at start before anything is asked, a kept want standing until the wants'
live read answers, and dropped when nothing names them. The shadow keeps its reads (G §628).

The node grant gains the verb entry and the running stream's consumer calls; OverNATS hears the running stream
as the link's session does.

Builds: design 47 G §421, §683, §614, §628, A-47, A-50
2026-11-11 23:55:50 +01:00

90 lines
2.8 KiB
Go

package agent
import (
"encoding/json"
"fmt"
"sort"
"strings"
"git.novox.be/novox/mesh-sdk/go/declaration"
)
// PartsDiffer is the start of the detail of a have held because a want's parts and the digests its composition fetches
// disagree (design 47 G section 524).
const PartsDiffer = "parts-differ:"
// EntryAsked and EntryMissing open the detail of a have held for the entry the want names (design 47 G section 421 and
// section 614): the entry is asked of the catalog half and not yet answered, or the half answered that it holds none. Both
// are the phase held, and each names the fingerprint.
const (
EntryAsked = "entry-asked:"
EntryMissing = "entry-missing:"
)
// heldForEntry says whether why is a hold for the entry a want names.
func heldForEntry(why string) bool {
return strings.HasPrefix(why, EntryAsked) || strings.HasPrefix(why, EntryMissing)
}
// partsDifference checks a want's `parts` against the digests of the archive, process and container resources of the
// composition, which are the digests the apply fetches and checks (design 47 G section 524). A want with no `parts` (an
// assigner of before G, or a node whose record does not state `want-parts`) is applied as today: it returns "". With
// `parts`, the want is applied only when the digest of every resource fetched is among the values of `parts` and every
// value of `parts` is the digest of a resource fetched; otherwise it returns the detail naming each digest.
func partsDifference(parts map[string]string, resources []json.RawMessage) string {
if len(parts) == 0 {
return ""
}
fetched := map[string]bool{}
for _, raw := range resources {
r, err := declaration.ParseResource(raw)
if err != nil {
continue
}
switch res := r.(type) {
case *declaration.Archive:
if res.Source != "" {
fetched[res.Digest] = true
}
case *declaration.Process:
if res.Source != "" {
fetched[res.Digest] = true
}
case *declaration.Container:
if strings.HasPrefix(res.Image, "sha256:") {
fetched[res.Image] = true
} else if _, digest, ok := strings.Cut(res.Image, "@"); ok {
fetched[digest] = true
}
}
}
named := map[string]bool{}
for _, d := range parts {
named[d] = true
}
var outside, missing []string
for d := range fetched {
if !named[d] {
outside = append(outside, d)
}
}
for d := range named {
if !fetched[d] {
missing = append(missing, d)
}
}
if len(outside) == 0 && len(missing) == 0 {
return ""
}
sort.Strings(outside)
sort.Strings(missing)
var said []string
if len(outside) > 0 {
said = append(said, "fetched and not in the want's parts: "+strings.Join(outside, ", "))
}
if len(missing) > 0 {
said = append(said, "in the want's parts and not fetched: "+strings.Join(missing, ", "))
}
return fmt.Sprintf("%s %s", PartsDiffer, strings.Join(said, "; "))
}