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.
This commit is contained in:
2026-10-22 17:59:55 +02:00
parent ab2ca6e3a7
commit 4dd967e62f
3 changed files with 26 additions and 11 deletions
+6 -7
View File
@@ -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 {
+17
View File
@@ -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)
}
}
+3 -4
View File
@@ -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")
}
}