delivery: indexer_pending's answer, so the view names a module waiting for its indexer (ADR 0633 §5, issue 1585)

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
2026-11-04 23:00:33 +01:00
parent 8ba75aae89
commit f0b1f57560
2 changed files with 52 additions and 0 deletions
+11
View File
@@ -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)
+41
View File
@@ -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.<kind>.<key>`) 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
}