From 09cf1ea6fc7b706b35dbe7740db9e7218a64a0f1 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 14:54:33 +0100 Subject: [PATCH] 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, /), with SeriesPackageKey, IsSeriesPackageKey and a checked encode and decode. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- go/delivery/published_packages_test.go | 28 +++++++ go/delivery/series_package.go | 105 +++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 go/delivery/series_package.go diff --git a/go/delivery/published_packages_test.go b/go/delivery/published_packages_test.go index a971dc9..6ae85b6 100644 --- a/go/delivery/published_packages_test.go +++ b/go/delivery/published_packages_test.go @@ -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, diff --git a/go/delivery/series_package.go b/go/delivery/series_package.go new file mode 100644 index 0000000..98ad84a --- /dev/null +++ b/go/delivery/series_package.go @@ -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, `.` (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, `/`, by which the builder +// finds the name to number under; Series its `.`. 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 `/`: 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 .", 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 /", 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 +}