From 05c180cf87d1a6a06dbb1e319eeb91be9b605a1b Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 00:31:19 +0000 Subject: [PATCH] Add the indexer's source revision logic and its tests that the last commit named (issue 1344) --- .../internal/index/source.go | 80 +++++++++++++ .../internal/index/source_test.go | 106 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 modules/mesh-delivery-indexer/internal/index/source.go create mode 100644 modules/mesh-delivery-indexer/internal/index/source_test.go diff --git a/modules/mesh-delivery-indexer/internal/index/source.go b/modules/mesh-delivery-indexer/internal/index/source.go new file mode 100644 index 0000000..19a5b7b --- /dev/null +++ b/modules/mesh-delivery-indexer/internal/index/source.go @@ -0,0 +1,80 @@ +package index + +import ( + "context" + "fmt" + "maps" + "slices" + + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +// The gate's source revision (novox/hq ADR 0437 §5, ADR 0450 §1): source.main names every repository's merged head, and its +// revision in the bucket is the mesh's source revision. The indexer keeps the latest it has seen. A module is stamped +// with it (Module.Src) when the head the source names for its repository is the merge commit the module's fingerprint +// was computed at: from the modules bucket when the merge was heard first, and when the merge is heard after the source. + +// sourceNow is the latest source revision seen. +type sourceNow struct { + rev uint64 + heads map[string]string +} + +// srcOf is the source revision that names this merge commit as the repository's head, or 0 when the latest source seen +// names another head or none has been seen. +func (ix *Indexer) srcOf(repository, merge string) uint64 { + ix.mu.Lock() + defer ix.mu.Unlock() + if ix.source.rev != 0 && merge != "" && ix.source.heads[repository] == merge { + return ix.source.rev + } + return 0 +} + +// ApplySource takes a revision of the gate's source.main in. An older revision than the latest seen is left. For each +// repository the source names, each module built from it whose fingerprint was computed at the head the source names is +// written again with Src the revision; a module computed at another commit is left (a merge heard later is stamped +// when it is written, applyMerged), and a repository none of the indexer's modules is built from writes nothing. +func (ix *Indexer) ApplySource(ctx context.Context, rev uint64, s delivery.Source) error { + ix.mu.Lock() + if rev < ix.source.rev { + ix.mu.Unlock() + return nil + } + ix.source = sourceNow{rev: rev, heads: maps.Clone(s.Heads)} + ix.mu.Unlock() + if ix.Modules == nil { + return nil + } + repositories := slices.Sorted(maps.Keys(s.Heads)) + for _, repository := range repositories { + head := s.Heads[repository] + for _, k := range ix.Builds.Modules(repository) { + key, err := delivery.ModulesKey(k.Module) + if err != nil { + ix.logf("%s: the module %q has no usable key, skipped: %v", repository, k.Module, err) + continue + } + err = write(ctx, ix.Modules, key, func(value []byte, found bool) ([]byte, bool, error) { + if !found { + return nil, false, nil + } + cur, err := delivery.DecodeModule(value) + if err != nil { + ix.logf("modules.%s holds what is not a module, left: %v", key, err) + return nil, false, nil + } + if cur.Repository != repository || cur.Commit != head || cur.Src == rev { + return nil, false, nil + } + cur.Src = rev + data, err := delivery.EncodeModule(cur) + return data, false, err + }) + if err != nil { + return fmt.Errorf("modules.%s: %w", key, err) + } + } + } + return nil +} diff --git a/modules/mesh-delivery-indexer/internal/index/source_test.go b/modules/mesh-delivery-indexer/internal/index/source_test.go new file mode 100644 index 0000000..8f8fd7a --- /dev/null +++ b/modules/mesh-delivery-indexer/internal/index/source_test.go @@ -0,0 +1,106 @@ +package index + +import ( + "context" + "testing" + + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +const otherMerge = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + +func sourceOf(repo, head string) delivery.Source { + return delivery.Source{Group: "g", Heads: map[string]string{repo: head}} +} + +func TestASourceRevisionStampsTheModulesOfTheRepositoriesHeadItNames(t *testing.T) { // 1344/AC1 + ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs), + rec(t, "build-2", "c", "novox/mesh-catalog", "modules/c", "main", "", "2026-10-19T09:00:00Z", aInputs)) + ctx := context.Background() + if err := ix.Apply(ctx, mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"})); err != nil { + t.Fatal(err) + } + before, _ := moduleOf(t, mods, "a") + if before.Src != 0 { + t.Fatalf("module before the source = %+v, want Src 0", before) + } + // The source names the head merged for the delivery repository, and another repository's head no module of ours is built from. + s := delivery.Source{Group: "g", Heads: map[string]string{"novox/mesh-delivery": merge, "novox/mesh-catalog": otherMerge}} + if err := ix.ApplySource(ctx, 7, s); err != nil { + t.Fatal(err) + } + m, ok := moduleOf(t, mods, "a") + want := before + want.Src = 7 + if !ok || m != want { + t.Errorf("module a = %+v (%v), want %+v: the fingerprint and the commit as the merge wrote them, with Src 7", m, ok, want) + } + if c, ok := moduleOf(t, mods, "c"); ok { + t.Errorf("module c = %+v: written for a head no merge message of the indexer carried", c) + } + // The same revision again changes nothing; a later revision moves Src. + if err := ix.ApplySource(ctx, 7, s); err != nil { + t.Fatal(err) + } + if err := ix.ApplySource(ctx, 9, s); err != nil { + t.Fatal(err) + } + if m, _ := moduleOf(t, mods, "a"); m.Src != 9 { + t.Errorf("module a Src = %d, want 9", m.Src) + } +} + +func TestAMergeHeardAfterTheSourceNamesItsHeadIsStampedWhenWritten(t *testing.T) { // 1344/AC1 (the source first) + ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs)) + ctx := context.Background() + if err := ix.ApplySource(ctx, 7, sourceOf("novox/mesh-delivery", merge)); err != nil { + t.Fatal(err) + } + if _, ok := moduleOf(t, mods, "a"); ok { + t.Fatal("a module was written from a source revision alone: its tree is the merge message's") + } + if err := ix.Apply(ctx, mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"})); err != nil { + t.Fatal(err) + } + if m, ok := moduleOf(t, mods, "a"); !ok || m.Src != 7 || m.Commit != merge || m.FP == "" { + t.Errorf("module a = %+v (%v), want the merge's fingerprint with Src 7", m, ok) + } +} + +func TestAMergeThatNoSourceRevisionNamesIsWrittenAsToday(t *testing.T) { // 1344/AC2 + ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs)) + ctx := context.Background() + // No source revision yet. + if err := ix.Apply(ctx, mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"})); err != nil { + t.Fatal(err) + } + if m, ok := moduleOf(t, mods, "a"); !ok || m.Src != 0 || m.FP == "" { + t.Fatalf("module a = %+v (%v), want a fingerprint with Src 0", m, ok) + } + // A source revision that names another head of the repository: a merge of the head it does not name keeps Src 0. + if err := ix.ApplySource(ctx, 7, sourceOf("novox/mesh-delivery", otherMerge)); err != nil { + t.Fatal(err) + } + if m, _ := moduleOf(t, mods, "a"); m.Src != 0 { + t.Errorf("module a Src = %d after a source naming another head, want 0", m.Src) + } + merged := mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newertree"}) + merged.Merge = "cccccccccccccccccccccccccccccccccccccccc" + if err := ix.Apply(ctx, merged); err != nil { + t.Fatal(err) + } + if m, _ := moduleOf(t, mods, "a"); m.Src != 0 || m.Commit != merged.Merge { + t.Errorf("module a = %+v, want the new merge with Src 0", m) + } +} + +func TestASourceRevisionOlderThanTheOneHeldIsLeft(t *testing.T) { + ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs)) + ctx := context.Background() + _ = ix.Apply(ctx, mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"})) + _ = ix.ApplySource(ctx, 9, sourceOf("novox/mesh-delivery", merge)) + _ = ix.ApplySource(ctx, 7, sourceOf("novox/mesh-delivery", merge)) + if m, _ := moduleOf(t, mods, "a"); m.Src != 9 { + t.Errorf("module a Src = %d, want 9", m.Src) + } +}