J4 (J3 item 7): merge A3's wip/registry-a3-sdk (c974a11): a holder's receiving side of a file in parts joins its sender

Brings A3's parts receiver and bus adapters, the shared caller lists and builder versions question, a transfer read
back from bus state (0424e87) and catalogue.SeatReference (c10ef07). c974a11 adds nothing: its version_numbered.go
and BuildAnswer.OutOfStep are the join's own. publish stays out (ADR 0627, 1a149d4): packages_list_test keeps the
join's form. TestAHolderTakesAFileInParts is a subtest of TestAFileTravelsInTheParts, so unit tests stay at 476.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 15:12:03 +01:00
11 changed files with 891 additions and 4 deletions
+17
View File
@@ -1,7 +1,9 @@
package delivery
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
"testing"
@@ -174,6 +176,21 @@ func TestBuilderBuildVersion(t *testing.T) {
t.Errorf("DecodeBuilderBuild(%s) = %v", in, err)
}
}
// A holder's question before it stores (ADR 0605 §4): asked at the builder's address with name, version and digest;
// an answer without recorded is an error, never a yes.
for answer, want := range map[string]string{`{"recorded":true}`: "true ", `{"recorded":false,"why":"other digest"}`: "false other digest",
`{"name":"x"}`: "error", `not json`: "error"} {
b := BuilderVersions{Ask: func(addr string, body any) (json.RawMessage, error) {
if a := body.(map[string]any); addr != BuilderVersionsAddress || a["name"] != "n" || a["version"] != "0.1.2" || a["digest"] != "sha256:d" {
t.Errorf("asked %s %v", addr, body)
}
return json.RawMessage(answer), nil
}}
ok, why, err := b.Recorded("n", "0.1.2", "sha256:d")
if got := fmt.Sprintf("%v %s", ok, why); err != nil && want != "error" || err == nil && got != want {
t.Errorf("answer %s: %s, %v; want %s", answer, got, err, want)
}
}
}
// 1358/AC6: the builder's bucket and module do not collide with the controller's build layout (buildrecord.go), which