D5: indexer_drop_pending counts a later pending merge as covering only when its commit has the pending merge's as an ancestor (issue 1966, ADR 0668 §6, ADR 0633 §5)

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-10 03:34:57 +01:00
parent e4dabdbc7a
commit d3f68c5372
2 changed files with 29 additions and 2 deletions
@@ -1307,7 +1307,23 @@ func (ix *Indexer) DropPending(ctx context.Context, key, by, why string, abandon
}
e := entries[at]
if !abandon {
covered := slices.ContainsFunc(entries[at+1:], func(o PendingEntry) bool { return o.Forge.Repository == e.Forge.Repository })
// Covered: a later merge of the same repository, pending or taken in, has this merge's commit as an ancestor (ADR
// 0668 §6), asked of the forge (older); a later merge whose history does not hold it covers nothing.
covered := false
for _, o := range entries[at+1:] {
if o.Forge.Repository != e.Forge.Repository {
continue
}
in, err := ix.older(ctx, e.Forge.Repository, e.Forge.Merge, o.Forge.Merge)
if err != nil {
r.Answer = fmt.Sprintf("%s is not dropped: whether the later pending merge %s covers it is not read: %v", key, o.Key, err)
return r
}
if in {
covered = true
break
}
}
if !covered {
taken, err := ix.takenAlready(ctx, forgemsg.GroupMerge{Repository: e.Forge.Repository, Number: e.Forge.Number, Merge: e.Forge.Merge})
if err != nil {
@@ -1317,7 +1333,7 @@ func (ix *Indexer) DropPending(ctx context.Context, key, by, why string, abandon
covered = taken
}
if !covered {
r.Answer = fmt.Sprintf("%s is not dropped: no later merge of %s is pending and its taken merge does not cover %s, so its "+
r.Answer = fmt.Sprintf("%s is not dropped: no later merge of %s, pending or taken in, has %s as an ancestor, so its "+
"changes would never be taken in (ADR 0633 §5); %s reads its message again, or abandon: true drops it all the same",
key, e.Forge.Repository, e.Forge.Merge, TakeInPendingTool)
return r
@@ -711,6 +711,17 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
answer.Waits[0].Said != takein.TakeInPendingEvent || len(answer.Waits[0].Keys) != 2 {
t.Fatalf("indexer_pending = %+v", answer)
}
// ADR 0668 §6: a later merge covers a pending one only when its commit has the pending merge's as an ancestor.
history := "cd"
b.ix.Contains = func(_ context.Context, _, commit, in string) (bool, error) {
i, j := strings.IndexByte(history, commit[0]), strings.IndexByte(history, in[0])
return i >= 0 && j >= 0 && i <= j, nil
}
history = "dc" // m.3's commit is not in m.2's later history (a history rewritten)
if r := b.ix.DropPending(ctx, takein.PendingKey(2), "lead@shanks", "replaced by m.3", false); r.Stored || !strings.Contains(r.Answer, "as an ancestor") {
t.Fatalf("a drop a later merge not holding it would cover: %+v", r)
}
history = "cd"
b.said = nil
if r := b.ix.DropPending(ctx, takein.PendingKey(2), "lead@shanks", "replaced by m.3", false); !r.Stored {
t.Fatalf("drop: %+v", r)