diff --git a/modules/mesh-delivery-builder/internal/build/run_test.go b/modules/mesh-delivery-builder/internal/build/run_test.go index 5558afeb..8b109e60 100644 --- a/modules/mesh-delivery-builder/internal/build/run_test.go +++ b/modules/mesh-delivery-builder/internal/build/run_test.go @@ -1342,6 +1342,44 @@ func aDoneKeyKeepsEachFormACountedNodeReads(t *testing.T) { if got := fs.Needs(withBoth); !slices.Equal(got, []string{"form:new", "form:old"}) { t.Errorf("needs %v", got) } + // The builder's own forms (forms.Current): a bundle a build step answers in the registry seat's form (mesh-build-go's + // oci-artifact-registry//@) keeps the artifact-store form it derives for each counted node + // whose engine does not state form:seat-reference, that one in the single reference (ADR 0624 §1, §3). + seatRef, storeRef := "oci-artifact-registry/m/code@"+digest, "artifact-store://m/code/blobs/"+digest + seat := []string{forms.FormNeed(forms.FormSeatReference), "word:module"} + for _, row := range []struct { + name string + present, counted map[string][]string + reference string + references []string + needs []string + }{ + {"seat form answered, B away stating nothing: the derived store form stays, in the single reference", + map[string][]string{"a": seat}, map[string][]string{"a": seat, "b": nil}, storeRef, []string{storeRef, seatRef}, + []string{"form:artifact-store", "form:seat-reference"}}, + {"seat form answered, every counted engine reads it: the seat form only", + map[string][]string{"a": seat}, map[string][]string{"a": seat, "b": seat}, seatRef, nil, []string{"form:seat-reference"}}, + {"seat form answered, a present engine stating nothing: the store form only", + map[string][]string{"a": seat, "b": nil}, map[string][]string{"a": seat, "b": nil}, storeRef, nil, []string{"form:artifact-store"}}, + {"seat form answered, no node present: the store form", nil, map[string][]string{"a": seat}, storeRef, nil, []string{"form:artifact-store"}}, + } { + a := forms.Current.Choose(delivery.BuiltArtifact{Name: "code", Kind: "bundle", Digest: digest}, []string{seatRef}, row.present, row.counted) + if a.Reference != row.reference || !slices.Equal(a.References, row.references) { + t.Errorf("%s: %q %v, want %q %v", row.name, a.Reference, a.References, row.reference, row.references) + } + needs := forms.Current.Needs([]delivery.BuiltArtifact{a}) + if !slices.Equal(needs, row.needs) { + t.Errorf("%s: needs %v, want %v", row.name, needs, row.needs) + } + for node, stated := range row.counted { + if forms.Current.Held(needs, stated) { + t.Errorf("%s: the want of %s is held", row.name, node) + } + } + if _, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: strings.TrimPrefix(digest, "sha256:"), Artifacts: []delivery.BuiltArtifact{a}, Needs: needs}); err != nil { + t.Errorf("%s: %v", row.name, err) + } + } // What the assigner holds a want by (ADR 0624 §2), from the same forms. for _, row := range []struct { name string diff --git a/modules/mesh-delivery-builder/internal/versions/versions_test.go b/modules/mesh-delivery-builder/internal/versions/versions_test.go index 5cc345aa..f138f16e 100644 --- a/modules/mesh-delivery-builder/internal/versions/versions_test.go +++ b/modules/mesh-delivery-builder/internal/versions/versions_test.go @@ -308,7 +308,7 @@ func TestTheBuildersVersions(t *testing.T) { } }) t.Run("old form", func(t *testing.T) { - // One kind, a first form and a newer one (the builder's own forms are all first today, so no window opens). + // One kind, a first form and a newer one. fs := forms.ArtifactForms{{Name: "registry", First: true, Kinds: []string{"image"}}, {Name: "oci", Kinds: []string{"image"}}} both := []string{forms.FormNeed("oci")} for _, c := range []struct { @@ -327,8 +327,10 @@ func TestTheBuildersVersions(t *testing.T) { t.Errorf("%s: Holding = %v, want %v", c.name, got, c.want) } } - if got := Holding(forms.Current, map[string][]string{"a": nil}, nil); got != nil { - t.Errorf("forms.Current, a node stating nothing: Holding = %v, want none", got) + // The builder's own forms: the registry seat's form of a bundle is not a first form, so a node stating nothing + // holds the window and one stating form:seat-reference does not. + if got := Holding(forms.Current, map[string][]string{"a": nil, "b": {forms.FormNeed(forms.FormSeatReference)}}, nil); !reflect.DeepEqual(got, []string{"a"}) { + t.Errorf("forms.Current, a node stating nothing and one stating form:seat-reference: Holding = %v, want a", got) } t0 := time.Unix(0, 0) w, closed, overdue := OldFormWindow{}.Step([]string{"c"}, t0) diff --git a/sdk/forms/forms.go b/sdk/forms/forms.go index 298b27fd..579e9cf8 100644 --- a/sdk/forms/forms.go +++ b/sdk/forms/forms.go @@ -32,20 +32,28 @@ const ( FormPackageVersion = "package-version" ) -// ArtifactForm is one reference form: its name, whether it is a first form, the artifact kinds it names, and how a -// reference is told to be in it. +// FormSeatReference is the form a registry seat answers for a bundle or an archive it published, +// oci-artifact-registry//@sha256: (novox/hq ADR 0605 §9; catalogue.SeatReference, read back by +// catalogue.InRegistrySeat), read by a node-engine that states form:seat-reference (catalogue.ReferenceFormSeat). Not a +// first form: an engine stating nothing reads its blob only in the older artifact-store form, which it derives (Older). +const FormSeatReference = catalogue.ReferenceFormSeat + +// ArtifactForm is one reference form: its name, whether it is a first form, the artifact kinds it names, how a +// reference is told to be in it, and, for a form that names the same artifact an older form does, that older reference +// (Older, nil when it derives none): the old-form window writes it beside the answered one (ADR 0624 §1, §3). type ArtifactForm struct { Name string First bool Kinds []string Is func(reference string) bool + Older func(reference string) string } // ArtifactForms are the forms a builder knows, oldest first. type ArtifactForms []ArtifactForm // Current are the forms the builder writes and the assigner knows: the three first forms (delivery.BuiltArtifact's -// reference by kind). +// reference by kind), then the registry seat's form of a bundle or an archive. var Current = ArtifactForms{ {Name: FormArtifactStore, First: true, Is: func(r string) bool { return strings.HasPrefix(r, artifactStore) }, Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive, catalogue.ArtifactImage, catalogue.ArtifactUpstream}}, @@ -53,6 +61,16 @@ var Current = ArtifactForms{ Kinds: []string{catalogue.ArtifactImage, catalogue.ArtifactUpstream}}, {Name: FormPackageVersion, First: true, Is: func(r string) bool { return strings.LastIndex(r, "@") > 0 && !strings.Contains(r, "@sha256:") }, Kinds: []string{catalogue.ArtifactPackage}}, + {Name: FormSeatReference, Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive}, + Is: func(r string) bool { + seat, _, ok := catalogue.InRegistrySeat(r) + return ok && seat == catalogue.OciArtifactRegistrySeat + }, + // The same blob in the artifact store's form: oci-artifact-registry//@ is artifact-store:////blobs/. + Older: func(r string) string { + path, _ := catalogue.InArtifactStore(r) + return catalogue.ArtifactStoreScheme + path + }}, } // FormNeed is the reader capability a form needs of a node-engine: `form:`. @@ -180,9 +198,12 @@ func (fs ArtifactForms) Kept(kind string, present, counted map[string][]string) // Choose names a built artifact by the references the build answered in the forms Kept keeps (ADR 0624 §1, §3): the // oldest kept form in the single Reference, every kept one in References when there are more than one. A reference in a // form Kept does not keep is left out, unless none of the answered is kept: then the artifact keeps what was answered, -// its first answered in Reference, since a key names where its artifact is (ADR 0498 clause 1). +// its first answered in Reference, since a key names where its artifact is (ADR 0498 clause 1). An answered reference +// in a form that derives an older one (ArtifactForm.Older) answers that older one too, so the build answers one form and +// the key keeps each a counted node reads. func (fs ArtifactForms) Choose(a delivery.BuiltArtifact, answered []string, present, counted map[string][]string) delivery.BuiltArtifact { kept := fs.Kept(a.Kind, present, counted) + answered = fs.withOlder(a.Kind, answered) type ref struct { at int reference string @@ -224,3 +245,22 @@ func (fs ArtifactForms) Choose(a delivery.BuiltArtifact, answered []string, pres } return a } + +// withOlder is answered with, after it, each older reference an answered one's form derives that was not answered itself. +func (fs ArtifactForms) withOlder(kind string, answered []string) []string { + out := slices.Clone(answered) + for _, r := range answered { + name, ok := fs.FormOf(kind, r) + if !ok { + continue + } + for _, f := range fs { + if f.Name == name && f.Older != nil { + if older := f.Older(r); !slices.Contains(out, older) { + out = append(out, older) + } + } + } + } + return out +}