BL1: the whole package-published per artifact on the registry seat's published, from the reservation and the step result

When a registry seat says published, the builder says the seat mesh-builds' package-published once per artifact the reservation
newly marks: the artifact reference, version and digest published, the watched branch and the source revision kept on the
reservation from the entry the build came from (A-57), and the dependencies of kind 1 (resolved) and 3 (toolchain) the step result
named (§466, §673), their parts the artifact references the entry's edges name. The reservation keeps the dependencies from the
build's end until the publication.

Builds: design 47 G §52, §334, §349, §371, §466, §673, A-57
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-11 23:30:45 +01:00
parent 088f55f94c
commit 8ea78f709a
8 changed files with 223 additions and 30 deletions
@@ -209,6 +209,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
// and version-released heard; nothing is reserved before the fill.
reservedKV := process.AwaitBucket(ctx, js, conn, versions.ReservedBucket, "no version is reserved: builds are made unreserved", logger.Printf)
reserved := newReservations(awaitedReserved{awaitedVersions{b: reservedKV, bucket: versions.ReservedBucket}}, calls.Call, pubBoundsKV.KV, found, b.Reconsider, logger.Printf)
// Each artifact the holder says published at a reserved version is said as the seat mesh-builds' package-published (design 47 G §52).
reserved.say = func(ctx context.Context, p meshbuilds.PackagePublished) error {
return emitSeatEvent(context.WithoutCancel(ctx), js, cred, meshbuilds.SubjectPackagePublished, "package-published-"+p.Module+"-"+p.Version+"-"+p.Artifact, p)
}
b.Reserved = reserved
// A state bound later numbers the builds looked at without it.
versionsKV.Ready(func(jetstream.KeyValue) { b.Reconsider(ctx) })
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
"strings"
"sync/atomic"
"time"
@@ -48,6 +49,8 @@ type reservations struct {
logf func(string, ...any)
// call asks a seat's verb: the git seat's tag (design 47 G §370).
call toolCaller
// say says package-published on the seat mesh-builds; nil says none.
say func(context.Context, meshbuilds.PackagePublished) error
}
// newReservations makes the reserving over store; the released versions of a module are asked of the seat mesh-catalog's
@@ -82,9 +85,9 @@ func (rv *reservations) Reserve(ctx context.Context, module, series string, arti
// Built records the digests; once every artifact has one, the publish's bound starts (a key of the state bounds with its
// own time to live): its end with the reservation standing is version-unreleased.
func (rv *reservations) Built(ctx context.Context, module, version string, digests map[string]string) error {
func (rv *reservations) Built(ctx context.Context, module, version string, digests map[string]string, deps map[string][]meshbuilds.Dependency) error {
for ref, d := range digests {
if _, err := rv.r.Built(ctx, module, version, ref, d); err != nil {
if _, err := rv.r.Built(ctx, module, version, ref, d, deps[ref]); err != nil {
return err
}
}
@@ -198,9 +201,19 @@ func (rv *reservations) heard(ctx context.Context, m jetstream.Msg) bool {
// published marks the artifacts of the reservation a registry seat's published names (its version and digest).
func (rv *reservations) published(ctx context.Context, o versions.Outcome) {
if _, err := rv.r.Published(ctx, o.Version, o.Digest); err != nil {
marked, err := rv.r.Published(ctx, o.Version, o.Digest)
if err != nil {
rv.logf("a published of %s@%s is not recorded in the reservations: %v", o.Name, o.Version, err)
}
// Each artifact the holder's published newly marked is said as the seat mesh-builds' package-published (design 47 G §52, §334).
for _, m := range marked {
if rv.say == nil {
continue
}
if err := rv.say(ctx, m.PackagePublished(o.Digest, time.Now())); err != nil {
rv.logf("the package-published of %s at %s is not said: %v", m.Artifact, m.Reserved.Version, err)
}
}
}
// dropped is builder_reserved_drop: the reservation is deleted and its bound ended.
@@ -11,6 +11,7 @@ import (
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/git"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"mesh-delivery-builder/internal/versions"
@@ -93,11 +94,13 @@ func TestTheCatalogsWordsMoveTheReservations(t *testing.T) {
if !rv.heard(ctx, filled) || !filled.acked || !rv.Filled() || woke != 1 {
t.Fatalf("versions-filled: acked %v, filled %v, woke %d", filled.acked, rv.Filled(), woke)
}
res, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"}, versions.Source{})
var said []meshbuilds.PackagePublished
rv.say = func(_ context.Context, p meshbuilds.PackagePublished) error { said = append(said, p); return nil }
res, err := rv.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"}, versions.Source{Branch: "main", Revision: 41})
if err != nil || res.Version != "1.4.3" {
t.Fatalf("reserved %q (%v), want 1.4.3 after the catalog's 1.4.2", res.Version, err)
}
if err := rv.Built(ctx, "m", "1.4.3", map[string]string{"m:a": "sha256:a", "m:b": "sha256:b"}); err != nil {
if err := rv.Built(ctx, "m", "1.4.3", map[string]string{"m:a": "sha256:a", "m:b": "sha256:b"}, nil); err != nil {
t.Fatal(err)
}
boundKey := versions.ReservedBoundKey("m", "1.4")
@@ -105,6 +108,12 @@ func TestTheCatalogsWordsMoveTheReservations(t *testing.T) {
t.Fatalf("the publish's bound is not armed: %v", bounds.keys)
}
rv.published(ctx, versions.Outcome{Name: "m-a", Version: "1.4.3", Digest: "sha256:a"})
// Design 47 G §52, A-57: the holder's published is said once, per artifact, as the seat mesh-builds' package-published with the
// branch and source revision of the reservation; a second published of the same artifact says nothing.
rv.published(ctx, versions.Outcome{Name: "m-a", Version: "1.4.3", Digest: "sha256:a"})
if len(said) != 1 || said[0].Artifact != "m:a" || said[0].Version != "1.4.3" || said[0].Digest != "sha256:a" || said[0].Branch != "main" || said[0].Source != 41 {
t.Fatalf("package-published said %+v, want one of m:a at 1.4.3 for main at source 41", said)
}
if !rv.ended(ctx, purged{key: boundKey}) {
t.Fatal("a reservation's bound was not taken in")
}
@@ -3,6 +3,7 @@ package build
import (
"context"
"fmt"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"strconv"
"strings"
@@ -135,8 +136,9 @@ type Reservations interface {
Filled() bool
// Reserve reserves module's next version in series for the artifacts (their references, <module>:<artifact>).
Reserve(ctx context.Context, module, series string, artifacts []string, src versions.Source) (versions.ReservedVersion, error)
// Built records the digests of the artifacts built under the reserved version, by reference.
Built(ctx context.Context, module, version string, digests map[string]string) error
// Built records the digests of the artifacts built under the reserved version, by reference, and what each resolved and was
// built with (design 47 G §466, §673).
Built(ctx context.Context, module, version string, digests map[string]string, deps map[string][]meshbuilds.Dependency) error
}
// reserves says whether a look at a trunk build is one that reserves: the build has a series of the form MAJOR.MINOR
@@ -2,6 +2,7 @@ package build
import (
"context"
"encoding/json"
"fmt"
"reflect"
"slices"
@@ -9,6 +10,7 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
@@ -24,6 +26,7 @@ type fakeReservations struct {
series []string
built map[string]string
sources []versions.Source
deps map[string][]meshbuilds.Dependency
}
func (f *fakeReservations) Filled() bool { return f.filled }
@@ -33,7 +36,8 @@ func (f *fakeReservations) Reserve(_ context.Context, module, series string, art
return versions.ReservedVersion{Module: module, Series: series, Version: series + ".5"}, nil
}
func (f *fakeReservations) Built(_ context.Context, _, version string, digests map[string]string) error {
func (f *fakeReservations) Built(_ context.Context, _, version string, digests map[string]string, deps map[string][]meshbuilds.Dependency) error {
f.deps = deps
f.built = map[string]string{version: ""}
for ref, d := range digests {
f.built[ref] = d
@@ -161,3 +165,42 @@ func TestOnlyABuildOfTheFirstWatchedBranchReservesAndItCarriesItsCatalogRevision
})
}
}
// TestAReservedBuildKeepsWhatItsStepResolvedAndWasBuiltWith: design 47 G §466, §673, A-57 — the `resolved` of each artifact and the
// toolchain digest of the step's result are kept on the reservation as dependencies of kind 1 and 3, their parts the artifact
// references the entry's edges name; a package no edge names is left out and said.
func TestAReservedBuildKeepsWhatItsStepResolvedAndWasBuiltWith(t *testing.T) {
r := newRig(t, goBundle)
r.mf.byDigest[mdigest('a')] = catalogue.Manifest{Module: "m", Version: "0.1", Build: &catalogue.Build{Artifacts: []catalogue.Artifact{goBundle}}}
res := &fakeReservations{filled: true}
r.b.Reserved = res
var journal []string
r.b.Logf = func(f string, a ...any) { journal = append(journal, fmt.Sprintf(f, a...)) }
r.asks.Answer = func(a buildstep.StepAsk) buildstep.Step {
st := r.answer(a)
var out buildstep.BuildAnswer
if err := json.Unmarshal(st.Result, &out); err != nil || len(out.Artifacts) == 0 {
return st
}
out.Artifacts[0].Resolved = []buildstep.AskResolved{{Package: "git.novox.be/novox/mesh-sdk/go", Version: "0.1.14", Digest: "sha256:11"},
{Package: "unnamed/pkg", Version: "1.0.0", Digest: "sha256:22"}}
out.Toolchain = &buildstep.ToolchainUsed{Language: "go", Version: "go1.27.1", Recipe: "1", Digest: "sha256:tc"}
st.Result, _ = json.Marshal(out)
return st
}
r.b.Entry(context.Background(), Origin{Branch: "main", Source: 41, First: true}, meshcatalog.CatalogEntry{Module: "m", Repository: "novox/mesh-catalog",
Path: "modules/m", Commit: "7a74cee83f88e6585a5511c3cc53bdf0f5303d20", Fp: fp1, ManifestDigest: mdigest('a'),
Inputs: &meshcatalog.EntryInputs{Toolchains: CatalogToolchains(agreedToolchains)},
Edges: []meshcatalog.Edge{{Kind: 1, From: "mesh-sdk:go", To: "m:tool", Why: "git.novox.be/novox/mesh-sdk/go"}, {Kind: 3, From: "go-toolchain:program", To: "m:tool"}}})
r.b.Wait()
want := map[string][]meshbuilds.Dependency{"m:tool": {{Kind: 1, Part: "mesh-sdk:go", Version: "0.1.14", Digest: "sha256:11"},
{Kind: 3, Part: "go-toolchain:program", Version: "go1.27.1", Digest: "sha256:tc"}}}
if !reflect.DeepEqual(res.deps, want) {
t.Errorf("dependencies = %+v, want %+v", res.deps, want)
}
if !slices.ContainsFunc(journal, func(l string) bool {
return strings.Contains(l, "unnamed/pkg") && strings.Contains(l, "names no artifact")
}) {
t.Errorf("journal %q, want the package no edge names said", journal)
}
}
@@ -1,6 +1,7 @@
package build
import (
"cmp"
"context"
"encoding/json"
"errors"
@@ -15,6 +16,7 @@ import (
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
sweep "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
@@ -120,6 +122,8 @@ type Builder struct {
known map[string]meshcatalog.ModulesByModuleValue // by modules key: the first watched branch's
heard map[string]meshcatalog.ModulesByModuleValue // by branch and module (take): what supersedes what
origin map[string]Origin // by build key: the catalog revision its entry came from (take)
edges map[string][]meshcatalog.Edge // by build key: the edges of its entry (take), whose parts a package-published names
deps map[string]map[string][]meshbuilds.Dependency // by build key: what each artifact resolved and was built with (keepDependencies)
inflight map[string]bool // by build key
again map[string]meshcatalog.ModulesByModuleValue // by build key: the newest value that arrived while the key was being built
plans map[string]planned // by manifest digest
@@ -172,7 +176,7 @@ func (b *Builder) init() {
b.sem = make(chan struct{}, limit)
b.cands, b.via, b.trunkFP = map[string]meshcatalog.CandidatesByGroupValue{}, map[string]via{}, map[string]bool{}
b.heard, b.known = map[string]meshcatalog.ModulesByModuleValue{}, map[string]meshcatalog.ModulesByModuleValue{}
b.origin = map[string]Origin{}
b.origin, b.edges = map[string]Origin{}, map[string][]meshcatalog.Edge{}
b.inflight, b.again, b.plans, b.tried = map[string]bool{}, map[string]meshcatalog.ModulesByModuleValue{}, map[string]planned{}, map[string]bool{}
b.published, b.held, b.waiting = map[string]bool{}, map[string]bool{}, map[string]map[string]chan string{}
b.hands = map[string]*handWatch{}
@@ -200,7 +204,7 @@ func (b *Builder) Module(ctx context.Context, key string, value []byte) {
b.logf("modules.%s holds what is not a module, left: %v", key, err)
return
}
b.take(ctx, Origin{First: true}, key, m)
b.take(ctx, Origin{First: true}, key, m, nil)
}
// Origin is the catalog revision an entry came from (design 47 G §371, A-57): the watched branch it is of, as named, the source
@@ -221,7 +225,7 @@ func (b *Builder) Entry(ctx context.Context, o Origin, e meshcatalog.CatalogEntr
b.logf("the catalog half of the branch %q holds an entry that names no module, left", o.Branch)
return
}
b.take(ctx, o, e.Module, ModuleOf(e))
b.take(ctx, o, e.Module, ModuleOf(e), e.Edges)
}
// EntryGone takes a module's entry out of a branch's half (the module was dropped from it, or the branch is followed no more).
@@ -235,7 +239,7 @@ func (b *Builder) EntryGone(branch string, first bool, module string) {
}
}
func (b *Builder) take(ctx context.Context, o Origin, key string, m meshcatalog.ModulesByModuleValue) {
func (b *Builder) take(ctx context.Context, o Origin, key string, m meshcatalog.ModulesByModuleValue, e []meshcatalog.Edge) {
b.mu.Lock()
hk := o.Branch + "\x00" + key
old, had := b.heard[hk]
@@ -253,6 +257,7 @@ func (b *Builder) take(ctx context.Context, o Origin, key string, m meshcatalog.
// A fingerprint a watched branch carries is built from that branch's commit, whoever asked for it first.
b.trunkFP[bk] = true
b.origin[bk] = o
b.edges[bk] = e
delete(b.via, bk)
}
b.mu.Unlock()
@@ -716,7 +721,7 @@ func (b *Builder) try(ctx context.Context, module string, m meshcatalog.ModulesB
for _, a := range result.Artifacts {
digests[module+":"+a.Name] = a.Digest
}
if err := b.Reserved.Built(ctx, module, reserved.Version, digests); err != nil {
if err := b.Reserved.Built(ctx, module, reserved.Version, digests, b.takeDependencies(key)); err != nil {
b.logf("%s: the digests of %s are not recorded: %v", cl.Key, reserved.Version, err)
}
}
@@ -763,11 +768,14 @@ type lane struct {
version string // the version the build reserved, MAJOR.MINOR.PATCH, empty for a build that reserves none (design 47 G §573)
tasks []Task
toolchain *builds.ToolchainUsed
digests map[string]string // artifact -> digest
references map[string]string // artifact -> the reference an image was pushed to (a trunk build only)
imports []string // the mesh packages the lane's programs import, as its build module answered them
kept string // the digest of the artifact the build node kept instead of storing it (BuildAnswer.Kept), else empty
outOfStep []string // the generated files its build module's compare found out of step, passed or failed
digests map[string]string // artifact -> digest
references map[string]string // artifact -> the reference an image was pushed to (a trunk build only)
imports []string // the mesh packages the lane's programs import, as its build module answered them
resolved map[string][]sdkstep.AskResolved // artifact -> the mesh packages its build resolved (design 47 G §466)
tcDigest string // the digest of the toolchain program the building node ran, kind 3's (design 47 G §466, §472)
tcVersion string // and the version it ran
kept string // the digest of the artifact the build node kept instead of storing it (BuildAnswer.Kept), else empty
outOfStep []string // the generated files its build module's compare found out of step, passed or failed
err error
}
@@ -816,7 +824,7 @@ func (b *Builder) build(ctx context.Context, module string, m meshcatalog.Module
}
}
// One ask builds each fingerprint's part of a build module, on whichever node pulls it (ADR 0525 §3).
lanes = append(lanes, &lane{module: bm, version: version, tasks: tasks, toolchain: toolchain, digests: map[string]string{}, references: map[string]string{}})
lanes = append(lanes, &lane{module: bm, version: version, tasks: tasks, toolchain: toolchain, digests: map[string]string{}, references: map[string]string{}, resolved: map[string][]sdkstep.AskResolved{}})
}
if key, err := builds.BuilderBuildKey(module, m.Fp2); err == nil {
b.handLanes(key, len(lanes))
@@ -829,6 +837,7 @@ func (b *Builder) build(ctx context.Context, module string, m meshcatalog.Module
wg.Wait()
if key, err := builds.BuilderBuildKey(module, m.Fp2); err == nil {
b.keepOutOfStep(key, lanes)
b.keepDependencies(key, module, lanes)
}
var errs []string
for _, l := range lanes {
@@ -1138,6 +1147,9 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m mes
}
l.imports = answer.Imports
l.kept = answer.Kept
if answer.Toolchain != nil {
l.tcDigest, l.tcVersion = answer.Toolchain.Digest, answer.Toolchain.Version
}
built := map[string]sdkstep.AskBuilt{}
for _, a := range answer.Artifacts {
built[a.Artifact] = a
@@ -1149,6 +1161,7 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m mes
return
}
l.digests[t.Artifact] = a.Digest
l.resolved[t.Artifact] = a.Resolved
if a.Reference != "" && (stored(t.Kind) || !candidate) {
l.references[t.Artifact] = a.Reference
}
@@ -1283,3 +1296,73 @@ func SittingOutWords(language string, a sdkstep.Agreement) []string {
}
return out
}
// keepDependencies keeps, for a build key, what its step results say each artifact resolved (kind 1) and was built with (kind 3) as
// the dependencies `package-published` carries (design 47 G §466, §673, A-57): the part of a kind-1 item is the artifact reference of
// the package, and of the kind-3 item the toolchain's part, each as the edges of the entry name it. A part no edge names is said and
// its item left out.
func (b *Builder) keepDependencies(key, module string, lanes []*lane) {
b.mu.Lock()
edges := b.edges[key]
b.mu.Unlock()
out := map[string][]meshbuilds.Dependency{}
for _, l := range lanes {
for _, t := range l.tasks {
ref := module + ":" + t.Artifact
var deps []meshbuilds.Dependency
for _, r := range l.resolved[t.Artifact] {
part := ""
for _, e := range edges {
if e.Kind == 1 && e.To == ref && e.Why == r.Package {
part = cmp.Or(e.Part, e.From)
}
}
if part == "" && b.Members != nil {
if mod, art, ok, err := b.Members(context.Background(), r.Package); ok && err == nil {
part = mod + ":" + art
}
}
if part == "" {
b.logf("%s: the package %s the build of %s resolved names no artifact: its dependency is not said", key, r.Package, ref)
continue
}
deps = append(deps, meshbuilds.Dependency{Kind: 1, Part: part, Version: r.Version, Digest: r.Digest})
}
if l.tcDigest != "" {
part := ""
for _, e := range edges {
if e.Kind == 3 && e.To == ref {
part = cmp.Or(e.Part, e.From)
}
}
if part == "" {
b.logf("%s: no edge of kind 3 names the toolchain part the build of %s used: its dependency is not said", key, ref)
} else {
deps = append(deps, meshbuilds.Dependency{Kind: 3, Part: part, Version: l.tcVersion, Digest: l.tcDigest})
}
}
if len(deps) > 0 {
out[ref] = deps
}
}
}
b.mu.Lock()
defer b.mu.Unlock()
if b.deps == nil {
b.deps = map[string]map[string][]meshbuilds.Dependency{}
}
if len(out) == 0 {
delete(b.deps, key)
return
}
b.deps[key] = out
}
// takeDependencies answers the dependencies held for a build key and forgets them.
func (b *Builder) takeDependencies(key string) map[string][]meshbuilds.Dependency {
b.mu.Lock()
defer b.mu.Unlock()
d := b.deps[key]
delete(b.deps, key)
return d
}
@@ -58,7 +58,10 @@ type ReservedVersion struct {
// (design 47 G §371, A-57): what `package-published` says of the publication.
Branch string `json:"branch,omitempty"`
Source int64 `json:"source,omitempty"`
Tag string `json:"tag,omitempty"`
// Dependencies are, by artifact reference, what the step's result says each artifact resolved (kind 1) and was built with
// (kind 3), kept for `package-published` (design 47 G §466, §673, A-57).
Dependencies map[string][]meshbuilds.Dependency `json:"dependencies,omitempty"`
Tag string `json:"tag,omitempty"`
}
// Source is what a build reserving a version is made from (design 47 G §370): the repository and commit the git tag is
@@ -181,7 +184,7 @@ func (r *Reserver) get(ctx context.Context, key string) (ReservedVersion, uint64
// Built records artifact's digest under the reserved version of module's series; once every artifact has one the build is
// ended (BuiltAt). A reservation moved on to another version (a newer build took the next) is left alone and answers false.
func (r *Reserver) Built(ctx context.Context, module, version, artifact, digest string) (bool, error) {
func (r *Reserver) Built(ctx context.Context, module, version, artifact, digest string, deps []meshbuilds.Dependency) (bool, error) {
series, _, err := SplitVersion(version)
if err != nil {
return false, err
@@ -193,6 +196,12 @@ func (r *Reserver) Built(ctx context.Context, module, version, artifact, digest
return false, err
}
cur.Artifacts[artifact] = digest
if len(deps) > 0 {
if cur.Dependencies == nil {
cur.Dependencies = map[string][]meshbuilds.Dependency{}
}
cur.Dependencies[artifact] = deps
}
if cur.BuiltAt.IsZero() && allSet(cur.Artifacts) {
cur.BuiltAt = r.now()
}
@@ -313,9 +322,24 @@ func (r *Reserver) Drop(ctx context.Context, module, series string) (bool, error
return true, r.Store.Delete(ctx, key)
}
// Marked is an artifact of a reservation a registry seat's published newly marked: the reservation as it stands then.
type Marked struct {
Reserved ReservedVersion
Artifact string
}
// PackagePublished is the seat mesh-builds' package-published of one artifact the holder said published at the reservation's
// version (design 47 G §52, §334, §673, A-57): the artifact reference, the version and the digest published, the watched branch and
// the source revision the build was made from, and the dependencies its step result named.
func (m Marked) PackagePublished(digest string, at time.Time) meshbuilds.PackagePublished {
at = at.UTC()
return meshbuilds.PackagePublished{V: 1, Artifact: m.Artifact, At: &at, Branch: m.Reserved.Branch, Source: m.Reserved.Source, Module: m.Reserved.Module,
Version: m.Reserved.Version, Digest: digest, Dependencies: m.Reserved.Dependencies[m.Artifact]}
}
// Published takes a registry seat's published in: the artifacts of a reservation at version (a leading v is the Go form)
// whose built digest it names are marked published. It answers the keys it marked.
func (r *Reserver) Published(ctx context.Context, version, digest string) ([]string, error) {
// whose built digest it names are marked published. It answers each artifact it newly marked, with the reservation as marked.
func (r *Reserver) Published(ctx context.Context, version, digest string) ([]Marked, error) {
version = strings.TrimPrefix(version, "v")
if version == "" || digest == "" {
return nil, nil
@@ -325,7 +349,7 @@ func (r *Reserver) Published(ctx context.Context, version, digest string) ([]str
return nil, fmt.Errorf("the reservations cannot be listed: %w", err)
}
sort.Strings(keys)
var marked []string
var marked []Marked
for _, k := range keys {
again:
cur, rev, found, err := r.get(ctx, k)
@@ -336,14 +360,17 @@ func (r *Reserver) Published(ctx context.Context, version, digest string) ([]str
continue
}
changed := false
var arts []string
for a, d := range cur.Artifacts {
if d == digest && !cur.Published[a] {
if cur.Published == nil {
cur.Published = map[string]bool{}
}
cur.Published[a], changed = true, true
arts = append(arts, a)
}
}
sort.Strings(arts)
if !changed {
continue
}
@@ -353,7 +380,9 @@ func (r *Reserver) Published(ctx context.Context, version, digest string) ([]str
} else if err != nil {
return marked, fmt.Errorf("%s cannot be written: %w", k, err)
}
marked = append(marked, k)
for _, a := range arts {
marked = append(marked, Marked{Reserved: cur, Artifact: a})
}
}
return marked, nil
}
@@ -78,11 +78,11 @@ func TestReservation(t *testing.T) {
// The artifacts' digests, then the bound.
for _, a := range arts {
if ok, err := r.Built(ctx, "m", "0.1.23", a, "sha256:"+a); err != nil || !ok {
if ok, err := r.Built(ctx, "m", "0.1.23", a, "sha256:"+a, nil); err != nil || !ok {
t.Fatalf("built %s: %v %v", a, ok, err)
}
}
if ok, _ := r.Built(ctx, "m", "0.1.22", "m:a", "x"); ok {
if ok, _ := r.Built(ctx, "m", "0.1.22", "m:a", "x", nil); ok {
t.Error("a digest was written under a version a newer build superseded")
}
published := func(_, _, a string) bool { return a == "m:a" }
@@ -118,11 +118,12 @@ func TestReservationIsReadAndMarkedPublished(t *testing.T) {
ctx := context.Background()
store := &reserveMem{mem: newMem()}
r := &Reserver{Store: store, ReleasedOf: func(context.Context, string) ([]string, error) { return []string{"1.4.2"}, nil }}
v, err := r.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"}, Source{})
v, err := r.Reserve(ctx, "m", "1.4", []string{"m:a", "m:b"}, Source{Branch: "main", Revision: 41})
if err != nil || v.Version != "1.4.3" {
t.Fatalf("reserved %q (%v), want 1.4.3", v.Version, err)
}
if _, err := r.Built(ctx, "m", "1.4.3", "m:a", "sha256:aa"); err != nil {
deps := []meshbuilds.Dependency{{Kind: 1, Part: "mesh-sdk:go", Version: "0.1.14", Digest: "sha256:dd"}, {Kind: 3, Part: "go-toolchain:program", Version: "go1.27", Digest: "sha256:tt"}}
if _, err := r.Built(ctx, "m", "1.4.3", "m:a", "sha256:aa", deps); err != nil {
t.Fatal(err)
}
rd := Reader{
@@ -142,9 +143,18 @@ func TestReservationIsReadAndMarkedPublished(t *testing.T) {
if got, err := AnswerReserved(rd, "other"); err != nil || len(got["reserved"].([]ReservedVersion)) != 0 {
t.Errorf("builder_reserved of another module = %v (%v), want none", got, err)
}
if marked, err := r.Published(ctx, "v1.4.3", "sha256:aa"); err != nil || len(marked) != 1 {
marked, err := r.Published(ctx, "v1.4.3", "sha256:aa")
if err != nil || len(marked) != 1 {
t.Fatalf("a published of 1.4.3 with a built digest marked %v (%v)", marked, err)
}
// Design 47 G §52, §673, A-57: the package-published of that artifact carries the digest published, the branch and the source
// revision the reservation was made for, and the dependencies its step result named.
at := time.Unix(100, 0)
said := marked[0].PackagePublished("sha256:aa", at)
atUTC := at.UTC()
if wantSaid := (meshbuilds.PackagePublished{V: 1, Artifact: "m:a", At: &atUTC, Branch: "main", Source: 41, Module: "m", Version: "1.4.3", Digest: "sha256:aa", Dependencies: deps}); !reflect.DeepEqual(said, wantSaid) {
t.Errorf("package-published = %+v, want %+v", said, wantSaid)
}
if marked, _ := r.Published(ctx, "1.4.3", "sha256:other"); len(marked) != 0 {
t.Errorf("a published with another digest marked %v", marked)
}