Files
mesh-delivery/modules/mesh-delivery-builder/internal/versions/claim.go
T
jschoubben 6b3a679151 DLV-B5: builder_publish_again, builder_floor_again, builder_tag, builder_tag_asks and builder_untag, with the state tag-asks
A holder that failed leaves a version to publish again by hand: said numbered again while the build is held, built
again under the same number when its file is gone, numbered anew after a published; refused for other bytes. The
floor is asked again by hand; a branch's tagged versions are removed through each seat's remove, per repository.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 12:30:20 +01:00

184 lines
6.1 KiB
Go

package versions
import (
"context"
"fmt"
"sort"
"strings"
"time"
)
// The registry seats a version is published to (design 51 *The seats*), by what is published.
const (
SeatImages = "container-image-registry"
SeatArtifacts = "oci-artifact-registry"
SeatGoPackages = "go-package-registry"
SeatNPM = "npm-package-registry"
)
// SeatOf is the registry seat an artifact of kind in language is published to, and the kind the builder numbers it as;
// ok is false for what is not numbered (an upstream copy is mirrored by its pinned digest, design 51 *Publishing*).
func SeatOf(kind, language string) (seat, numberedAs string, ok bool) {
switch kind {
case KindImage:
return SeatImages, KindImage, true
case KindBundle, KindArchive:
return SeatArtifacts, kind, true
case KindPackage:
switch language {
case "go":
return SeatGoPackages, KindPackage, true
case "typescript", "javascript", "node":
return SeatNPM, KindPackage, true
}
}
return "", "", false
}
// NameOf is the name a module's artifact is numbered under: `<module>/<artifact>`, the name the seat answers it by
// (ADR 0605 §9's `<registry>/<module>/<artifact>@…`).
func NameOf(module, artifact string) string { return module + "/" + artifact }
// TagPrefix is the git tag prefix of a package in the module's directory path: `go/v` for mesh-sdk's `go`, `v` at the
// root (design 51: the builder tags each Go version `go/v0.1.<n>`).
func TagPrefix(path string) string {
path = strings.Trim(path, "/")
if path == "" || path == "." {
return "v"
}
return path + "/v"
}
// ClaimArtifact is one artifact of a trunk build to number.
type ClaimArtifact struct {
Artifact, Kind, Language string
}
// BuildClaim is a trunk build at its claim: what Numberer.Number numbers.
type BuildClaim struct {
Module, Path, Repository, Fingerprint, Commit, Series string
// Revision is the source revision of the merge (a package is numbered per revision); Merge its sequence, for order.
Revision string
Merge uint64
Artifacts []ClaimArtifact
}
// Numbering is what the claim got.
type Numbering struct {
// Versions are each artifact's version, by artifact; an artifact built unnumbered (its floor not known) has none.
Versions map[string]string
// Unnumbered are the artifacts built and published by digest, numbered once their floor answers (ADR 0617 §1).
Unnumbered []string
// Wait is why the build waits: a package whose floor is not known (design 51 *The floor*). Nothing is claimed.
Wait string
// Said are the builder's narrations of each decision, for its log.
Said []string
}
// Numberer numbers trunk builds at their claim (design 51 *The versions*, DLV-B2, B3): it asks a name's floor only
// when versions holds nothing for it, records a silent floor as a wait (FloorWaits, woken by its source's event), and
// says each new number (Said, the event version.numbered).
type Numberer struct {
Versions Versions
Floor FloorSources
Waits *FloorWaits
// Holder is the module holding a seat, whose health condition's clear wakes a silent seat source; nil names none.
Holder func(seat string) string
// Said is told each version newly numbered; nil tells nobody.
Said func(NumberedBody)
Now func() time.Time
}
// Number numbers every numbered artifact of c. An error is a store that cannot be read or written: the claim is then
// built as before, unnumbered, and said.
func (n *Numberer) Number(ctx context.Context, c BuildClaim) (Numbering, error) {
out := Numbering{Versions: map[string]string{}}
arts := append([]ClaimArtifact(nil), c.Artifacts...)
sort.Slice(arts, func(i, j int) bool { return arts[i].Artifact < arts[j].Artifact })
for _, a := range arts {
seat, kind, ok := SeatOf(a.Kind, a.Language)
if !ok {
continue
}
name := NameOf(c.Module, a.Artifact)
entries, err := n.Versions.Of(ctx, name)
if err != nil {
return out, err
}
var floor Floor
var why map[string]string
if len(entries) == 0 {
floor, why = n.askFloor(ctx, seat, name, c)
}
d, e, err := n.Versions.Number(ctx, Claim{Name: name, Kind: kind, Series: c.Series, Module: c.Module, Fingerprint: c.Fingerprint,
Commit: c.Commit, Revision: c.Revision, Seat: seat, Merge: c.Merge, Floor: floor})
if err != nil {
return out, err
}
out.Said = append(out.Said, name+": "+d.Why)
switch {
case d.Wait:
n.wait(name, seat, floor, why)
out.Wait = name + ": " + d.Why
return out, nil
case d.Unnumbered:
n.wait(name, seat, floor, why)
out.Unnumbered = append(out.Unnumbered, a.Artifact)
continue
}
if n.Waits != nil {
n.Waits.Answered(name)
}
out.Versions[a.Artifact] = d.Version
if !d.Keep && n.Said != nil {
n.Said(NumberedOf(e, ""))
}
}
return out, nil
}
func (n *Numberer) askFloor(ctx context.Context, seat, name string, c BuildClaim) (Floor, map[string]string) {
return n.Floor.Ask(ctx, FloorOf{Seat: seat, Name: name, Repository: c.Repository, Prefix: TagPrefix(c.Path)})
}
func (n *Numberer) wait(name, seat string, f Floor, why map[string]string) {
if n.Waits == nil {
return
}
holder := ""
if n.Holder != nil {
holder = n.Holder(seat)
}
now := time.Now()
if n.Now != nil {
now = n.Now()
}
n.Waits.Wait(name, seat, holder, f, why, now)
}
// Built records the digest each numbered artifact was built with, on an entry that holds none yet or was never published: what the holder's
// builder_versions question compares (ADR 0605 §4).
func (n *Numberer) Built(ctx context.Context, c BuildClaim, got Numbering, digests map[string]string) error {
var errs []string
for artifact, version := range got.Versions {
d := digests[artifact]
if d == "" {
continue
}
if _, err := n.Versions.Mark(ctx, NameOf(c.Module, artifact), version, func(e *Entry) {
// The new digest replaces the recorded one only while no published was said for the number (design 51
// *Tools of the builder*): a rebuild of a gone file under the same number.
if e.Digest == "" || e.State == Numbered || e.State == Refused {
e.Digest = d
}
}); err != nil {
errs = append(errs, err.Error())
}
}
if len(errs) > 0 {
sort.Strings(errs)
return fmt.Errorf("the built digests could not be recorded: %s", strings.Join(errs, "; "))
}
return nil
}