The delivery repository's contracts and SDK live under modules/ as mesh-delivery-contracts and mesh-delivery-sdk, their Go module paths renamed to match the folders (the TypeScript names already did), and every importer in mesh-delivery, mesh-catalog, mesh-host, mesh-tools and mesh-sdk follows in the same group: a design as it should be, one path per module, checked by the group's check building every importer. Builds: design 47 G §427, §88 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
149 lines
5.8 KiB
Go
149 lines
5.8 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// **The artifact store's holder takes its own new build even when its seat is the one broken** (novox/hq design 47,
|
|
// lines 1888-1906 at b33ac325; test 60): when the seat oci-artifact-registry refuses or does not answer the store of a
|
|
// trunk build of a module that claims that seat, the build module keeps that build on the bus, one object per build
|
|
// named by its digest, and the node-engine composing the holder asks the build seats' verb kept-artifact where it is.
|
|
// The judgement the three build modules share is here (ADR 0634 §2): what may be kept, the kept builds a node holds,
|
|
// and the verb's answer with its three refusals. The store and the verb's serving are each build module's own.
|
|
|
|
// StoreSeat is the seat whose holder's builds a build node keeps.
|
|
const StoreSeat = "oci-artifact-registry"
|
|
|
|
// The refusals of kept-artifact, the words of the part build-step's enum.
|
|
const (
|
|
KeptRefusedClaimsNoStore = "claims no " + StoreSeat
|
|
KeptRefusedNotKept = "not kept"
|
|
KeptRefusedDigestDiffers = "digest differs"
|
|
)
|
|
|
|
// CatalogModuleVerb is the seat mesh-catalog's verb that answers one module's manifest, as a manifest invokes it.
|
|
const CatalogModuleVerb = "seat:mesh-catalog.module"
|
|
|
|
// KeptFor is what a build keeps a refused artifact for: the module, which claims the store's seat, and its build's
|
|
// fingerprint, which kept-artifact is asked by.
|
|
type KeptFor struct{ Module, Fp string }
|
|
|
|
// KeepOf is the keep of a build, or nil when nothing may be kept: only a trunk build (publish) of a module whose manifest
|
|
// at the built commit claims the store's seat, named by its fingerprint.
|
|
func KeepOf(publish bool, manifest catalogue.Manifest, fp string) *KeptFor {
|
|
if !publish || fp == "" || !manifest.ClaimsSeat(StoreSeat) {
|
|
return nil
|
|
}
|
|
return &KeptFor{Module: manifest.Module, Fp: fp}
|
|
}
|
|
|
|
// KeptBuild is one kept build: the object's description names its module, fingerprint and the node that kept it.
|
|
type KeptBuild struct {
|
|
Module string `json:"module"`
|
|
Fp string `json:"fp"`
|
|
Node string `json:"node"`
|
|
Digest string `json:"digest"`
|
|
Bytes uint64 `json:"bytes"`
|
|
LastUsed time.Time `json:"last-used"`
|
|
}
|
|
|
|
// KeptObject is the object name (the hex digest of "sha256:<hex>") and description a build is kept under on node.
|
|
func KeptObject(k KeptFor, node, digest string) (name, description string) {
|
|
desc, _ := json.Marshal(KeptBuild{Module: k.Module, Fp: k.Fp, Node: node})
|
|
return strings.TrimPrefix(digest, "sha256:"), string(desc)
|
|
}
|
|
|
|
// KeptBuildsOf are the builds node keeps among a store's objects, by digest.
|
|
func KeptBuildsOf(infos []buscalls.ObjectInfo, node string) []KeptBuild {
|
|
out := []KeptBuild{}
|
|
for _, i := range infos {
|
|
var k KeptBuild
|
|
if i.Deleted || json.Unmarshal([]byte(i.Description), &k) != nil || k.Node != node {
|
|
continue
|
|
}
|
|
k.Digest, k.Bytes, k.LastUsed = i.Name, i.Size, i.Modified.UTC()
|
|
out = append(out, k)
|
|
}
|
|
slices.SortFunc(out, func(a, b KeptBuild) int { return strings.Compare(a.Digest, b.Digest) })
|
|
return out
|
|
}
|
|
|
|
// ClaimsStoreOf reads the seat mesh-catalog's answer of one module (CatalogModuleVerb): whether its manifest claims the
|
|
// store's seat; a module the catalog holds no manifest of claims nothing.
|
|
func ClaimsStoreOf(module string, raw []byte) (bool, error) {
|
|
var a struct {
|
|
Module struct {
|
|
Manifest json.RawMessage `json:"manifest"`
|
|
} `json:"module"`
|
|
}
|
|
if err := json.Unmarshal(raw, &a); err != nil {
|
|
return false, fmt.Errorf("%s answered what is no module: %w", CatalogModuleVerb, err)
|
|
}
|
|
if len(a.Module.Manifest) == 0 {
|
|
return false, nil
|
|
}
|
|
man, err := catalogue.Decode(a.Module.Manifest)
|
|
if err != nil {
|
|
return false, fmt.Errorf("the manifest of %s: %w", module, err)
|
|
}
|
|
return man.ClaimsSeat(StoreSeat), nil
|
|
}
|
|
|
|
// KeptArtifactAnswer is the verb kept-artifact's answer, the part build-step's layout.
|
|
type KeptArtifactAnswer struct {
|
|
Module string `json:"module"`
|
|
Fp string `json:"fp"`
|
|
Digest string `json:"digest,omitempty"`
|
|
Size int64 `json:"size,omitempty"`
|
|
Store string `json:"store,omitempty"`
|
|
Object string `json:"object,omitempty"`
|
|
Refused string `json:"refused,omitempty"`
|
|
Words string `json:"words,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// KeptArtifact answers kept-artifact for module, fp and the hex digest from the builds this node keeps, in store (the
|
|
// store's name on the bus); claims asks the seat mesh-catalog whether a module claims the store's seat, only when no
|
|
// build of it is kept here at that digest.
|
|
func KeptArtifact(module, fp, digest string, kept []KeptBuild, store string, claims func(module string) (bool, error)) KeptArtifactAnswer {
|
|
a := KeptArtifactAnswer{Module: module, Fp: fp}
|
|
var other *KeptBuild
|
|
for i, k := range kept {
|
|
if k.Module != module || k.Fp != fp {
|
|
continue
|
|
}
|
|
if k.Digest == digest {
|
|
a.Digest, a.Size, a.Object = k.Digest, int64(k.Bytes), k.Digest
|
|
if store == "" {
|
|
a.Error = "this module runs with no name of its own (MESH_MODULE), so its store's name on the bus is unknown"
|
|
return a
|
|
}
|
|
a.Store = store
|
|
return a
|
|
}
|
|
other = &kept[i]
|
|
}
|
|
ok, err := claims(module)
|
|
switch {
|
|
case err != nil:
|
|
a.Error = "asking whether " + module + " claims " + StoreSeat + ": " + err.Error()
|
|
case !ok:
|
|
a.Refused = KeptRefusedClaimsNoStore
|
|
a.Words = fmt.Sprintf("%s claims no %s: a build node keeps builds for that seat's holder alone", module, StoreSeat)
|
|
case other != nil:
|
|
a.Refused, a.Digest = KeptRefusedDigestDiffers, other.Digest
|
|
a.Words = fmt.Sprintf("this node keeps the build of %s at %s under the digest %s, not %s", module, fp, other.Digest, digest)
|
|
default:
|
|
a.Refused = KeptRefusedNotKept
|
|
a.Words = fmt.Sprintf("this node keeps no build of %s at %s (never kept, or swept)", module, fp)
|
|
}
|
|
return a
|
|
}
|