From 4dd967e62f490143275a6782fdd3c505611e36bb Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 17:59:55 +0200 Subject: [PATCH] Let a module that builds no artifact have a done key that can be delivered (issue 1613, the lead's request for the builder lane) A done key needs its manifest digest, no longer an artifact; Referenced answers ok for it, since no artifact lacks a reference. --- go/delivery/builder.go | 13 ++++++------- go/delivery/builder_reference_test.go | 17 +++++++++++++++++ go/delivery/builder_test.go | 7 +++---- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/go/delivery/builder.go b/go/delivery/builder.go index fd414b8..51d6d17 100644 --- a/go/delivery/builder.go +++ b/go/delivery/builder.go @@ -125,14 +125,15 @@ func (a BuiltArtifact) referenceProblem() string { } // Referenced says whether b is done and every artifact names where it is, which the controller delivers from (novox/hq -// ADR 0498 clause 8), and names the artifacts that name no reference. +// ADR 0498 clause 8), and names the artifacts that name no reference. A done key with no artifact (a module that builds +// none, issue 1613) is referenced. func (b BuilderBuild) Referenced() (ok bool, missing []string) { for _, a := range b.Artifacts { if a.Reference == "" { missing = append(missing, a.Name) } } - return b.State == BuildDone && len(b.Artifacts) > 0 && len(missing) == 0, missing + return b.State == BuildDone && len(missing) == 0, missing } // The prefixes of a failed key's error that a build module answers (novox/hq ADR 0498 clauses 2, 4 and 5; ADR 0478 @@ -226,9 +227,7 @@ func (b BuilderBuild) check() error { return fmt.Errorf("delivery: a %s build carries no artifact and no error", b.State) } case BuildDone: - if len(b.Artifacts) == 0 { - return fmt.Errorf("delivery: a done build names no artifact") - } + // A module that builds no artifact gets a done key too, and is delivered from it (issue 1613). if b.Manifest == "" { return fmt.Errorf("delivery: a done build names no manifest digest") } @@ -243,8 +242,8 @@ func (b BuilderBuild) check() error { return nil } -// EncodeBuilderBuild is b as JSON at Version; it refuses a build its state does not allow (done without artifacts or a -// manifest digest, failed without an error, an artifact without a name or digest, a toolchain without a version or a +// EncodeBuilderBuild is b as JSON at Version; it refuses a build its state does not allow (done without a manifest +// digest, failed without an error, an artifact without a name or digest, a toolchain without a version or a // recipe version). func EncodeBuilderBuild(b BuilderBuild) ([]byte, error) { if err := b.check(); err != nil { diff --git a/go/delivery/builder_reference_test.go b/go/delivery/builder_reference_test.go index b8f804a..cbe206d 100644 --- a/go/delivery/builder_reference_test.go +++ b/go/delivery/builder_reference_test.go @@ -116,3 +116,20 @@ func TestAKeyWaitsQueuedForAPackageVersion(t *testing.T) { t.Fatal("a wait naming no version was accepted") } } + +// A module that builds no artifact gets a done key too (issue 1613), and it is delivered: Referenced is ok for a done +// key with no artifact, since no artifact lacks a reference; a queued one with none still is not. +func TestADoneKeyWithNoArtifactIsReferenced(t *testing.T) { + b := BuilderBuild{State: BuildDone, Manifest: digestOf('4')} + data, err := EncodeBuilderBuild(b) + if err != nil { + t.Fatalf("a done key with no artifact was refused: %v", err) + } + back, err := DecodeBuilderBuild(data) + if err != nil { + t.Fatal(err) + } + if ok, missing := back.Referenced(); !ok || len(missing) != 0 { + t.Fatalf("Referenced = %v, %v; want true", ok, missing) + } +} diff --git a/go/delivery/builder_test.go b/go/delivery/builder_test.go index ebe3003..cdcb8f5 100644 --- a/go/delivery/builder_test.go +++ b/go/delivery/builder_test.go @@ -83,14 +83,13 @@ func TestBuilderBuildRoundTrip(t *testing.T) { } } -// 1358/AC3: done without artifacts or without a manifest digest, failed without an error, an artifact without a digest +// 1358/AC3: done without a manifest digest, failed without an error, an artifact without a digest // and an unknown state are refused. func TestBuilderBuildChecked(t *testing.T) { cases := map[string]struct { edit func(*BuilderBuild) words []string }{ - "done without artifacts": {func(b *BuilderBuild) { b.Artifacts = nil }, []string{"artifact"}}, "done without a manifest": {func(b *BuilderBuild) { b.Manifest = "" }, []string{"manifest"}}, "a malformed manifest": {func(b *BuilderBuild) { b.Manifest = "abc" }, []string{"abc"}}, "an artifact without a digest": {func(b *BuilderBuild) { b.Artifacts[1].Digest = "" }, []string{"digest", "server"}}, @@ -125,8 +124,8 @@ func TestBuilderBuildChecked(t *testing.T) { t.Error("a queued build with artifacts accepted") } // decode refuses what encode refuses - if _, err := DecodeBuilderBuild([]byte(`{"v":1,"state":"done","manifest":"` + digestOf('4') + `"}`)); err == nil { - t.Error("decoded a done build without artifacts") + if _, err := DecodeBuilderBuild([]byte(`{"v":1,"state":"done"}`)); err == nil { + t.Error("decoded a done build without a manifest digest") } }