Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/moves.go
T
jschoubben 4e4601a1eb Take-in and forge contracts live with their owners: the indexer's takein package, the forge bridge's forgemsg package; mesh-sdk pinned at main (issue 1585, ADR 0633)
The operator, 2026-11-04: logic one module owns lives inside that module as a package of its own, so the take-in step
changes no mesh-sdk. TakeIn, its keys and event names, PendingWait and PendingAnswer move to
modules/mesh-delivery-indexer/takein (its merges a takein.Merge of its own); Forge whole, with its group fields,
ForgeGroupEnd and GroupMerge, moves to modules/mesh-delivery-forge-bridge/forgemsg. Both are nested Go modules that their
readers take by replace, as buildrun is. Every importer moves in this commit: the indexer, the bridge, the watchman and
the view. Forge's private checks (the repository name, the version) are copied small; mesh-sdk main still holds an unused
Forge until its own fold removes it.

The indexer, bridge, watchman, view and checker pin mesh-sdk 5a38a12 (origin/main), no feat/take-in commit.

The bridge's integration bus now grants the state end-asks that bridge_group_end_again added: without it every test of
the package failed on a permissions violation. Test budget: two new test functions (TestForge, TestTakeInEvents) and the
two the join added are paid by merging four pairs in internal/bridge; the repository stays at 806 unit tests.

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
2026-11-05 00:05:01 +01:00

132 lines
5.4 KiB
Go

package index
import (
"context"
"fmt"
"maps"
"slices"
"strings"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// heldByAnotherTrunk reads, for a module the trunk of repository no longer holds, whether the trunk of another repository
// holds it at the head the latest source names: the module directories there are asked of the forge (Trunk) and the manifest of
// each directory named like the module is read (Read). It answers the merge to hand the module over with, or none: a forge
// that cannot say leaves the module to be dropped as it was, said (issue 1367).
func (ix *Indexer) heldByAnotherTrunk(ctx context.Context, module, repository string) (forgemsg.Forge, bool) {
if ix.Trunk == nil || ix.Read == nil {
return forgemsg.Forge{}, false
}
ix.mu.Lock()
heads := maps.Clone(ix.source.heads)
ix.mu.Unlock()
for _, other := range slices.Sorted(maps.Keys(heads)) {
head := heads[other]
if head == "" || delivery.SameRepository(other, repository) {
continue
}
dirs, err := ix.Trunk(ctx, other, head)
if err != nil {
ix.logf("%s at %s: the modules of the trunk cannot be read, so %s is not looked for there: %v", other, head, module, err)
continue
}
for _, d := range dirs {
dir := strings.Trim(d, "/")
if nameOfDirectory(dir) != module {
continue
}
read, err := ix.Read(ctx, other, head, dir)
if err != nil {
ix.logf("%s at %s: %q cannot be read, so %s is not handed over from it: %v", other, head, dir, module, err)
continue
}
raw := read.Manifests[dir]
if read.Trees[dir] == "" || manifestModule(raw) != module {
continue
}
f := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: other, Merge: head, Root: read.Root,
Trees: map[string]string{dir: read.Trees[dir]}, Manifests: map[string]string{dir: raw}}
if l := read.Locks[dir]; len(l) > 0 {
f.Locks = map[string]map[string]string{dir: l}
}
if p := read.Packages[dir]; len(p) > 0 {
f.Packages = map[string][]string{dir: p}
}
ix.logf("%s at %s: the trunk holds the module %s at %q, so it is handed over from there and not deleted (issue 1367)", other, head, module, dir)
return f, true
}
}
return forgemsg.Forge{}, false
}
// nameOfDirectory is the last element of a module directory.
func nameOfDirectory(dir string) string { return dir[strings.LastIndex(dir, "/")+1:] }
// A module moves between repositories by the merges of the delivery group that moves it (novox/hq ADR 0420 clause 7, ADR
// 0501 clause 3; issue 1126: route-proxy leaves the controller's repository, which is archived). The merge of the
// repository it moves to names it while the indexer holds it of another: that merge leaves it, says so, and waits here.
// The merge of the repository that held it, whose trunk no longer holds it, then hands it over: the module is written from
// the waiting merge, in place, so its key never goes missing between the two (dropRemoved). Merged the other way round,
// the first merge deletes the key and the second writes it as a new module. Nothing else moves a module: a merge of a
// repository naming a module whose holding trunk still holds it changes nothing.
//
// The wait is the indexer's memory of a merge it heard, not state of its own: an indexer started between the two merges
// has forgotten it, and the module is deleted by the second; the next merge of the repository it moved to writes it.
// waitToMove keeps, by modules key, the part of a merged message naming one module directory the indexer holds of another
// repository. A later merge of the same repository replaces it.
func (ix *Indexer) waitToMove(key string, f forgemsg.Forge, dir string) {
one := forgemsg.Forge{V: f.V, Kind: f.Kind, Repository: f.Repository, Branch: f.Branch, Number: f.Number, Merge: f.Merge, Root: f.Root,
Trees: map[string]string{dir: f.Trees[dir]}, Manifests: map[string]string{dir: f.Manifests[dir]}}
if l := f.Locks[dir]; len(l) > 0 {
one.Locks = map[string]map[string]string{dir: l}
}
if p := f.Paths[dir]; len(p) > 0 {
one.Paths = map[string]map[string]string{dir: p}
}
if p := f.Packages[dir]; len(p) > 0 {
one.Packages = map[string][]string{dir: p}
}
ix.mu.Lock()
defer ix.mu.Unlock()
if ix.moving == nil {
ix.moving = map[string]forgemsg.Forge{}
}
ix.moving[key] = one
}
// movingTo says a merge of another repository than from waits to take the module at key.
func (ix *Indexer) movingTo(key, from string) bool {
ix.mu.Lock()
defer ix.mu.Unlock()
f, ok := ix.moving[key]
return ok && !delivery.SameRepository(f.Repository, from)
}
// handOver writes the module at key from the merge waiting to take it, and forgets the wait.
func (ix *Indexer) handOver(ctx context.Context, key string) error {
ix.mu.Lock()
f, ok := ix.moving[key]
delete(ix.moving, key)
ix.mu.Unlock()
if !ok {
return nil
}
return ix.handOverFrom(ctx, key, f)
}
// handOverFrom writes the module at key from the merge f, which names it, in place.
func (ix *Indexer) handOverFrom(ctx context.Context, key string, f forgemsg.Forge) error {
w, err := ix.applyMoving(ctx, f, false, map[string]bool{key: true})
if err != nil {
return fmt.Errorf("modules.%s, moving to %s: %w", key, f.Repository, err)
}
// A module moved in is judged as a merge's are (ADR 0578 §1, ADR 0579 §1).
if _, err := ix.JudgeAll(ctx, w.fresh); err != nil {
ix.logf("judge: the manifests are not judged after moving %s to %s: %v", key, f.Repository, err)
}
return nil
}