A seat that cannot remove a tag alone (another tag names its digest) now answers and says kept. The builder ends that remove there, with no retry, and marks the version kept on its store with the reason and the other tags; builder_versions shows it, and a later branch end asks it no more. The four seats' kept are consumed. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
112 lines
4.4 KiB
Go
112 lines
4.4 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, or kept: its seat kept it whole on remove, its digest shared, with the reason and the other tags naming it, kept_with) 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."
|