Fill the catalog's versions from every number the builder gave, numbered or published (design 47 G §372, A-350)

The builder numbers fresh versions without publishing them, so a fill from published alone would number again
below what the builder already said, and one version would name two contents. The fill and its kind 1 and 3 edges
take numbered and published alike; the version-form check and the claimed cut stay. A version the builder numbered
without a digest is filled with none.

Builds: design 47 G §372, A-350, A-351

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-13 13:06:53 +01:00
parent ff9f089dd8
commit 8d40d90950
3 changed files with 23 additions and 13 deletions
@@ -17,9 +17,9 @@ import (
)
// The first fill of the versions (design 47 G §372): at the first start under G the state versions is filled once, as
// released, for each module from the highest published version over every artifact of the module that the builder's state
// versions held, read by the builder's tool builder_versions as a kept call; then versions-filled is said once, and the
// builder reserves nothing before it (§369).
// released, for each module from the highest version over every artifact of the module that the builder's state versions
// gave a number, numbered or published (A-350), read by the builder's tool builder_versions as a kept call; then
// versions-filled is said once, and the builder reserves nothing before it (§369).
const (
// BuilderVersionsCall is the tool the manifest names in its invokes (until that tool goes).
BuilderVersionsCall = "mesh-delivery-builder.builder_versions"
@@ -39,6 +39,11 @@ type BuilderVersion struct {
At time.Time `json:"at"`
}
// givenANumber says whether the builder's state versions gave the version its number: numbered, or numbered and then
// published (A-350: the builder numbers fresh versions it has not published, so a fill from published alone would number
// again below what the builder already said).
func givenANumber(state string) bool { return state == "numbered" || state == "published" }
// BuilderVersionsAnswer is what builder_versions answers for a name.
type BuilderVersionsAnswer struct {
Name string `json:"name"`
@@ -90,10 +95,10 @@ func (ix *Indexer) FillVersions(ctx context.Context, ask VersionsAsker) error {
name string
ref string
}
best := map[string][]found1{} // by module: each artifact's highest published version
best := map[string][]found1{} // by module: each artifact's highest numbered or published version (A-350)
var unanswered error
for _, e := range entries {
var top string // the module's highest published version
var top string // the module's highest version the builder gave a number, numbered or published (A-350)
per := []found1{}
for _, a := range e.Artifacts {
name := versionNameOf(e.Module, a)
@@ -106,7 +111,7 @@ func (ix *Indexer) FillVersions(ctx context.Context, ask VersionsAsker) error {
continue
}
for _, v := range ans.Versions {
if _, ok := versionParts(v.Version); !ok || v.State != "published" {
if _, ok := versionParts(v.Version); !ok || !givenANumber(v.State) {
continue
}
per = append(per, found1{v: v, name: name, ref: a.Ref})
@@ -66,7 +66,7 @@ func (ix *Indexer) filledEdges(ctx context.Context, entry meshcatalog.CatalogEnt
}
var best *BuilderVersion
for i, v := range ans.Versions {
if _, ok := versionParts(v.Version); !ok || v.State != "published" || v.At.After(claimed) {
if _, ok := versionParts(v.Version); !ok || !givenANumber(v.State) || v.At.After(claimed) {
continue
}
if best == nil || v.At.After(best.At) {
@@ -87,10 +87,10 @@ func TestAVersionIsReleasedOnceEveryArtifactIsPublished(t *testing.T) {
}
}
// Design 47 G §372: the first fill records, once, each module's highest published version over its artifacts as released
// and says versions-filled once; a builder_versions that does not answer holds it and, after five minutes, is the finding
// Design 47 G §372, A-350: the first fill records, once, each module's highest version the builder gave a number, numbered or
// published, over its artifacts as released and says versions-filled once; a builder_versions that does not answer holds it and, after five minutes, is the finding
// call-unanswered; a fill made is not made again.
func TestTheFirstFillOfTheVersionsComesFromTheBuildersPublished(t *testing.T) {
func TestTheFirstFillOfTheVersionsComesFromTheBuildersNumbers(t *testing.T) {
ctx := context.Background()
now := time.Date(2026, 11, 1, 3, 0, 0, 0, time.UTC)
w := newFindings()
@@ -128,7 +128,8 @@ func TestTheFirstFillOfTheVersionsComesFromTheBuildersPublished(t *testing.T) {
case "@novox/mesh-sdk":
return BuilderVersionsAnswer{Name: name, Versions: []BuilderVersion{pub("0.1.18", "sha256:ts18")}}, nil
case "avahi/image":
return BuilderVersionsAnswer{Name: name, Versions: []BuilderVersion{pub("0.2.3", "sha256:av")}}, nil
// published 0.1.0, numbered up to 0.1.14 and never published (A-350): the fill starts above 0.1.14
return BuilderVersionsAnswer{Name: name, Versions: []BuilderVersion{pub("0.1.0", "sha256:av"), {Version: "0.1.14", State: "numbered", Revision: "9"}}}, nil
}
return BuilderVersionsAnswer{Name: name}, nil
}
@@ -157,6 +158,9 @@ func TestTheFirstFillOfTheVersionsComesFromTheBuildersPublished(t *testing.T) {
if len(sdk) != 1 || sdk[0].Version != "0.1.18" || len(sdk[0].Artifacts) != 1 || sdk[0].Artifacts[0].Artifact != "mesh-sdk:ts" || sdk[0].Source != 9 {
t.Errorf("mesh-sdk's filled version: %+v (the higher of Go's 0.1.14 and npm's 0.1.18)", sdk)
}
if avahi, _ := ModuleVersions(ctx, ix.Versions, "avahi"); len(avahi) != 1 || avahi[0].Version != "0.1.14" || len(avahi[0].Artifacts) != 1 || avahi[0].Artifacts[0].Digest != "" {
t.Errorf("avahi's filled version: %+v (numbered 0.1.14 above published 0.1.0, so the first G build of 0.1 is 0.1.15, with no digest where the builder kept none, A-351)", avahi)
}
if fresh, _ := ModuleVersions(ctx, ix.Versions, "fresh"); len(fresh) != 0 {
t.Errorf("a module with none published is filled: %+v", fresh)
}
@@ -462,7 +466,8 @@ func TestTheFirstFillGivesTheNewestVersionsTheirFilledEdges(t *testing.T) {
}
switch name {
case "x/lib":
return BuilderVersionsAnswer{Versions: []BuilderVersion{pub("0.1.1", "sha256:l1", t0), pub("0.1.2", "sha256:l2", t0.Add(2*time.Hour))}}, nil
// 0.1.1 numbered and not published still counts (A-350)
return BuilderVersionsAnswer{Versions: []BuilderVersion{{Version: "0.1.1", State: "numbered", Digest: "sha256:l1", Revision: "9", At: t0}, pub("0.1.2", "sha256:l2", t0.Add(2*time.Hour))}}, nil
case "app/program":
return BuilderVersionsAnswer{Versions: []BuilderVersion{pub("0.3.1", "sha256:a1", t0.Add(time.Hour))}}, nil
}
@@ -482,7 +487,7 @@ func TestTheFirstFillGivesTheNewestVersionsTheirFilledEdges(t *testing.T) {
switch e.Kind {
case 1:
if e.Version != "0.1.1" || e.Digest != "sha256:l1" || !e.Stale {
t.Errorf("kind 1 is the version published before the build was claimed: %+v", e)
t.Errorf("kind 1 is the version numbered or published before the build was claimed: %+v", e)
}
case 4:
if e.Version != "0.9.1" || e.Digest != "sha256:prov/program" {