The builder's forms gain seat-reference for bundles and archives (oci-artifact-registry/<module>/<artifact>@<digest>); the old-form window writes the older artifact-store reference beside it (catalogue.InArtifactStore), the oldest kept in the single reference (ADR 0624 §1, §3), so mesh-build-go's seat-form answer no longer strands an engine stating nothing. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
267 lines
10 KiB
Go
267 lines
10 KiB
Go
// Package forms holds the artifact reference forms the builder writes a done build key in and the assigner holds a want
|
|
// by (novox/hq ADR 0618 §1-§2, ADR 0624): logic only those two modules of mesh-delivery share, so it is mesh-delivery's
|
|
// and not mesh-sdk's (ADR 0593 §1, ADR 0608 §5). The build key's fields (delivery.BuilderBuild.Needs,
|
|
// delivery.BuiltArtifact.References) are the contract, in mesh-sdk.
|
|
package forms
|
|
|
|
import (
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// artifactStore opens the artifact store's references (delivery.BuiltArtifact).
|
|
const artifactStore = "artifact-store://"
|
|
|
|
// The artifact reference forms (novox/hq ADR 0605 §9, ADR 0618 §1-§2, ADR 0624): each form a done key's reference may be
|
|
// written in, named as the reader capability `form:<name>` a node-engine states when it reads it. The forms written
|
|
// before any engine stated what it reads are first forms: every engine reads them, so none needs a statement, and a
|
|
// node whose engine states nothing (the baseline, ADR 0614 §4) reads exactly those. A new form is added to
|
|
// Current, newest last, in the change that teaches the node-engine to read it.
|
|
|
|
// The first forms, by name.
|
|
const (
|
|
// FormArtifactStore: artifact-store://<module>/<artifact>/blobs/<digest>, or an image's kept artifact-store://…@<digest>.
|
|
FormArtifactStore = "artifact-store"
|
|
// FormRegistry: an image's or an upstream copy's <registry>/<module>/<artifact>@<digest>.
|
|
FormRegistry = "registry"
|
|
// FormPackageVersion: a package's <name>@<version>.
|
|
FormPackageVersion = "package-version"
|
|
)
|
|
|
|
// FormSeatReference is the form a registry seat answers for a bundle or an archive it published,
|
|
// oci-artifact-registry/<module>/<artifact>@sha256:<digest> (novox/hq ADR 0605 §9; catalogue.SeatReference, read back by
|
|
// catalogue.InRegistrySeat), read by a node-engine that states form:seat-reference (catalogue.ReferenceFormSeat). Not a
|
|
// first form: an engine stating nothing reads its blob only in the older artifact-store form, which it derives (Older).
|
|
const FormSeatReference = catalogue.ReferenceFormSeat
|
|
|
|
// ArtifactForm is one reference form: its name, whether it is a first form, the artifact kinds it names, how a
|
|
// reference is told to be in it, and, for a form that names the same artifact an older form does, that older reference
|
|
// (Older, nil when it derives none): the old-form window writes it beside the answered one (ADR 0624 §1, §3).
|
|
type ArtifactForm struct {
|
|
Name string
|
|
First bool
|
|
Kinds []string
|
|
Is func(reference string) bool
|
|
Older func(reference string) string
|
|
}
|
|
|
|
// ArtifactForms are the forms a builder knows, oldest first.
|
|
type ArtifactForms []ArtifactForm
|
|
|
|
// Current are the forms the builder writes and the assigner knows: the three first forms (delivery.BuiltArtifact's
|
|
// reference by kind), then the registry seat's form of a bundle or an archive.
|
|
var Current = ArtifactForms{
|
|
{Name: FormArtifactStore, First: true, Is: func(r string) bool { return strings.HasPrefix(r, artifactStore) },
|
|
Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive, catalogue.ArtifactImage, catalogue.ArtifactUpstream}},
|
|
{Name: FormRegistry, First: true, Is: func(r string) bool { return !strings.HasPrefix(r, artifactStore) && strings.Contains(r, "@sha256:") },
|
|
Kinds: []string{catalogue.ArtifactImage, catalogue.ArtifactUpstream}},
|
|
{Name: FormPackageVersion, First: true, Is: func(r string) bool { return strings.LastIndex(r, "@") > 0 && !strings.Contains(r, "@sha256:") },
|
|
Kinds: []string{catalogue.ArtifactPackage}},
|
|
{Name: FormSeatReference, Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive},
|
|
Is: func(r string) bool {
|
|
seat, _, ok := catalogue.InRegistrySeat(r)
|
|
return ok && seat == catalogue.OciArtifactRegistrySeat
|
|
},
|
|
// The same blob in the artifact store's form: oci-artifact-registry/<m>/<a>@<d> is artifact-store://<m>/<a>/blobs/<d>.
|
|
Older: func(r string) string {
|
|
path, _ := catalogue.InArtifactStore(r)
|
|
return catalogue.ArtifactStoreScheme + path
|
|
}},
|
|
}
|
|
|
|
// FormNeed is the reader capability a form needs of a node-engine: `form:<name>`.
|
|
func FormNeed(form string) string { return catalogue.ReadForm + form }
|
|
|
|
// FormOf is the name of the form reference is written in for an artifact of kind, and whether one of fs names it.
|
|
func (fs ArtifactForms) FormOf(kind, reference string) (string, bool) {
|
|
for i := len(fs) - 1; i >= 0; i-- { // the newest first: a newer form may be a narrower shape of an older one
|
|
f := fs[i]
|
|
if (kind == "" || slices.Contains(f.Kinds, kind)) && f.Is(reference) {
|
|
return f.Name, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
// Reads says whether an engine whose statement is stated reads form: a first form always (the baseline included), any
|
|
// other only when stated names `form:<name>`. A form fs does not know is read only when stated.
|
|
func (fs ArtifactForms) Reads(stated []string, form string) bool {
|
|
for _, f := range fs {
|
|
if f.Name == form && f.First {
|
|
return true
|
|
}
|
|
}
|
|
return slices.Contains(stated, FormNeed(form))
|
|
}
|
|
|
|
// Needs are the reader capabilities a done build's artifacts carry (ADR 0618 §1): `form:<name>` of every form any of
|
|
// its references, single or listed, is written in, sorted. Nil when it names none (no artifact, or no reference).
|
|
func (fs ArtifactForms) Needs(artifacts []delivery.BuiltArtifact) []string {
|
|
set := map[string]bool{}
|
|
for _, a := range artifacts {
|
|
for _, r := range append([]string{a.Reference}, a.References...) {
|
|
if r == "" {
|
|
continue
|
|
}
|
|
if form, ok := fs.FormOf(a.Kind, r); ok {
|
|
set[FormNeed(form)] = true
|
|
}
|
|
}
|
|
}
|
|
if len(set) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]string, 0, len(set))
|
|
for n := range set {
|
|
out = append(out, n)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// Held says whether a build's reference forms hold a node's want (ADR 0624 §2): only when the build names forms and the
|
|
// node's engine, its statement stated, reads none of them. A build that names none holds no want.
|
|
func (fs ArtifactForms) Held(needs, stated []string) bool {
|
|
if len(needs) == 0 {
|
|
return false
|
|
}
|
|
for _, n := range needs {
|
|
if form, ok := strings.CutPrefix(n, catalogue.ReadForm); ok && fs.Reads(stated, form) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Writable are the forms of kind every present node's engine reads (ADR 0618 §2: a new form is written only once every
|
|
// present engine states it), oldest first, by node its statement. With no node present, the first forms only.
|
|
func (fs ArtifactForms) Writable(kind string, present map[string][]string) []string {
|
|
var out []string
|
|
for _, f := range fs {
|
|
if !slices.Contains(f.Kinds, kind) {
|
|
continue
|
|
}
|
|
every := f.First || len(present) > 0
|
|
for _, stated := range present {
|
|
every = every && fs.Reads(stated, f.Name)
|
|
}
|
|
if every {
|
|
out = append(out, f.Name)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Kept are the forms a build key names an artifact of kind in (ADR 0624 §1, §3), oldest first, so the first is the one
|
|
// the single reference holds: the newest form every present engine reads (Writable), then, for each counted node, present
|
|
// or away, whose engine reads none of those kept so far, the newest form of kind it reads. A node stating nothing reads
|
|
// the first forms. A form leaves only once no counted node reads it alone; a node leaves the count when it is retired.
|
|
func (fs ArtifactForms) Kept(kind string, present, counted map[string][]string) []string {
|
|
writable := fs.Writable(kind, present)
|
|
kept := map[string]bool{}
|
|
if len(writable) > 0 {
|
|
kept[writable[len(writable)-1]] = true
|
|
}
|
|
nodes := make([]string, 0, len(counted))
|
|
for node := range counted {
|
|
nodes = append(nodes, node)
|
|
}
|
|
sort.Strings(nodes)
|
|
for _, node := range nodes {
|
|
stated := counted[node]
|
|
var newest string
|
|
readsKept := false
|
|
for _, f := range fs {
|
|
if !slices.Contains(f.Kinds, kind) || !fs.Reads(stated, f.Name) {
|
|
continue
|
|
}
|
|
newest = f.Name
|
|
readsKept = readsKept || kept[f.Name]
|
|
}
|
|
if !readsKept && newest != "" {
|
|
kept[newest] = true
|
|
}
|
|
}
|
|
var out []string
|
|
for _, f := range fs {
|
|
if kept[f.Name] {
|
|
out = append(out, f.Name)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Choose names a built artifact by the references the build answered in the forms Kept keeps (ADR 0624 §1, §3): the
|
|
// oldest kept form in the single Reference, every kept one in References when there are more than one. A reference in a
|
|
// form Kept does not keep is left out, unless none of the answered is kept: then the artifact keeps what was answered,
|
|
// its first answered in Reference, since a key names where its artifact is (ADR 0498 clause 1). An answered reference
|
|
// in a form that derives an older one (ArtifactForm.Older) answers that older one too, so the build answers one form and
|
|
// the key keeps each a counted node reads.
|
|
func (fs ArtifactForms) Choose(a delivery.BuiltArtifact, answered []string, present, counted map[string][]string) delivery.BuiltArtifact {
|
|
kept := fs.Kept(a.Kind, present, counted)
|
|
answered = fs.withOlder(a.Kind, answered)
|
|
type ref struct {
|
|
at int
|
|
reference string
|
|
}
|
|
var in []ref
|
|
for _, r := range answered {
|
|
if r == "" {
|
|
continue
|
|
}
|
|
form, ok := fs.FormOf(a.Kind, r)
|
|
if at := slices.Index(kept, form); ok && at >= 0 {
|
|
in = append(in, ref{at, r})
|
|
}
|
|
}
|
|
sort.SliceStable(in, func(i, j int) bool { return in[i].at < in[j].at })
|
|
a.References = nil
|
|
switch {
|
|
case len(in) > 0:
|
|
a.Reference = in[0].reference
|
|
if len(in) > 1 {
|
|
for _, r := range in {
|
|
a.References = append(a.References, r.reference)
|
|
}
|
|
}
|
|
default:
|
|
a.Reference = ""
|
|
for _, r := range answered {
|
|
if r == "" {
|
|
continue
|
|
}
|
|
if a.Reference == "" {
|
|
a.Reference = r
|
|
}
|
|
a.References = append(a.References, r)
|
|
}
|
|
if len(a.References) < 2 {
|
|
a.References = nil
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
// withOlder is answered with, after it, each older reference an answered one's form derives that was not answered itself.
|
|
func (fs ArtifactForms) withOlder(kind string, answered []string) []string {
|
|
out := slices.Clone(answered)
|
|
for _, r := range answered {
|
|
name, ok := fs.FormOf(kind, r)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, f := range fs {
|
|
if f.Name == name && f.Older != nil {
|
|
if older := f.Older(r); !slices.Contains(out, older) {
|
|
out = append(out, older)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|