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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user