diff --git a/go/delivery/published_packages_test.go b/go/delivery/published_packages_test.go index 773f022..a971dc9 100644 --- a/go/delivery/published_packages_test.go +++ b/go/delivery/published_packages_test.go @@ -105,8 +105,21 @@ func TestPublishedPackagesAndImportsRoundTripAndRefuse(t *testing.T) { } } var answer BuildAnswer - if err := json.Unmarshal([]byte(`{"artifacts":[],"imports":["`+sdk+`"]}`), &answer); err != nil || len(answer.Imports) != 1 { - t.Fatalf("build answer imports: %v, %v", err, answer.Imports) + if err := json.Unmarshal([]byte(`{"artifacts":[],"imports":["`+sdk+`"],"out-of-step":["go/seats/x/contract.go"]}`), &answer); err != nil || + len(answer.Imports) != 1 || !reflect.DeepEqual(answer.OutOfStep, []string{"go/seats/x/contract.go"}) { + t.Fatalf("build answer imports and files out of step: %v, %v, %v", err, answer.Imports, answer.OutOfStep) + } + // The builder's version.numbered, as a build module consumes it: the builder's own body whole, and its subject. + numbered := VersionNumbered{Name: "mesh-sdk/go", Kind: "package", Version: "0.1.7", Module: "mesh-sdk", Fingerprint: "src2:x", + Commit: commit, Digest: "sha256:d", Seat: "go-package-registry", Head: commit, Again: "2026-11-04T15:00:00Z"} + if raw, err := EncodeVersionNumbered(numbered); err != nil { + t.Errorf("numbered refused: %v", err) + } else if back, err := DecodeVersionNumbered(raw); err != nil || back != numbered || + !strings.Contains(string(raw), `"fingerprint":"src2:x"`) || VersionNumberedSubject != "mesh.mod.mesh-delivery-builder.event.version.numbered" { + t.Errorf("numbered round trip: %+v, %v, %s", back, err, raw) + } + if _, err := DecodeVersionNumbered([]byte(`{"name":"mesh-sdk/go","version":"0.1.7"}`)); err == nil { + t.Error("a numbered version naming no build is read") } if err := CheckImports([]string{"fmt"}); err == nil { t.Error("CheckImports takes a standard package") diff --git a/go/delivery/stepasks.go b/go/delivery/stepasks.go index 110cd21..a4ddb74 100644 --- a/go/delivery/stepasks.go +++ b/go/delivery/stepasks.go @@ -588,6 +588,10 @@ type BuildAnswer struct { // once (novox/hq ADR 0593 ยง2): a Go build module reports them, the builder carries them onto the build's end // (BuildLogEntry.Imports). Absent from a build module that reports none. Imports []string `json:"imports,omitempty"` + // OutOfStep are the generated files the build's compare found out of step with what their generator writes (the test + // step's `out-of-step`, answered by a trunk build's ask too): the builder carries them onto the build's end + // (BuildLogEntry.OutOfStep). Absent when none is, or from a build module that compares none. + OutOfStep []string `json:"out-of-step,omitempty"` } // AskBuilt is one artifact a build ask built. diff --git a/go/delivery/version_numbered.go b/go/delivery/version_numbered.go new file mode 100644 index 0000000..634cb84 --- /dev/null +++ b/go/delivery/version_numbered.go @@ -0,0 +1,57 @@ +package delivery + +import ( + "encoding/json" + "fmt" +) + +// The builder's event version.numbered (design 51 *Events of the builder*, *The floor*): said once per version the builder +// numbers, on its own family `version` (seven days), apart from its builds' ends, so a waiter on a build's end does not +// wake on it. A build module consumes it to publish a build's version with the held digest, which stores nothing. +const ( + // VersionNumberedEvent is the event's local name. + VersionNumberedEvent = "version.numbered" + // VersionNumberedSubject is its subject, as a consumer's filter names it. + VersionNumberedSubject = "mesh.mod." + BuilderModule + ".event." + VersionNumberedEvent +) + +// VersionNumbered is the body of VersionNumberedEvent: a numbered version of a name, the build it was numbered for, and +// what the build module needs to publish it. +type VersionNumbered struct { + // Name is the numbered name, as builder_versions lists it; Kind its artifact's kind. + Name string `json:"name"` + Kind string `json:"kind"` + Version string `json:"version"` + Tag string `json:"tag,omitempty"` + // Module and Fingerprint are the build the version was numbered for; Commit its source commit. + Module string `json:"module"` + Fingerprint string `json:"fingerprint"` + Commit string `json:"commit,omitempty"` + // Digest is the built artifact's, when the build held one; Seat the registry seat it is published through. + Digest string `json:"digest,omitempty"` + Seat string `json:"seat,omitempty"` + // Head is the head the version answers, when it answers a tag ask. + Head string `json:"head,omitempty"` + // Again is when builder_publish_again said it once more, so the bus's dedup does not drop it. + Again string `json:"again,omitempty"` +} + +// EncodeVersionNumbered writes the body, refusing one that names no name, version, module or fingerprint. +func EncodeVersionNumbered(n VersionNumbered) ([]byte, error) { + if n.Name == "" || n.Version == "" || n.Module == "" || n.Fingerprint == "" { + return nil, fmt.Errorf("delivery: %s names its name, version, module and fingerprint", VersionNumberedEvent) + } + return json.Marshal(n) +} + +// DecodeVersionNumbered reads the body, with the same refusal. +func DecodeVersionNumbered(data []byte) (VersionNumbered, error) { + var n VersionNumbered + if err := json.Unmarshal(data, &n); err != nil { + return VersionNumbered{}, fmt.Errorf("delivery: %s is not readable: %w", VersionNumberedEvent, err) + } + if _, err := EncodeVersionNumbered(n); err != nil { + return VersionNumbered{}, err + } + return n, nil +}