diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts.go b/modules/mesh-delivery-indexer/internal/index/verdicts.go index 24ce8159..ccb67181 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts.go @@ -245,6 +245,12 @@ func (ix *Indexer) judgeOverHeld(ctx context.Context, fresh map[string]bool, ove a := acceptedOf(digests[name], v, revision, raws[name]) _ = ix.sayAccepted(ctx, a, acceptedEventID(a)) } + } else if over == nil && fresh[digests[name]] && !meshcatalogclient.VerdictHeld(v) { + // A manifest a merge just made its module's trunk manifest is newly accepted there, even when a pull request's + // judgement wrote its verdict before the merge and said it nowhere (a candidate's verdict is not said): it is + // said now from the verdict held, with its revision, so the bus module and the grants part hear it (design 25 + // bus-state §1; issue 1966: claude-licence-manager's new builds waited for a grant nothing wrote). + ix.sayHeld(ctx, digests[name], raws[name]) } // The reader capabilities the manifest's own words, forms and seat names need, named with the verdict (ADR 0614 §4). needs, _ := catalogue.ManifestNeeds(raws[name]) @@ -262,6 +268,26 @@ func (ix *Indexer) judgeOverHeld(ctx context.Context, fresh map[string]bool, ove return out, nil } +// sayHeld says manifest-accepted for the trunk's manifest of digest from the accepted verdict the verdicts state holds on +// it, with that verdict's revision; a verdict that cannot be read, or that refuses or waits, says nothing. +func (ix *Indexer) sayHeld(ctx context.Context, digest string, raw []byte) { + key, err := meshcatalogclient.VerdictsKey(digest) + if err != nil { + return + } + value, revision, found, err := ix.Verdicts.Get(ctx, key) + if err != nil || !found { + ix.logf("judge: the verdict on %s cannot be read, so its manifest-accepted is not said: %v", digest, err) + return + } + v, err := meshcatalogclient.DecodeVerdict(value) + if err != nil || meshcatalogclient.VerdictHeld(v) { + return + } + a := acceptedOf(digest, v, revision, raw) + _ = ix.sayAccepted(ctx, a, acceptedEventID(a)) +} + // JudgeAtStart is JudgeAll of every manifest held, none fresh, as the indexer starts (a judge that changed judges every // one again); one that fails is judged again at the next message or source revision heard (kickJudge). Then every other // digest of the verdicts state that another judge refused or held waiting (one of a candidate, or one back on the trunk diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go index 09d6cf52..80be7d72 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go @@ -185,8 +185,9 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { moved := hold("moved", `{"module":"moved","version":"1","requires":[`+ `{"name":"cache","storage":"key-value","template":"state","options":{"history":8}},`+ `{"name":"audit","storage":"stream","template":"log","options":{"max-mib":64}}]}`) - for pass := 0; pass < 2; pass++ { - if _, err := ix.JudgeAll(ctx, map[string]bool{moved: true}); err != nil { + // The merge's pass, then a later one that knows nothing of the merge. + for _, fresh := range []map[string]bool{{moved: true}, nil} { + if _, err := ix.JudgeAll(ctx, fresh); err != nil { t.Fatal(err) } } @@ -235,6 +236,17 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { if _, err := ix.judgeOver(ctx, nil, "bydigest", candidate); err != nil || accepted[len(accepted)-1].Digest != byDigest { t.Fatalf("a candidate's verdict was said: %v %+v", err, accepted[len(accepted)-1]) } + // Issue 1966 (design 25 bus-state §1): the candidate merged, its verdict unchanged, is newly accepted on the trunk and + // said at the merge's pass, from the verdict held and with its revision, though the pass writes no verdict. + if hold("bydigest", `{"module":"bydigest","version":"2"}`) != candidate { + t.Fatal("the merged manifest is not the candidate's") + } + if j, err := ix.JudgeAll(ctx, map[string]bool{candidate: true}); err != nil || j.Written != 0 { + t.Fatalf("the merge's pass: %+v %v", j, err) + } + if _, rev, _, _ := ix.Verdicts.Get(ctx, candidate); accepted[len(accepted)-1].Digest != candidate || accepted[len(accepted)-1].Revision != int64(rev) { + t.Fatalf("the merged candidate's manifest was not said accepted with its verdict's revision %d: last said %+v", rev, accepted[len(accepted)-1]) + } // DLV-I9b: a failed publish is published again; one failing twice writes the finding naming the module, which the next // judging pass says again and clears. ix.Findings = newMem()