Merge pull request 'The indexer says a merged manifest accepted, so a new build gets its part grant without a hand act (issue 1966)' (#207) from fix/1966-new-build-grant into main

This commit was merged in pull request #207.
This commit is contained in:
2026-11-12 12:28:38 +00:00
2 changed files with 40 additions and 2 deletions
@@ -245,6 +245,12 @@ func (ix *Indexer) judgeOverHeld(ctx context.Context, fresh map[string]bool, ove
a := acceptedOf(digests[name], v, revision, raws[name]) a := acceptedOf(digests[name], v, revision, raws[name])
_ = ix.sayAccepted(ctx, a, acceptedEventID(a)) _ = 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). // 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]) needs, _ := catalogue.ManifestNeeds(raws[name])
@@ -262,6 +268,26 @@ func (ix *Indexer) judgeOverHeld(ctx context.Context, fresh map[string]bool, ove
return out, nil 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 // 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 // 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 // digest of the verdicts state that another judge refused or held waiting (one of a candidate, or one back on the trunk
@@ -185,8 +185,9 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
moved := hold("moved", `{"module":"moved","version":"1","requires":[`+ moved := hold("moved", `{"module":"moved","version":"1","requires":[`+
`{"name":"cache","storage":"key-value","template":"state","options":{"history":8}},`+ `{"name":"cache","storage":"key-value","template":"state","options":{"history":8}},`+
`{"name":"audit","storage":"stream","template":"log","options":{"max-mib":64}}]}`) `{"name":"audit","storage":"stream","template":"log","options":{"max-mib":64}}]}`)
for pass := 0; pass < 2; pass++ { // The merge's pass, then a later one that knows nothing of the merge.
if _, err := ix.JudgeAll(ctx, map[string]bool{moved: true}); err != nil { for _, fresh := range []map[string]bool{{moved: true}, nil} {
if _, err := ix.JudgeAll(ctx, fresh); err != nil {
t.Fatal(err) 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 { 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]) 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 // 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. // judging pass says again and clears.
ix.Findings = newMem() ix.Findings = newMem()