// Package versions is the builder's state `versions` (novox/hq design 51 *The versions*, ADR 0606): one key per name, // tag and version, written only by the builder under compare-and-set, read by others only through the builder's tool // builder_versions or its events (ADR 0596 §2). It holds what each version is (its fingerprint, commit, source revision // and digest) and where it stands: numbered, published, refused with its reason, or removed with its time, reason and the // event that removed it. // // The state is declared in the builder's old `state` section (ADR 0619: the builder is one of the foundation's nine // parts), history one; its cap is 64 MiB, the most the template state allows (ADR 0595 §3), and past 80 % of it the // builder writes a finding, cleared below it (Fill). package versions import ( "encoding/json" "errors" "fmt" "sort" "strconv" "strings" "time" ) // State is the builder state's name; Bucket the bucket the bus keeps it in. const ( State = "versions" Bucket = "mesh-delivery-builder_versions" // Cap is the bucket's cap in bytes (64 MiB); FullPercent the share of it past which the builder writes its finding. Cap = 64 << 20 FullPercent = 80 // DefaultSeries is the series of a name whose manifest declares none (design 32). DefaultSeries = "0.1" ) // The states a version is in. const ( Numbered = "numbered" Published = "published" Refused = "refused" Removed = "removed" // Kept is a tagged version its seat kept whole on remove: another tag names its digest, so the store cannot remove the // tag alone (review round 2; the registry seats' `kept`). Its Reason is the seat's (shared-digest), KeptWith the // other tags; it is asked no more. Kept = "kept" ) // The kinds the builder numbers: a package per source revision, the rest per fingerprint (design 51). const ( KindPackage = "package" KindImage = "image" KindBundle = "bundle" KindArchive = "archive" ) // Entry is one key of the state: a version of a name, tagged or not. type Entry struct { Name string `json:"name"` Kind string `json:"kind"` Series string `json:"series"` Version string `json:"version"` // Tag is the cleaned tag of a tagged version (`feat-x`); empty for a trunk version. Tag string `json:"tag,omitempty"` Module string `json:"module"` Fingerprint string `json:"fingerprint"` Commit string `json:"commit,omitempty"` // Revision is the source revision a package's version is numbered for (one version per revision, design 51). Revision string `json:"revision,omitempty"` // Digest is the digest the build recorded (an image's manifest, a blob's, Go's h1: hash). Digest string `json:"digest,omitempty"` // Seat is the registry seat it is published to. Seat string `json:"seat,omitempty"` State string `json:"state"` At time.Time `json:"at"` // Reason is why it was refused or removed: the holder's reason, a branch's end, retention, the sweep. Reason string `json:"reason,omitempty"` // Repository is a tagged version's repository: its branch's end removes it (Untagged). Repository string `json:"repository,omitempty"` // KeptWith are the other tags naming its digest, when its seat kept it (Kept). KeptWith []string `json:"kept_with,omitempty"` // RemovedBy is the id of the event that removed it. RemovedBy string `json:"removed_by,omitempty"` // RefusedDigest is the digest the holder refused: one other than Digest means the seat was offered other bytes under // this number, which builder_publish_again refuses to publish again (design 51 *Tools of the builder*). RefusedDigest string `json:"refused_digest,omitempty"` // Merge is the forge bridge's sequence of the merge that numbered it, for merge order. Merge uint64 `json:"merge,omitempty"` } // Valid says what is wrong with an entry, or nil. func (e Entry) Valid() error { switch { case strings.TrimSpace(e.Name) == "": return errors.New("a version names no name") case e.Version == "": return fmt.Errorf("%s: a version with no version", e.Name) } switch e.State { case Numbered, Published, Refused, Removed, Kept: default: return fmt.Errorf("%s@%s: unknown state %q", e.Name, e.Version, e.State) } switch e.Kind { case KindPackage, KindImage, KindBundle, KindArchive: default: return fmt.Errorf("%s@%s: unknown kind %q", e.Name, e.Version, e.Kind) } if _, _, err := SplitSeries(e.Series); err != nil { return fmt.Errorf("%s@%s: %w", e.Name, e.Version, err) } return nil } // Encode and Decode are the value's bytes. func Encode(e Entry) ([]byte, error) { if err := e.Valid(); err != nil { return nil, err } return json.Marshal(e) } func Decode(raw []byte) (Entry, error) { var e Entry if err := json.Unmarshal(raw, &e); err != nil { return Entry{}, err } return e, e.Valid() } // Key is the state's key of a name's version: two tokens, the name and the version each escaped (EscapeToken), so a // name's versions are the keys under NameFilter and no name's keys fall under another's. func Key(name, version string) string { return EscapeToken(name) + "." + EscapeToken(version) } // NameFilter is the key filter of every version of name. func NameFilter(name string) string { return EscapeToken(name) + ".*" } // EscapeToken makes s one token of a bus key: letters, digits, '-', '_' and '/' stay; '=' and every other byte become // '=' and two hex digits ('.' is "=2E", '@' "=40"). Two different strings never escape alike. func EscapeToken(s string) string { var b strings.Builder for i := 0; i < len(s); i++ { c := s[i] switch { case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9', c == '-', c == '_', c == '/': b.WriteByte(c) default: fmt.Fprintf(&b, "=%02X", c) } } return b.String() } // SplitSeries reads a series `.`. func SplitSeries(series string) (major, minor int, err error) { a, b, ok := strings.Cut(series, ".") if !ok { return 0, 0, fmt.Errorf("series %q is not .", series) } if major, err = strconv.Atoi(a); err != nil || major < 0 { return 0, 0, fmt.Errorf("series %q is not .", series) } if minor, err = strconv.Atoi(b); err != nil || minor < 0 { return 0, 0, fmt.Errorf("series %q is not .", series) } return major, minor, nil } // Patch is the count of a trunk version of series (`0.1.14` → 14), read leniently: a leading 'v' and a path prefix // (`go/v0.1.13`) are dropped; ok is false for a version of another series or a tagged one. func Patch(series, version string) (int, bool) { if i := strings.LastIndex(version, "/"); i >= 0 { version = version[i+1:] } version = strings.TrimPrefix(version, "v") if strings.ContainsAny(version, "-+") { return 0, false } rest, ok := strings.CutPrefix(version, series+".") if !ok { return 0, false } n, err := strconv.Atoi(rest) if err != nil || n < 0 { return 0, false } return n, true } // Floor is what the three sources answered for a name (design 51 *The floor*, ADR 0606 §3): the seat's versions, the // package's git tags (the forge bridge's bridge_tags) and the pseudo-versions consumers require (the indexer's // indexer_requirements). A source not answered is listed in Silent. type Floor struct { Seat []string `json:"seat,omitempty"` Tags []string `json:"tags,omitempty"` Requirements []string `json:"requirements,omitempty"` // Silent names the sources that did not answer: "seat", "bridge_tags", "indexer_requirements". Silent []string `json:"silent,omitempty"` } // The floor's sources, as Floor.Silent names them. const ( SourceSeat = "seat" SourceTags = "bridge_tags" SourceRequirements = "indexer_requirements" ) // Known is whether every source answered. func (f Floor) Known() bool { return len(f.Silent) == 0 } // Highest is the highest count of series the floor's three sources name, -1 when none names one. A pseudo-version // (`v0.1.14-0.2026…-abc`) counts as the version below it was cut from, so its base (`0.1.13`) counts: Go orders it // before `0.1.14`. func (f Floor) Highest(series string) int { high := -1 for _, list := range [][]string{f.Seat, f.Tags, f.Requirements} { for _, v := range list { n, ok := Patch(series, v) if !ok { n, ok = pseudoBase(series, v) } if ok && n > high { high = n } } } return high } // pseudoBase is the count a Go pseudo-version stands on: `v0.1.14-0.