From f0b1f57560f84fef129b23e18251e5b14432832c Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 23:00:33 +0100 Subject: [PATCH] =?UTF-8?q?delivery:=20indexer=5Fpending's=20answer,=20so?= =?UTF-8?q?=20the=20view=20names=20a=20module=20waiting=20for=20its=20inde?= =?UTF-8?q?xer=20(ADR=200633=20=C2=A75,=20issue=201585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn --- go/delivery/source_test.go | 11 ++++++++++ go/delivery/takein.go | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/go/delivery/source_test.go b/go/delivery/source_test.go index e740eab..f462d46 100644 --- a/go/delivery/source_test.go +++ b/go/delivery/source_test.go @@ -250,6 +250,17 @@ func TestSourceValuesRoundTrip(t *testing.T) { // 1200/AC3 t.Errorf("take-in %s round trip: %+v %v", ti.Key, got, err) } } + // 1585/T12: indexer_pending's answer reads back; a wait of neither kind is refused. + pa := PendingAnswer{Waits: []PendingWait{{Key: PendingKey(12), What: TakeInRevision, Merges: sampleTakeIns(since)[0].Merges, + WaitsFor: "src2:abc", Since: since, Holds: []string{"mesh-delivery-indexer"}, Said: "take-in.pending." + PendingKey(12), SaidAt: since}}} + if data, err := json.Marshal(pa); err != nil { + t.Fatal(err) + } else if got, err := DecodePendingAnswer(data); err != nil || !reflect.DeepEqual(got, pa) { + t.Errorf("pending answer round trip: %+v %v", got, err) + } + if _, err := DecodePendingAnswer([]byte(`{"waits":[{"key":"m.1","what":"held"}]}`)); err == nil { + t.Error("a pending answer with a wait of neither kind accepted") + } // 1280: the taken and reach values (ADR 0556). tk := Taken{Repository: "novox/mesh-sdk", Merge: "cccc", At: time.Date(2026, 10, 26, 20, 0, 0, 0, time.UTC)} data, err = EncodeTaken(tk) diff --git a/go/delivery/takein.go b/go/delivery/takein.go index 4971796..bf64c92 100644 --- a/go/delivery/takein.go +++ b/go/delivery/takein.go @@ -166,3 +166,44 @@ func DecodeTakeIn(data []byte) (TakeIn, error) { // SameTakeIn says whether two take-in bodies are the same value. func SameTakeIn(a, b TakeIn) bool { return reflect.DeepEqual(a, b) } + +// PendingWait is one wait the indexer's tool indexer_pending answers (novox/hq ADR 0633 ยง5), as the view reads it to +// name a module waiting for its revision's indexer. Key and What are a take-in event's: a revision under its oldest +// merge's key, waiting for the indexer build WaitsFor (empty while it is worked out), or a group being gathered under +// its group key, with Members every approved member and Merges those held so far. A revision that waits behind a +// group being gathered names that group's key in Behind. Holds are the modules it holds back: every module of its +// merges' trees. Said is the last take-in event said of it (`take-in..`) and SaidAt when. +type PendingWait struct { + Key string `json:"key"` + What string `json:"what"` + Merges []GroupMerge `json:"merges"` + Group string `json:"group,omitempty"` + Members []Head `json:"members,omitempty"` + Behind string `json:"behind,omitempty"` + WaitsFor string `json:"waits-for,omitempty"` + Since time.Time `json:"since"` + Holds []string `json:"holds"` + Said string `json:"said,omitempty"` + SaidAt time.Time `json:"said-at,omitzero"` +} + +// PendingAnswer is indexer_pending's answer: every wait, oldest first, and the pending keys that do not read. +type PendingAnswer struct { + Waits []PendingWait `json:"waits"` + Unread []string `json:"unread,omitempty"` +} + +// DecodePendingAnswer reads indexer_pending's answer, refusing a wait that is not a revision or a group, or names no +// key. +func DecodePendingAnswer(data []byte) (PendingAnswer, error) { + var a PendingAnswer + if err := json.Unmarshal(data, &a); err != nil { + return a, fmt.Errorf("delivery: not an answer of indexer_pending: %w", err) + } + for _, w := range a.Waits { + if w.Key == "" || (w.What != TakeInRevision && w.What != TakeInGroup) { + return a, fmt.Errorf("delivery: indexer_pending answers a wait %q of %q, not a revision or a group", w.Key, w.What) + } + } + return a, nil +}