Record the inputs of a build's source fingerprint in its build record (issue 1215)

This commit is contained in:
2026-10-19 11:54:21 +02:00
parent 8da2619920
commit 68088ec782
13 changed files with 204 additions and 16 deletions
+3
View File
@@ -5,6 +5,7 @@ import (
"strings"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-controller/internal/chain"
)
@@ -51,6 +52,8 @@ type Build struct {
// SourceFingerprint is what the build was made from, hashed, as its builder said it (novox/hq
// issue 280); empty from a builder that predates it, or where the source does not pin the build.
SourceFingerprint string
// SourceInputs are what it was computed from (novox/hq ADR 0437 §5); nil from a build recorded before they were.
SourceInputs *delivery.SourceInputs
// Failed is the builder's own words, empty when it worked.
Failed string
Made []Artifact
+2 -2
View File
@@ -23,7 +23,7 @@ import (
// toChainBuild is a build as the bucket keeps it.
func toChainBuild(b Build) chain.Build {
c := chain.Build{ID: b.ID, Repository: b.Repository, Ref: b.Ref, Module: b.Module, Commit: b.Commit, On: b.On,
Path: b.Path, Against: b.Against, Mirrored: b.Mirrored, SourceFingerprint: b.SourceFingerprint,
Path: b.Path, Against: b.Against, Mirrored: b.Mirrored, SourceFingerprint: b.SourceFingerprint, SourceInputs: b.SourceInputs,
ManifestDigest: b.ManifestDigest, Failed: b.Failed, Asked: b.Asked.UTC(), At: b.At.UTC(), ReuseKey: b.ReuseKey, Published: b.Published}
if len(b.Manifest) > 0 {
c.Manifest = json.RawMessage(b.Manifest)
@@ -46,7 +46,7 @@ func toChainBuild(b Build) chain.Build {
// fromChainBuild is a build as the mesh reads it.
func fromChainBuild(c chain.Build) Build {
b := Build{ID: c.ID, Repository: c.Repository, Ref: c.Ref, Module: c.Module, Commit: c.Commit, On: c.On, Path: c.Path,
Manifest: []byte(c.Manifest), ManifestDigest: c.ManifestDigest, Against: c.Against, Mirrored: c.Mirrored, SourceFingerprint: c.SourceFingerprint,
Manifest: []byte(c.Manifest), ManifestDigest: c.ManifestDigest, Against: c.Against, Mirrored: c.Mirrored, SourceFingerprint: c.SourceFingerprint, SourceInputs: c.SourceInputs,
Failed: c.Failed, Asked: c.Asked, At: c.At, ReuseKey: c.ReuseKey, Published: c.Published}
if len(b.Manifest) == 0 {
b.Manifest = nil
+54
View File
@@ -5,9 +5,11 @@ import (
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/chain"
@@ -129,3 +131,55 @@ func TestTheBusBeingAwayIsNotNowLikeTheStoreBeingAway(t *testing.T) {
}
}
}
// 1215/AC1 and AC4: the inputs of a build's fingerprint are kept in the build's record, through the bucket's bytes, unchanged.
func TestABuildKeepsTheInputsOfItsSourceFingerprint(t *testing.T) {
in := &delivery.SourceInputs{Module: "notes", Tree: "t1", Bases: []string{"r/b@sha256:aa"},
Contexts: map[string]string{"server": "narrow:ff"}, Toolchains: map[string]string{"code": "go sha256:cc 0123"}}
fp, _ := delivery.SourceFingerprint(*in)
b := Build{ID: "build-1", Module: "notes", Commit: "c1", SourceFingerprint: fp, SourceInputs: in}
raw, err := json.Marshal(toChainBuild(b))
if err != nil {
t.Fatal(err)
}
var back chain.Build
if err := json.Unmarshal(raw, &back); err != nil {
t.Fatal(err)
}
got := fromChainBuild(back)
if !reflect.DeepEqual(got.SourceInputs, in) {
t.Fatalf("the inputs came back as %+v, not %+v", got.SourceInputs, in)
}
if again, _ := delivery.SourceFingerprint(*got.SourceInputs); again != got.SourceFingerprint {
t.Errorf("the inputs read back give %q, the record says %q", again, got.SourceFingerprint)
}
}
// 1215/AC3: a build with no fingerprint keeps its inputs, so the reason is visible.
func TestABuildWithNoFingerprintKeepsItsInputs(t *testing.T) {
in := &delivery.SourceInputs{Module: "notes", Tree: "t1", Unpinned: "it resolves packages when built"}
raw, _ := json.Marshal(toChainBuild(Build{ID: "build-1", Module: "notes", SourceInputs: in}))
var back chain.Build
if err := json.Unmarshal(raw, &back); err != nil {
t.Fatal(err)
}
if got := fromChainBuild(back); got.SourceFingerprint != "" || !reflect.DeepEqual(got.SourceInputs, in) {
t.Errorf("read back as %q with %+v", got.SourceFingerprint, got.SourceInputs)
}
}
// 1215/AC2: a record written before the inputs were kept reads as before, with none and no error.
func TestABuildRecordFromBeforeTheInputsReadsAsBefore(t *testing.T) {
var back chain.Build
old := `{"id":"build-1","module":"notes","commit":"c1","source-fingerprint":"src1:aa","reuse-key":"rk"}`
if err := json.Unmarshal([]byte(old), &back); err != nil {
t.Fatal(err)
}
got := fromChainBuild(back)
if got.SourceInputs != nil || got.SourceFingerprint != "src1:aa" || got.ReuseKey != "rk" {
t.Errorf("an old record reads as %+v", got)
}
if raw, _ := json.Marshal(toChainBuild(got)); strings.Contains(string(raw), "source-inputs") {
t.Errorf("an old record is written with inputs: %s", raw)
}
}