A module the tree no longer holds does not stop the others' packages being published (issue 1585)
mesh/delivery merged: source.main moved to novox/mesh-delivery@2e439228275a
mesh/check checking 3422e1a6f8fd on 2e439228275a

publish asked the forge for every published module of the repository in one call; when the merge removed one (seatfiles), the whole call failed and a revision's own new Go modules stayed unpublished, so the take-in waited for a version only its own take-in would publish. packagesAt now reads the modules one by one when the tree lacks a directory and leaves the missing one out, said.
This commit is contained in:
2026-11-07 15:51:38 +01:00
parent 8ad2bf6951
commit 3422e1a6f8
2 changed files with 73 additions and 2 deletions
@@ -149,12 +149,35 @@ func (ix *Indexer) packagesAt(ctx context.Context, repository, commit string, mo
}
}
}
if err := list(slices.Sorted(maps.Values(dirOf))); err != nil {
return nil, err
// The modules are read in one call; when the tree at the commit lacks a directory of them (a module published at an earlier
// merge that this one removed or moved), each is read alone and the missing one is left out, said, so it never keeps the
// others from being published (issue 1585): a revision's own new module is published at the revision's own merge.
absent := map[string]bool{}
dirs := slices.Sorted(maps.Values(dirOf))
if err := list(dirs); err != nil {
if !errors.Is(err, ErrNotInTree) || len(dirs) < 2 {
return nil, err
}
whole := err
for _, d := range dirs {
if err := list([]string{d}); err != nil {
if !errors.Is(err, ErrNotInTree) {
return nil, err
}
ix.logf("%s at %s: the directory %q is not in the tree, so the module there is not read: %v", repository, commit, d, err)
absent[d] = true
}
}
if len(absent) == len(dirs) {
return nil, whole // the tree itself fails: the caller asks once per head (candidatePublished)
}
}
out := map[string]meshcatalog.PackagesByModuleValue{}
for _, m := range slices.Sorted(maps.Keys(dirOf)) {
dir := dirOf[m]
if absent[dir] {
continue
}
requires, pkgs, err := fingerprint.Packages(m, listings[dir])
if err != nil {
ix.logf("%s at %s: %v; it is not published", repository, commit, err)
@@ -4,12 +4,15 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
"slices"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"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"
@@ -207,6 +210,51 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
t.Errorf("said %v, want %v", b.events(), want)
}
})
t.Run("P1 a revision that adds a Go module its own indexer uses is computed against that module's packages at its merge", func(t *testing.T) {
// The Go module exists only at the revision's own merge: nothing publishes it before the take-in, so the fingerprint
// the revision names must come from the packages its merge holds (issue 1585, ADR 0593 §2), not wait for a version only
// its own take-in publishes.
const sibling = "git.novox.be/novox/mesh-catalog/modules/mesh-new/pkg"
b := newTakeInBed(t)
f := b.merge(21, sha('c'), "tree-avahi-3", "tree-indexer-2")
if b.ix.Packages == nil {
b.ix.Packages = newMem()
}
b.ix.GoMod = func(_ context.Context, _, commit, dir string) ([]byte, error) {
if commit == f.Merge && dir == "modules/"+Module {
return []byte("module x\n\nreplace " + sibling + " => ../mesh-new/pkg\n"), nil
}
return []byte("module x\n"), nil
}
b.ix.Files = func(_ context.Context, repository, commit string, dirs []string) (map[string]map[string]string, error) {
out := map[string]map[string]string{}
for _, d := range dirs {
if repository != catalogueRepo || commit != f.Merge || d != "modules/mesh-new/pkg" {
return nil, fmt.Errorf("no directory %s: %w", d, ErrNotInTree)
}
out[d] = map[string]string{"go.mod": "m1", "go.sum": "s1", "x.go": "g1"}
}
return out, nil
}
// A Go module the repository published before and its merge no longer holds does not stop the others being published.
const gone = "git.novox.be/novox/mesh-catalog/modules/mesh-gone/pkg"
old, err := EncodePublishedPackages(meshcatalog.PackagesByModuleValue{Module: gone, Repository: catalogueRepo, Dir: "modules/mesh-gone/pkg",
Commit: sha('a'), Requires: "r", At: ptr(time.Now()), Packages: []meshcatalog.PackagesByModuleValuePackagesItem{{Package: gone, Digest: "d"}}})
if err != nil {
t.Fatal(err)
}
goneKey, _ := PackagesKey(gone)
if err := b.ix.Packages.Create(ctx, goneKey, old); err != nil {
t.Fatal(err)
}
b.arrive(t, 2, f)
if len(b.said) != 1 || !strings.HasPrefix(b.said[0].WaitsFor, "src2:") {
t.Errorf("said %+v, want m.2 waiting for the indexer build its merge's packages give, not for a version no take-in has published", b.said)
}
if _, ok := b.ix.publishedAll(ctx)[sibling]; !ok {
t.Errorf("the packages the revision publishes at its merge lack %s", sibling)
}
})
t.Run("K1 the indexer a revision names is judged with the seat definitions the revision carries", func(t *testing.T) {
// A successor's manifest invokes a verb of the node-engine's seat that the seat list lacks and the revision's
// definitions add (ADR 0633 §1, §6): refused without them, with its reason said on the wait and in indexer_pending;