diff --git a/modules/mesh-delivery-indexer/internal/index/catalog_revisions.go b/modules/mesh-delivery-indexer/internal/index/catalog_revisions.go index 256e16ff..20a88327 100644 --- a/modules/mesh-delivery-indexer/internal/index/catalog_revisions.go +++ b/modules/mesh-delivery-indexer/internal/index/catalog_revisions.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/sdk/graph" "git.novox.be/novox/mesh-sdk/go/process" @@ -224,3 +225,34 @@ func (ix *Indexer) keepTrunkRevision(ctx context.Context, edges map[string]graph } return nil } + +// TrunkView is the indexer's view of the trunk read from the newest trunk revision the bucket keeps (the lead's ruling +// on milestone 47/G finding 1; ADR 0656 ยง6, one form of the fact): a store in memory holding each module's value as +// ModuleValueOf reads it back from its entry, under its modules key, and the revision it was read at (0 when the bucket +// keeps none yet: the view starts empty and the first merge taken in fills it). A lost entry is an error naming the +// module (ReadRevision), never a module left out of the view. +func TrunkView(ctx context.Context, o Objects) (Store, int64, error) { + view := newOverlay(nil) + newest, any, err := newestTrunk(ctx, o) + if err != nil || !any { + return view, 0, err + } + _, entries, err := ReadRevision(ctx, o, RevisionObject(newest)) + if err != nil { + return nil, 0, err + } + for _, e := range entries { + key, err := meshcatalogclient.ModulesKey(e.Module) + if err != nil { + return nil, 0, fmt.Errorf("trunk.%d: the entry of %q: %w", newest.Revision, e.Module, err) + } + data, err := meshcatalogclient.EncodeModule(ModuleValueOf(e)) + if err != nil { + return nil, 0, fmt.Errorf("trunk.%d: the entry of %s: %w", newest.Revision, e.Module, err) + } + if err := view.Create(ctx, key, data); err != nil { + return nil, 0, fmt.Errorf("trunk.%d: the entry of %s: %w", newest.Revision, e.Module, err) + } + } + return view, newest.Revision, nil +} diff --git a/modules/mesh-delivery-indexer/internal/index/toolchains_test.go b/modules/mesh-delivery-indexer/internal/index/toolchains_test.go index 8d1d2753..e3abfa5e 100644 --- a/modules/mesh-delivery-indexer/internal/index/toolchains_test.go +++ b/modules/mesh-delivery-indexer/internal/index/toolchains_test.go @@ -352,6 +352,23 @@ func TestBasesAreComputedFirstAndNoFingerprintTakesTheBuilder(t *testing.T) { // !slices.ContainsFunc(got, func(e meshcatalog.CatalogEntry) bool { return e.Module == "mesh-tools" && e.Fp == tools.Fp }) { t.Errorf("trunk.2 read back = %+v, %d entries, %v", back, len(got), err) } + // W4.3's second part: the view of the trunk read at start is the newest trunk revision, each module's value as its + // entry gives it back; a bucket that keeps none is an empty view at 0. + view, at, err := TrunkView(context.Background(), kept) + if err != nil || at != 2 { + t.Fatalf("the view is read at %d (%v), want 2", at, err) + } + for name, e := range moved { + key, _ := meshcatalogclient.ModulesKey(name) + raw, _, found, _ := view.Get(context.Background(), key) + got, err := meshcatalogclient.DecodeModule(raw) + if want := ModuleValueOf(e); !found || err != nil || !reflect.DeepEqual(got, want) { + t.Errorf("the view's %s = %+v (%v), want %+v", name, got, err, want) + } + } + if empty, at, err := TrunkView(context.Background(), newMemObjects(0)); err != nil || at != 0 || func() bool { k, _ := empty.Keys(context.Background()); return len(k) != 0 }() { + t.Errorf("an empty bucket's view is read at %d (%v), want an empty view at 0", at, err) + } _ = kept.Delete(context.Background(), EntryObject(next.Entries[0].Digest)) if _, _, err := ReadRevision(context.Background(), kept, "trunk.2"); err == nil || !strings.Contains(err.Error(), next.Entries[0].Module) { t.Errorf("trunk.2 with a lost entry read without error naming it: %v", err)