From 0424e87399d8540bf64ad6f254ce796057da2af4 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 14:19:21 +0100 Subject: [PATCH] =?UTF-8?q?ADR=200627=20=C2=A72:=20a=20transfer=20read=20b?= =?UTF-8?q?ack=20from=20the=20bus=20takes=20its=20first=20chunk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A transfer is kept in a key-value state as JSON, and its empty Chunks map is omitted, so the one read back for the first chunk had none and Take panicked: every publish in parts through BusTransfers. The unit tests' transfers now round-trip JSON as the bus does. Found by the forge module's whole test (CAT-G3). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- go/go.sum | 4 ++++ go/parts/receive.go | 4 ++++ go/parts/receive_test.go | 28 +++++++++++++++++++++++----- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/go/go.sum b/go/go.sum index b6588f4..8ca93df 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,10 +1,13 @@ github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op h1:1BOWQJweNyvZMlpAHXGLiZQn9S+QXGcz3xh94lC0w6E= +github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/minio/highwayhash v1.0.4 h1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4= github.com/minio/highwayhash v1.0.4/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/nats-io/jwt/v2 v2.8.2 h1:XXRgB60MSTnqsRwejQurVDs/hcv2dkt+86GjI+I/bMc= +github.com/nats-io/jwt/v2 v2.8.2/go.mod h1:Ag/56sq9OblL4JgdYufDd16Egb17Kr/8WwwuO/forVc= github.com/nats-io/nats-server/v2 v2.11.17 h1:GKEghcFK6A+aFx11Yf1LjgLC3txAwvyhnYzhBIQZA8I= github.com/nats-io/nats-server/v2 v2.11.17/go.mod h1:B1sFVz4StNosQ903ak4N1G01Fl/9f8e06mXpFIE2K24= github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= @@ -19,3 +22,4 @@ golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= diff --git a/go/parts/receive.go b/go/parts/receive.go index f745188..cdb6b6e 100644 --- a/go/parts/receive.go +++ b/go/parts/receive.go @@ -240,6 +240,10 @@ func (r *Receiver[M]) Take(id, opener, digest string, sequence int, last bool, d if err := r.Objects.Put(r.chunkName(id, digest, sequence), data); err != nil { return Received{}, fmt.Errorf("the objects did not take the chunk: %w", err) } + // A transfer read back from a key-value state has no Chunks until one was taken: the empty map is kept omitted. + if t.Chunks == nil { + t.Chunks = map[string]int{} + } t.Chunks[digest] = sequence + 1 t.Last = r.now() if last { diff --git a/go/parts/receive_test.go b/go/parts/receive_test.go index ac37613..8f2f8d2 100644 --- a/go/parts/receive_test.go +++ b/go/parts/receive_test.go @@ -3,6 +3,7 @@ package parts import ( "crypto/sha256" "encoding/hex" + "encoding/json" "errors" "reflect" "strings" @@ -28,14 +29,31 @@ func (m memObjects) Used() (uint64, error) { return u, nil } -type memTransfers[M any] map[string]Transfer[M] +// memTransfers keeps each transfer as the bus keeps it, its JSON, so every case reads back what a key-value state +// gives (BusTransfers): an empty map kept with omitempty comes back nil. +type memTransfers[M any] map[string][]byte -func (m memTransfers[M]) Get(id string) (Transfer[M], bool, error) { t, ok := m[id]; return t, ok, nil } -func (m memTransfers[M]) Put(t Transfer[M]) error { m[t.ID] = t; return nil } -func (m memTransfers[M]) Delete(id string) error { delete(m, id); return nil } +func (m memTransfers[M]) Get(id string) (Transfer[M], bool, error) { + var t Transfer[M] + raw, ok := m[id] + if !ok { + return t, false, nil + } + return t, true, json.Unmarshal(raw, &t) +} +func (m memTransfers[M]) Put(t Transfer[M]) error { + raw, err := json.Marshal(t) + m[t.ID] = raw + return err +} +func (m memTransfers[M]) Delete(id string) error { delete(m, id); return nil } func (m memTransfers[M]) List() ([]Transfer[M], error) { var out []Transfer[M] - for _, t := range m { + for id := range m { + t, _, err := m.Get(id) + if err != nil { + return nil, err + } out = append(out, t) } return out, nil