Files
mesh-delivery/modules/mesh-delivery-builder/internal/versions/tools.go
T
jschoubben 7ae4f2bb7f Join item 6: the builder says version.numbered as delivery.VersionNumbered and reads out-of-step by BuildAnswer's field
versions.NumberedBody and NumberedEvent are mesh-sdk's delivery.VersionNumbered and VersionNumberedEvent now (J3), the
contract a build module consumes for BG2's late publish; the body's JSON is unchanged. The lane reads a step's
out-of-step files through delivery.BuildAnswer.OutOfStep, a failed step's result too (as before, A2k's d15d66fc: read
before the step's state; its three rows hold it), in place of a local decode by name. The integration test hears
version.numbered on delivery.VersionNumberedSubject and reads it with DecodeVersionNumbered, as a build module does.

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

112 lines
4.3 KiB
Go

package versions
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The builder's tools of its versions (design 51 *Events of the builder*, *Tools of the builder*).
const (
VersionsTool = "builder_versions"
PublishAgainTool = "builder_publish_again"
FloorAgainTool = "builder_floor_again"
TagTool = "builder_tag"
TagAsksTool = "builder_tag_asks"
UntagTool = "builder_untag"
OldFormTool = "builder_old_form"
// OldFormClosedEvent is, with delivery.VersionNumberedEvent, the builder's own family `version`, kept seven days,
// apart from its builds' ends, so a waiter on a build's end does not wake on them.
OldFormClosedEvent = "version.old-form-closed"
)
// NumberedOf is the version.numbered body of an entry (delivery.VersionNumbered, the contract a build module reads).
func NumberedOf(e Entry, head string) delivery.VersionNumbered {
return delivery.VersionNumbered{Name: e.Name, Kind: e.Kind, Version: e.Version, Tag: e.Tag, Module: e.Module, Fingerprint: e.Fingerprint,
Commit: e.Commit, Digest: e.Digest, Seat: e.Seat, Head: head}
}
// Reader reads the state as the tools bundle may: every key, and one.
type Reader struct {
Keys func() ([]string, error)
Get func(key string) (json.RawMessage, bool, error)
}
// readStore is a Store over a Reader that refuses every write: the tools bundle only reads versions.
type readStore struct{ r Reader }
var errReadOnly = errors.New("the versions are written by the builder alone")
func (s readStore) Get(_ context.Context, key string) ([]byte, uint64, bool, error) {
v, ok, err := s.r.Get(key)
return v, 0, ok, err
}
func (readStore) Create(context.Context, string, []byte) (uint64, error) { return 0, errReadOnly }
func (readStore) Update(context.Context, string, []byte, uint64) (uint64, error) {
return 0, errReadOnly
}
func (s readStore) KeysOf(_ context.Context, filter string) ([]string, error) {
all, err := s.r.Keys()
if err != nil {
return nil, err
}
if filter == AllFilter {
return all, nil
}
prefix := strings.TrimSuffix(filter, "*")
var out []string
for _, k := range all {
if strings.HasPrefix(k, prefix) && !strings.Contains(k[len(prefix):], ".") {
out = append(out, k)
}
}
return out, nil
}
// VersionsAnswer is what builder_versions answers.
type VersionsAnswer struct {
Name string `json:"name"`
Versions []Entry `json:"versions"`
// Asked, Recorded and Why answer a holder's question for one version and digest (ADR 0605 §4).
Asked string `json:"asked,omitempty"`
Recorded *bool `json:"recorded,omitempty"`
Why string `json:"why,omitempty"`
}
// AnswerVersions is builder_versions: a name's versions in order, in one call; with a version (and a digest), whether
// the builder numbered it with that digest, which the holder asks before it stores (ADR 0605 §4).
func AnswerVersions(ctx context.Context, r Reader, name, version, digest string) (VersionsAnswer, error) {
name = strings.TrimSpace(name)
if name == "" {
return VersionsAnswer{}, errors.New("name is required: the package, image, bundle or archive name")
}
v := Versions{Store: readStore{r}}
all, err := v.Of(ctx, name)
if err != nil {
return VersionsAnswer{}, err
}
out := VersionsAnswer{Name: name, Versions: all}
if out.Versions == nil {
out.Versions = []Entry{}
}
if version = strings.TrimSpace(version); version != "" {
_, ok, why, err := v.Recorded(ctx, name, version, strings.TrimSpace(digest))
if err != nil {
return VersionsAnswer{}, fmt.Errorf("%s@%s: %w", name, version, err)
}
out.Asked, out.Recorded, out.Why = version, &ok, why
}
return out, nil
}
// VersionsDescription is builder_versions' description.
const VersionsDescription = "A name's versions as the builder numbered them (its state versions, design 51): every trunk and tagged " +
"version of a package, image, bundle or archive in order, each with its kind, series, fingerprint, commit, source revision, digest, " +
"seat, state (numbered, published, refused with its reason, removed with its time, reason and the event that removed it) and when. " +
"With version, and digest, it also answers recorded: whether the builder numbered that version with that digest, and why not: the " +
"question a registry seat's holder asks before it stores a publish (ADR 0605 §4). Read-only."