J4 (lead's item): the packages state's series value is a contract its readers take, not the indexer's own type
delivery.SeriesPackage and SeriesVersion, field for field as the indexer writes them (name, the ecosystem name; package, <module>/<artifact>), with SeriesPackageKey, IsSeriesPackageKey and a checked encode and decode. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -56,6 +56,34 @@ func TestPublishedPackagesAndImportsRoundTripAndRefuse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The indexer's series value (design 51 *The versions*), as the indexer writes it: read back whole, and refused
|
||||
// where a reader could not take it.
|
||||
written := `{"v":1,"name":"` + sdk + `","package":"mesh-sdk/go","series":"0.1","seat":"go-package-registry","go-module":"` + sdk +
|
||||
`","newest":{"version":"0.1.15","commit":"` + commit + `","digest":"sha256:0f","fingerprint":"fp1","contents":` + string(raw) +
|
||||
`},"numbered":{"version":"0.1.16","commit":"` + commit + `"},"at":"2026-11-04T14:00:00Z"}`
|
||||
series, err := DecodeSeriesPackage([]byte(written))
|
||||
if err != nil || series.Name != sdk || series.Package != "mesh-sdk/go" || series.Newest.Contents == nil || series.Numbered.Version != "0.1.16" {
|
||||
t.Fatalf("a series value as the indexer writes it reads %+v, %v", series, err)
|
||||
}
|
||||
if again, err := EncodeSeriesPackage(series); err != nil || string(again) != written {
|
||||
t.Fatalf("a series value writes back as\n%s, %v, not\n%s", again, err, written)
|
||||
}
|
||||
if key := SeriesPackageKey(sdk, "0.1"); key != "git=2enovox=2ebe/novox/mesh-sdk/go.0.1" || !IsSeriesPackageKey(key) || IsSeriesPackageKey(EscapeName(sdk)) {
|
||||
t.Fatalf("the series key %q is not told apart from a per-module key", key)
|
||||
}
|
||||
for name, mutate := range map[string]func(s *SeriesPackage){
|
||||
"a series with no name": func(s *SeriesPackage) { s.Name = "" },
|
||||
"a series that is no series": func(s *SeriesPackage) { s.Series = "0.1.15" },
|
||||
"built from no artifact": func(s *SeriesPackage) { s.Package = "mesh-sdk" },
|
||||
"a numbered version of nothing": func(s *SeriesPackage) { s.Numbered = &SeriesVersion{} },
|
||||
} {
|
||||
s := series
|
||||
mutate(&s)
|
||||
if _, err := EncodeSeriesPackage(s); err == nil {
|
||||
t.Errorf("%s: encoded", name)
|
||||
}
|
||||
}
|
||||
|
||||
imports := []string{sdk, sdk + "/announce"}
|
||||
outOfStep := []string{"go/seats/meshbroker/contract.go", "ts/seats/meshbroker/src/index.ts"}
|
||||
end := BuildLogEntry{Event: BuildEndedEvent, Module: "netcheck", FP: "src2:x", Repository: "novox/mesh-catalog", Commit: commit,
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The packages state per series (design 51 *The versions*, design 32; ADR 0606 §1, ADR 0609 §1): beside the per-module
|
||||
// values (PublishedPackages), the indexer keeps one key per package and series, `<name>.<series>` (SeriesPackageKey),
|
||||
// holding the newest trunk version of that series the builder numbered and the newest a registry seat published. The
|
||||
// indexer alone writes it; its readers (the builder, the checker, the build modules) take this value's form from here.
|
||||
// A series key always holds a dot, which EscapeName never leaves in a per-module key (IsSeriesPackageKey).
|
||||
|
||||
// SeriesPackage is the value of a series key. Name is the package's name in its own ecosystem (a Go package's module
|
||||
// path in its go.mod at the commit, an npm package's package.json name): the key is named by it, and a user's
|
||||
// `packages` names it. Package is the module and artifact it is built from, `<module>/<artifact>`, by which the builder
|
||||
// finds the name to number under; Series its `<major>.<minor>`. Seat is the registry seat it is published through;
|
||||
// GoModule the Go module path a Go package is fetched under, empty for any other. Newest is the newest version of the
|
||||
// series a seat published (what a user accepting the series builds against), Numbered the newest the builder numbered,
|
||||
// published or not yet.
|
||||
type SeriesPackage struct {
|
||||
V int `json:"v"`
|
||||
Name string `json:"name"`
|
||||
Package string `json:"package,omitempty"`
|
||||
Series string `json:"series"`
|
||||
Seat string `json:"seat,omitempty"`
|
||||
GoModule string `json:"go-module,omitempty"`
|
||||
Newest *SeriesVersion `json:"newest,omitempty"`
|
||||
Numbered *SeriesVersion `json:"numbered,omitempty"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// SeriesVersion is one version of a series: its number, the source commit it was built from, the digest and
|
||||
// fingerprint. Contents are, for a series' newest of a Go package, its Go module's requirements' and packages' digests
|
||||
// at Commit: what a user accepting the series is fingerprinted against; nil when the forge could not list it, and on a
|
||||
// numbered version.
|
||||
type SeriesVersion struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
Digest string `json:"digest,omitempty"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
Contents *PublishedPackages `json:"contents,omitempty"`
|
||||
}
|
||||
|
||||
// SeriesPackageKey is the key of a package's series in the packages state: its ecosystem name as a key part and the
|
||||
// series.
|
||||
func SeriesPackageKey(name, series string) string { return EscapeName(name) + "." + series }
|
||||
|
||||
// IsSeriesPackageKey says whether a key of the packages state is a series key, not a per-module one.
|
||||
func IsSeriesPackageKey(key string) bool { return strings.Contains(key, ".") }
|
||||
|
||||
// builtFrom says whether pkg is `<module>/<artifact>`: a module name (no slash) and an artifact named after it.
|
||||
func builtFrom(pkg string) bool {
|
||||
module, artifact, ok := strings.Cut(pkg, "/")
|
||||
return ok && module != "" && artifact != ""
|
||||
}
|
||||
|
||||
var seriesForm = regexp.MustCompile(`^[0-9]+\.[0-9]+$`)
|
||||
|
||||
func (s SeriesPackage) check() error {
|
||||
switch {
|
||||
case s.Name == "":
|
||||
return fmt.Errorf("delivery: a series of %q names no package in its ecosystem", s.Package)
|
||||
case !seriesForm.MatchString(s.Series):
|
||||
return fmt.Errorf("delivery: the series %q of %s is no <major>.<minor>", s.Series, s.Name)
|
||||
case s.Package != "" && !builtFrom(s.Package):
|
||||
return fmt.Errorf("delivery: the series %s of %s is built from %q, which is no <module>/<artifact>", s.Series, s.Name, s.Package)
|
||||
}
|
||||
for which, v := range map[string]*SeriesVersion{"newest": s.Newest, "numbered": s.Numbered} {
|
||||
if v != nil && v.Version == "" {
|
||||
return fmt.Errorf("delivery: the %s version of %s %s names no version", which, s.Name, s.Series)
|
||||
}
|
||||
if v != nil && v.Contents != nil {
|
||||
if err := v.Contents.check(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeSeriesPackage is s as JSON at Version; it refuses what a reader could not take.
|
||||
func EncodeSeriesPackage(s SeriesPackage) ([]byte, error) {
|
||||
if err := s.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.V = Version
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
// DecodeSeriesPackage reads a series key's value; it refuses an unknown version (ErrUnknownVersion) and what
|
||||
// EncodeSeriesPackage refuses.
|
||||
func DecodeSeriesPackage(data []byte) (SeriesPackage, error) {
|
||||
var s SeriesPackage
|
||||
if err := decode(data, &s); err != nil {
|
||||
return SeriesPackage{}, err
|
||||
}
|
||||
if err := s.check(); err != nil {
|
||||
return SeriesPackage{}, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
Reference in New Issue
Block a user