The indexer's catalog graph revisions are read and steered by its tools: one revision, every revision with the store's fill, the candidates' halves, a recompute compared with the kept one, and a drop by hand that says room (milestone 47/G W4.12)

Pins and the consumer of the gate's events come with W4.5, which makes them.

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 11:48:46 +01:00
parent b2b449c913
commit a217a44f05
7 changed files with 447 additions and 11 deletions
@@ -289,6 +289,10 @@ func main() {
// The seat mesh-catalog's verb revision, a first view of the catalog half answered in this runtime of its own to exactly
// the parts whose entry declares it (novox/hq ADR 0663).
tools = append(tools, index.RevisionTool(revisions, index.Module))
tools = append(tools, index.CatalogRevisionTools(revisions, asks, time.Now, func(what, why string) error {
_, err := acts.Record(meshhandactsclient.Request{What: what, Why: why, Cause: index.DropCandidateGraphTool, Verb: "drop-candidate-graph"})
return err
})...)
tools = append(tools, viewTools(os.Getenv, trunk.State, time.Now)...)
tools = append(tools, index.PendingTools(keptRead{stdio.State(index.PendingState)}, keptRead{stdio.State(index.OwnBuildStateName)}, asks, time.Now)...)
tools = append(tools, index.FingerprintsTools(asks, time.Now, func(what, why string) error {
@@ -26,6 +26,8 @@ type takerIn interface {
DropTryCandidate(ctx context.Context, id, by, why string) index.TakenIn
RecoverTriesByHand(ctx context.Context, by, why string) index.TakenIn
FingerprintsAgain(ctx context.Context, module string) index.TakenIn
RecomputeByHand(ctx context.Context, by, why string) index.TakenIn
DropCandidateGraph(ctx context.Context, group, heads, by, why string) index.TakenIn
}
// takenIn answers one value of the takeins state (the tool indexer_take_in, issue 1945): a value not yet answered takes the
@@ -44,7 +46,13 @@ func takenIn(ctx context.Context, ix takerIn, kv jetstream.KeyValue, e jetstream
return
}
asked, r := "take-in", index.TakenIn{}
if a.Fingerprints {
if a.Recompute {
asked, r = "recompute of the catalog graph", ix.RecomputeByHand(ctx, a.By, a.Why)
a.Module = r.Module
} else if a.CandidateGroup != "" && a.Drop {
asked, r = "drop of a candidate's catalog half", ix.DropCandidateGraph(ctx, a.CandidateGroup, a.CandidateHeads, a.By, a.Why)
a.Module = r.Module
} else if a.Fingerprints {
asked, r = "computing of fingerprints again", ix.FingerprintsAgain(ctx, a.Module)
} else if a.RecoverTries {
asked, r = "recovery of the tries", ix.RecoverTriesByHand(ctx, a.By, a.Why)
@@ -18,6 +18,7 @@ import (
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
"git.novox.be/novox/mesh-delivery/sdk/trunk"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/busretry"
"git.novox.be/novox/mesh-sdk/go/catalogue"
@@ -794,6 +795,91 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
t.Errorf("a take-in to 85 %% said filling %+v (%v), pending %v: want it said once and the merge taken in", filling, err, b2.keys())
}
})
t.Run("W4.12 a candidate's half dropped by hand frees its own entries, says room and takes the held merge in; the read tools list it; a recompute that moves nothing is equal", func(t *testing.T) {
bed := func(store *memObjects) *takeInBed {
b := newTakeInBed(t)
b.arrive(t, 2, b.merge(21, sha('c'), "tree-avahi-3", "tree-indexer-2"))
b.ix.ownMu.Lock()
b.ix.ownFP = b.fp(t, Module)
b.ix.ownMu.Unlock()
b.ix.Revisions = store
return b
}
free := newMemObjects(0)
if _, _, err := bed(free).ix.TakeInAtStart(ctx); err != nil {
t.Fatal(err)
}
size := free.used() // what the merge's revision takes
store := newMemObjects(2 * size)
shared := []byte(`{"module":"b"}`)
if err := KeepRevision(ctx, store, meshcatalog.CatalogRevision{Kind: "candidate", Group: "g1", Heads: "h1", Revision: 1,
Entries: []meshcatalog.EntryRef{{Module: "a", Digest: "da"}, {Module: "b", Digest: "db"}}},
map[string][]byte{"da": make([]byte, size+10), "db": shared}); err != nil {
t.Fatal(err)
}
if err := KeepRevision(ctx, store, meshcatalog.CatalogRevision{Kind: "candidate", Group: "g2", Heads: "h2", Revision: 1,
Entries: []meshcatalog.EntryRef{{Module: "b", Digest: "db"}}}, map[string][]byte{"db": shared}); err != nil {
t.Fatal(err)
}
b := bed(store)
var said []string
b.ix.SayFill = func(_ context.Context, event, _ string, _ meshcatalog.Fill) error { said = append(said, event); return nil }
if _, _, err := b.ix.TakeInAtStart(ctx); err != nil || len(b.keys()) != 1 {
t.Fatalf("pending %v (%v): want the merge held for objects full", b.keys(), err)
}
var recorded []string
asks := 0
tools := map[string]stdio.Tool{}
for _, tool := range CatalogRevisionTools(store, func(_ string, value []byte) (TakeInAsk, error) {
asks++
a, err := DecodeTakeIn(value)
if err != nil {
return a, err
}
var r TakenIn
if a.Recompute {
r = b.ix.RecomputeByHand(ctx, a.By, a.Why)
} else {
r = b.ix.DropCandidateGraph(ctx, a.CandidateGroup, a.CandidateHeads, a.By, a.Why)
}
a.Result = &r
return a, nil
}, time.Now, func(what, why string) error {
recorded = append(recorded, what)
if why == "unrecorded" {
return errors.New("mesh-hand-acts did not answer")
}
return nil
}) {
tools[tool.Name] = tool
}
if _, err := tools[DropCandidateGraphTool].Run(map[string]any{"group": "g1", "heads": "h1", "why": "unrecorded"}); err == nil || asks != 0 {
t.Fatalf("a drop whose hand act is not recorded: %v, %d asks: want refused before it is asked", err, asks)
}
got, err := tools[DropCandidateGraphTool].Run(map[string]any{"group": "g1", "heads": "h1", "why": "its gate event never came"})
r, _ := got.(*TakenIn)
if err != nil || r == nil || !r.Stored || !slices.Equal(r.Dropped, []string{"entry.da"}) || len(recorded) != 2 {
t.Fatalf("drop answered %+v (%v), recorded %q: want candidate.g1.h1 dropped with entry.da alone, after its hand act", r, err, recorded)
}
if _, err := store.Get(ctx, "entry.db"); err != nil || len(b.keys()) != 0 || !slices.Contains(said, meshcatalog.EventRoom) {
t.Errorf("entry.db %v, pending %v, said %q: want the shared entry kept, room said and the merge taken in", err, b.keys(), said)
}
list, err := tools[CatalogRevisionsTool].Run(map[string]any{})
l, _ := list.(RevisionsAnswer)
if err != nil || len(l.Revisions) != 2 || l.Revisions[0].Name != "candidate.g2.h2" || l.Revisions[1].Name != "trunk.1" || !l.Revisions[1].Current ||
l.Fill.Cap != 2*size || l.Fill.Bytes != store.used() || len(l.Unlisted) != 0 {
t.Errorf("indexer_catalog_revisions = %+v (%v): want candidate.g2.h2 and the current trunk.1, the fill against its cap", l, err)
}
if c, err := tools[CandidateGraphsTool].Run(map[string]any{}); err != nil || len(c.(RevisionsAnswer).Revisions) != 1 {
t.Errorf("indexer_candidate_graphs = %+v (%v): want g2's alone", c, err)
}
if one, err := tools[CatalogRevisionTool].Run(map[string]any{}); err != nil || one.(map[string]any)["name"] != "trunk.1" {
t.Errorf("indexer_catalog_revision = %v (%v): want the newest trunk revision whole", one, err)
}
if got, err := tools[RecomputeTool].Run(map[string]any{"why": "a check"}); err != nil || !strings.Contains(got.(*TakenIn).Answer, "equal to the kept revision 1") {
t.Errorf("indexer_recompute = %+v (%v): want equal to the kept revision 1", got, err)
}
})
t.Run("T7 a toolchain report evaluates pending again: the revision then names the running build and is taken in", func(t *testing.T) {
b := newTakeInBed(t)
b.arrive(t, 2, b.merge(21, sha('c'), "tree-avahi-3", "tree-indexer-2"))
@@ -0,0 +1,326 @@
package index
import (
"context"
"encoding/json"
"errors"
"fmt"
"regexp"
"slices"
"strconv"
"strings"
"time"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// The indexer's tools of its catalog graph revisions (design 47, the indexer's tools; the instruction "tools are how a
// module is spoken to"): indexer_catalog_revision and indexer_catalog_revisions read the objects catalog-revisions,
// indexer_candidate_graphs every candidate's catalog half; indexer_recompute and indexer_drop_candidate_graph steer the
// indexer by an ask in takeins, as indexer_take_in does, since the indexer's process serves no request subject.
const (
CatalogRevisionTool = "indexer_catalog_revision"
CatalogRevisionsTool = "indexer_catalog_revisions"
CandidateGraphsTool = "indexer_candidate_graphs"
RecomputeTool = "indexer_recompute"
DropCandidateGraphTool = "indexer_drop_candidate_graph"
)
// RecomputeKey is the takeins key of a recompute by hand: one at a time.
const RecomputeKey = "recompute"
var candidateToken = regexp.MustCompile(`^[A-Za-z0-9_=-]+$`)
// NewRecomputeAsk is the value indexer_recompute writes: its key in takeins and the value.
func NewRecomputeAsk(why, by string, at time.Time) (string, []byte, error) {
data, err := json.Marshal(TakeInAsk{V: 1, Recompute: true, Why: strings.TrimSpace(why), By: by, At: at.UTC()})
return RecomputeKey, data, err
}
// NewDropCandidateGraphAsk is the value indexer_drop_candidate_graph writes: its key in takeins,
// candidate-graph.<group>.<heads>, and the value.
func NewDropCandidateGraphAsk(group, heads, why, by string, at time.Time) (string, []byte, error) {
group, heads = strings.TrimSpace(group), strings.TrimSpace(heads)
switch {
case !candidateToken.MatchString(group) || !candidateToken.MatchString(heads):
return "", nil, errors.New("name the candidate by its group and its heads, as indexer_candidate_graphs names them")
case strings.TrimSpace(why) == "":
return "", nil, errors.New("say why (why): a drop is a hand act, recorded through mesh-hand-acts")
}
data, err := json.Marshal(TakeInAsk{V: 1, CandidateGroup: group, CandidateHeads: heads, Drop: true, Why: strings.TrimSpace(why), By: by, At: at.UTC()})
return "candidate-graph." + group + "." + heads, data, err
}
// RevisionSummary is one kept catalog graph revision as indexer_catalog_revisions and indexer_candidate_graphs list it.
type RevisionSummary struct {
Name string `json:"name"`
Kind string `json:"kind"`
Revision int64 `json:"revision"`
Source int64 `json:"source"`
Group string `json:"group,omitempty"`
Heads string `json:"heads,omitempty"`
At *time.Time `json:"at,omitempty"`
Entries int `json:"entries"`
// Current says it is the trunk's newest revision, which is kept whatever pins it.
Current bool `json:"current,omitempty"`
}
// RevisionsAnswer is what indexer_catalog_revisions answers: every kept revision, the entries stored once by digest, the
// store's fill against its cap, and each object it could not read.
type RevisionsAnswer struct {
Revisions []RevisionSummary `json:"revisions"`
Entries int `json:"entries"`
Unlisted []string `json:"unlisted,omitempty"`
Fill meshcatalog.Fill `json:"fill"`
FillUnread string `json:"fill-unread,omitempty"`
Unread []string `json:"unread,omitempty"`
}
// ListRevisions reads every revision the objects keep, candidates included (kind "candidate" alone when candidates).
func ListRevisions(ctx context.Context, o Objects, candidates bool) (RevisionsAnswer, error) {
out := RevisionsAnswer{Revisions: []RevisionSummary{}}
names, err := o.Names(ctx)
if err != nil {
return out, fmt.Errorf("the objects %s cannot be listed: %w", CatalogRevisionsObjects, err)
}
listed := map[string]bool{}
var newest int64
entries := []string{}
for _, n := range names {
if strings.HasPrefix(n, "entry.") {
entries = append(entries, n)
continue
}
data, err := o.Get(ctx, n)
var rev meshcatalog.CatalogRevision
if err == nil {
err = json.Unmarshal(data, &rev)
}
if err != nil {
out.Unread = append(out.Unread, n+": "+err.Error())
continue
}
for _, ref := range rev.Entries {
listed[EntryObject(ref.Digest)] = true
}
if rev.Kind == "trunk" && rev.Revision > newest {
newest = rev.Revision
}
if candidates && rev.Kind != "candidate" {
continue
}
out.Revisions = append(out.Revisions, RevisionSummary{Name: n, Kind: rev.Kind, Revision: rev.Revision, Source: rev.Source,
Group: rev.Group, Heads: rev.Heads, At: rev.At, Entries: len(rev.Entries)})
}
for i := range out.Revisions {
out.Revisions[i].Current = out.Revisions[i].Kind == "trunk" && out.Revisions[i].Revision == newest
}
slices.SortFunc(out.Revisions, func(a, b RevisionSummary) int { return strings.Compare(a.Name, b.Name) })
out.Entries = len(entries)
for _, e := range entries {
if !listed[e] {
out.Unlisted = append(out.Unlisted, e)
}
}
slices.Sort(out.Unlisted)
used, capacity, err := o.Fill(ctx)
out.Fill = meshcatalog.Fill{Bytes: used, Cap: capacity, Store: CatalogRevisionsObjects}
if err != nil {
out.FillUnread = err.Error()
}
return out, nil
}
// removeUnlisted deletes every entry no kept revision lists any more: an entry is stored once and shared, so it goes
// only with the last revision that lists it. It answers the entries removed.
func removeUnlisted(ctx context.Context, o Objects) ([]string, error) {
list, err := ListRevisions(ctx, o, false)
if err != nil {
return nil, err
}
if len(list.Unread) > 0 {
// A revision that cannot be read may list any entry: none is removed rather than one it still needs.
return nil, fmt.Errorf("no entry is removed while a revision cannot be read: %s", strings.Join(list.Unread, "; "))
}
var removed []string
for _, e := range list.Unlisted {
if err := o.Delete(ctx, e); err != nil {
return removed, fmt.Errorf("%s: %w", e, err)
}
removed = append(removed, e)
}
return removed, nil
}
// DropCandidateGraph drops a candidate's catalog half by hand (indexer_drop_candidate_graph, design 47: for a candidate
// whose gate event never came), the entries no other revision lists with it, and says room when that takes the store
// below its filling mark, which takes in a merge held for objects full (RoomAgain).
func (ix *Indexer) DropCandidateGraph(ctx context.Context, group, heads, by, why string) TakenIn {
name := RevisionObject(meshcatalog.CatalogRevision{Kind: "candidate", Group: group, Heads: heads})
r := TakenIn{Module: name}
if ix.Revisions == nil {
r.Answer = "the indexer keeps no catalog graph revisions: nothing dropped"
return r
}
if _, err := ix.Revisions.Get(ctx, name); err != nil {
r.Answer = name + " is not kept (" + err.Error() + "): nothing dropped"
return r
}
ix.keepMu.Lock()
err := ix.Revisions.Delete(ctx, name)
var removed []string
if err == nil {
removed, err = removeUnlisted(ctx, ix.Revisions)
}
ix.keepMu.Unlock()
if err != nil {
r.Answer = name + " could not be dropped whole: " + err.Error()
ix.logf("%s", r.Answer)
return r
}
r.Stored, r.Dropped = true, removed
r.Answer = fmt.Sprintf("%s is dropped by %s (%s), with %d entries no other revision lists", name, by, why, len(removed))
if roomed, err := ix.RoomAgain(ctx); err != nil {
r.Answer += "; room is not evaluated: " + err.Error()
} else if roomed {
r.Answer += "; room said, what waited for it evaluated again"
}
ix.logf("%s", r.Answer)
return r
}
// RecomputeByHand computes the catalog graph again now from the view of the trunk, as a toolchain report or a merge
// does, and compares it with the newest revision kept: the modules whose entry moved, and the revision kept for them
// (none when the recompute equals the one kept).
func (ix *Indexer) RecomputeByHand(ctx context.Context, by, why string) TakenIn {
r := TakenIn{Module: "catalog graph"}
if ix.Revisions == nil {
r.Answer = "the indexer keeps no catalog graph revisions: nothing recomputed"
return r
}
before, _, err := newestTrunk(ctx, ix.Revisions)
if err != nil {
r.Answer = "the newest catalog graph revision cannot be read: " + err.Error()
return r
}
if err := ix.Recompute(ctx); err != nil {
r.Answer = "the recompute failed: " + err.Error()
return r
}
after, _, err := newestTrunk(ctx, ix.Revisions)
if err != nil {
r.Answer = "the newest catalog graph revision cannot be read after the recompute: " + err.Error()
return r
}
r.Stored = true
if after.Revision == before.Revision {
r.Answer = fmt.Sprintf("recomputed by %s (%s): equal to the kept revision %d", by, why, before.Revision)
} else {
r.Handed = changedEntries(before.Entries, after.Entries)
r.Answer = fmt.Sprintf("recomputed by %s (%s): revision %d differs from the kept %d in %s; revision %d is kept", by, why,
after.Revision, before.Revision, strings.Join(r.Handed, ", "), after.Revision)
}
ix.logf("%s", r.Answer)
return r
}
// CatalogRevisionTools are the tools of the catalog graph revisions; ask writes an ask in takeins and waits for the
// indexer's answer, record records a hand act through mesh-hand-acts before a drop is asked.
func CatalogRevisionTools(o Objects, ask func(key string, value []byte) (TakeInAsk, error), now func() time.Time, record func(what, why string) error) []stdio.Tool {
str := func(args map[string]any, k string) string { s, _ := args[k].(string); return strings.TrimSpace(s) }
asked := func(key string, value []byte, err error) (any, error) {
if err != nil {
return nil, err
}
a, err := ask(key, value)
if err != nil {
return nil, err
}
return a.Result, nil
}
return []stdio.Tool{
{Name: CatalogRevisionTool,
Description: "One catalog graph revision the indexer keeps in its objects catalog-revisions, whole: its list (kind, number, " +
"source revision, trunk heads, a candidate's group and heads) and every entry it lists, each module as its manifest " +
"declares it at that source revision (bases, packages, toolchains, provides, requires, invokes, serves, fingerprint, " +
"acting rules, inputs). The trunk's newest with no input; revision a kept trunk revision; group and heads a candidate's. " +
"An entry the store lost is an error naming it. indexer_catalog_revisions lists what is kept.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"revision": map[string]any{"type": "integer", "description": "a kept trunk revision's number"},
"group": map[string]any{"type": "string", "description": "a candidate's group, with heads"},
"heads": map[string]any{"type": "string", "description": "a candidate's heads, with group"}}},
Run: func(args map[string]any) (any, error) {
ctx := context.Background()
n, _ := args["revision"].(float64)
name := ""
switch {
case str(args, "group") != "" || str(args, "heads") != "":
name = RevisionObject(meshcatalog.CatalogRevision{Kind: "candidate", Group: str(args, "group"), Heads: str(args, "heads")})
case n > 0:
name = "trunk." + strconv.FormatInt(int64(n), 10)
default:
newest, ok, err := newestTrunk(ctx, o)
if err != nil {
return nil, fmt.Errorf("the catalog graph revisions cannot be read: %w", err)
}
if !ok {
return nil, ErrNoRevision
}
name = RevisionObject(newest)
}
rev, entries, err := ReadRevision(ctx, o, name)
if err != nil {
return nil, err
}
return map[string]any{"name": name, "revision": rev, "entries": entries}, nil
}},
{Name: CatalogRevisionsTool,
Description: "Every catalog graph revision the indexer keeps in its objects catalog-revisions, trunk and candidate: each " +
"one's name, kind, number, source revision, when, how many entries it lists and whether it is the trunk's newest " +
"(current); how many entries are stored once by digest, those no revision lists (unlisted), and the store's fill " +
"in bytes against its cap (filling is said at 80 %, full refuses the next revision). An object it cannot read is " +
"named in unread. indexer_catalog_revision reads one whole.",
Input: map[string]any{"type": "object", "properties": map[string]any{}},
Run: func(map[string]any) (any, error) {
return ListRevisions(context.Background(), o, false)
}},
{Name: CandidateGraphsTool,
Description: "Every candidate's catalog half the indexer keeps (a delivery group's or a pull request's heads merged onto " +
"a trunk source revision): its group, its heads, its revision, the source revision it is merged onto, since when, " +
"and how many entries it lists; and the store's fill. indexer_drop_candidate_graph drops one whose gate event never came.",
Input: map[string]any{"type": "object", "properties": map[string]any{}},
Run: func(map[string]any) (any, error) {
return ListRevisions(context.Background(), o, true)
}},
{Name: RecomputeTool,
Description: "Compute the catalog graph again now, as the indexer does on a toolchain report or a merge, from the view of " +
"the trunk (each module's manifest, its bases, the agreed toolchains and the published packages), and compare it with " +
"the newest revision kept: answers equal, or the modules whose entry differs and the revision kept for them.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"why": map[string]any{"type": "string", "description": "why it is recomputed now, in plain words"}}},
Run: func(args map[string]any) (any, error) {
key, value, err := NewRecomputeAsk(str(args, "why"), stdio.CallerOf(args), now())
return asked(key, value, err)
}},
{Name: DropCandidateGraphTool,
Description: "Drop one candidate's catalog half by hand, for a candidate whose gate event never came: its revision and the " +
"entries no other revision lists. A hand act, recorded through mesh-hand-acts before it is asked. When the drop takes " +
"the store below its filling mark, room is said and a merge held for objects full is taken in. indexer_candidate_graphs " +
"names the candidates.",
Input: map[string]any{"type": "object", "required": []string{"group", "heads", "why"}, "properties": map[string]any{
"group": map[string]any{"type": "string", "description": "the candidate's group"},
"heads": map[string]any{"type": "string", "description": "the candidate's heads"},
"why": map[string]any{"type": "string", "description": "why it is dropped, in plain words"}}},
Run: func(args map[string]any) (any, error) {
key, value, err := NewDropCandidateGraphAsk(str(args, "group"), str(args, "heads"), str(args, "why"), stdio.CallerOf(args), now())
if err != nil {
return nil, err
}
if err := record("dropped the catalog half of the candidate "+str(args, "group")+" at "+str(args, "heads")+" by hand", str(args, "why")); err != nil {
return nil, fmt.Errorf("the hand act was not recorded through mesh-hand-acts, so nothing was dropped: %w", err)
}
return asked(key, value, nil)
}},
}
}
@@ -66,12 +66,17 @@ type TakeInAsk struct {
// packages as they are read (indexer_fingerprints_again, fingerprints.go).
Fingerprints bool `json:"fingerprints,omitempty"`
// RecoverTries asks the open tries' candidates to be recovered now (indexer_recover_tries).
RecoverTries bool `json:"recover-tries,omitempty"`
Drop bool `json:"drop,omitempty"`
By string `json:"by,omitempty"`
At time.Time `json:"at"`
Result *TakenIn `json:"result,omitempty"`
Answered time.Time `json:"answered,omitzero"`
RecoverTries bool `json:"recover-tries,omitempty"`
// Recompute asks the catalog graph to be computed again now and compared with the newest kept revision (indexer_recompute).
Recompute bool `json:"recompute,omitempty"`
// CandidateGroup and CandidateHeads, with Drop, ask that candidate's catalog half to be dropped (indexer_drop_candidate_graph).
CandidateGroup string `json:"candidate-group,omitempty"`
CandidateHeads string `json:"candidate-heads,omitempty"`
Drop bool `json:"drop,omitempty"`
By string `json:"by,omitempty"`
At time.Time `json:"at"`
Result *TakenIn `json:"result,omitempty"`
Answered time.Time `json:"answered,omitzero"`
}
// Done says the ask is answered.
@@ -86,7 +91,7 @@ func DecodeTakeIn(data []byte) (TakeInAsk, error) {
if a.V != 1 {
return a, fmt.Errorf("a takeins value of version %d, not 1", a.V)
}
if a.Module == "" && a.Repository == "" && a.Pending == "" && a.Merge == "" && a.TryCandidate == "" && !a.RecoverTries && !a.Fingerprints && !a.CatchUp {
if a.Module == "" && a.Repository == "" && a.Pending == "" && a.Merge == "" && a.TryCandidate == "" && !a.RecoverTries && !a.Fingerprints && !a.CatchUp && !a.Recompute && a.CandidateGroup == "" {
return a, errors.New("a takeins value naming no module, repository, pending wait, merge or try")
}
return a, nil
@@ -189,8 +189,10 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
want = append(want, "indexer_first_views", "indexer_first_view_again", index.ConfigReadersTool, index.BusStateTool, index.RequirementsTool, index.PackageUsersTool, index.SayAcceptedTool, // DLV-F3r, DLV-I10, DLV-I11, DLV-I7, DLV-I8, DLV-I9c
index.SeatDefinitionTool, index.SeatDefinitionsAgainTool, index.OutOfStepTool, // DLV-I19
index.HoldsTool, index.EngineReadsTool, // ADR 0632 §2: the mesh-wide holds and the engines' reads they weigh
index.PendingTool, index.TakeInPendingTool, index.DropPendingTool, index.TryCandidatesTool, index.DropTryCandidateTool, index.RecoverTriesTool, index.TakeInMergeTool, index.FingerprintsAgainTool) // issue 1966; 1585/T12, T13 (ADR 0633 §5)
if !reflect.DeepEqual(m.Tools, want) || len(want) != 40 || slices.Contains(m.Tools, "modules") {
index.PendingTool, index.TakeInPendingTool, index.DropPendingTool, index.TryCandidatesTool, index.DropTryCandidateTool, index.RecoverTriesTool, index.TakeInMergeTool, index.FingerprintsAgainTool, // issue 1966; 1585/T12, T13 (ADR 0633 §5)
// milestone 47/G W4.12 (design 47, the indexer's tools): the catalog graph revisions read and steered.
index.CatalogRevisionTool, index.CatalogRevisionsTool, index.CandidateGraphsTool, index.RecomputeTool, index.DropCandidateGraphTool)
if !reflect.DeepEqual(m.Tools, want) || len(want) != 45 || slices.Contains(m.Tools, "modules") {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
// Every read tool of the indexer a reader of this repository invokes is one it serves (the checker's seventh first
+6 -1
View File
@@ -56,7 +56,12 @@
"indexer_drop_try_candidate",
"indexer_recover_tries",
"indexer_take_in_merge",
"indexer_fingerprints_again"
"indexer_fingerprints_again",
"indexer_catalog_revision",
"indexer_catalog_revisions",
"indexer_candidate_graphs",
"indexer_recompute",
"indexer_drop_candidate_graph"
],
"consumes": [
"go-package-registry.published",