diff --git a/modules/mesh-delivery-indexer/internal/index/modules.go b/modules/mesh-delivery-indexer/internal/index/modules.go index fece3567..241bd88e 100644 --- a/modules/mesh-delivery-indexer/internal/index/modules.go +++ b/modules/mesh-delivery-indexer/internal/index/modules.go @@ -7,6 +7,7 @@ import ( "slices" "strings" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-delivery-indexer/internal/fingerprint" @@ -95,6 +96,9 @@ func (ix *Indexer) applyMoving(ctx context.Context, f delivery.Forge, drop bool, } dirOf[name] = dir next := delivery.Module{Repository: f.Repository, Commit: f.Merge, Src: ix.srcOf(f.Repository, f.Merge), Manifest: digests[dir], Path: pathOf(dir)} + // The reader capabilities a changed manifest needs are written with it, before the judge's pass names them with its + // verdict, so no reader takes a new manifest as needing nothing (ADR 0614 §4). + next.Needs, _ = catalogue.ManifestNeeds([]byte(raw)) if next.Manifest == "" { next.None2 = "the manifest cannot be read: it cannot be kept as a value" } else { diff --git a/modules/mesh-delivery-indexer/internal/index/modules_test.go b/modules/mesh-delivery-indexer/internal/index/modules_test.go index b7c32974..294e2ff5 100644 --- a/modules/mesh-delivery-indexer/internal/index/modules_test.go +++ b/modules/mesh-delivery-indexer/internal/index/modules_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/process" ) @@ -413,6 +414,11 @@ func TestAMergeAddingAModuleDirectoryWritesItsValueWithNoBuildRecordAnywhere(t * if m.FP2 == "" && m.None2 == "" { t.Errorf("module = %+v, want a new-form fingerprint or its reason", m) } + // ADR 0614 §4: the merge writes the reader capabilities its manifest needs, so no reader takes it as needing nothing + // before the judge's pass. + if want, _ := catalogue.ManifestNeeds([]byte(avahi)); len(m.Needs) == 0 || !slices.Equal(m.Needs, want) { + t.Errorf("module needs %v, want %v", m.Needs, want) + } } func TestAMergeWritesOnlyTheModulesItsManifestsName(t *testing.T) { // I1 diff --git a/modules/mesh-delivery-indexer/internal/index/toolchains.go b/modules/mesh-delivery-indexer/internal/index/toolchains.go index 2b13d2f7..8218c5f7 100644 --- a/modules/mesh-delivery-indexer/internal/index/toolchains.go +++ b/modules/mesh-delivery-indexer/internal/index/toolchains.go @@ -365,8 +365,9 @@ func (ix *Indexer) writeOverlay(ctx context.Context, overlay, finals map[string] held := found && err == nil if held && cur.Manifest == next.Manifest { // The judge's verdict stands while the digest does (ADR 0578 §3, ADR 0579 §1): a recompute never writes a - // refused or outgrown module as accepted; only the judge changes it. + // refused or outgrown module as accepted; only the judge changes it. So do the reader capabilities it named. next.Verdict, next.Problems, next.Unknown, next.Warnings = cur.Verdict, cur.Problems, cur.Unknown, cur.Warnings + next.Needs = cur.Needs } // The source revision at which FP2 last changed (ADR 0601 §1): this merge's when it moved it (0 until the // source revision naming the merge is heard, which stamps it), else the one held. @@ -401,6 +402,7 @@ func sameButWhere(cur, next delivery.Module) bool { next = withV(next) next.Commit, next.Src, next.FPSrc = cur.Commit, cur.Src, cur.FPSrc next.Verdict, next.Problems, next.Unknown, next.Warnings = cur.Verdict, cur.Problems, cur.Unknown, cur.Warnings + next.Needs = cur.Needs return reflect.DeepEqual(cur, next) } diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts.go b/modules/mesh-delivery-indexer/internal/index/verdicts.go index 6d79ec5a..50b72e2d 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts.go @@ -160,7 +160,9 @@ func (ix *Indexer) JudgeAll(ctx context.Context, fresh map[string]bool) (Judged, } } } - if err := ix.carryVerdict(ctx, name, digests[name], v); err != nil { + // The reader capabilities the manifest's own words, forms and seat names need, named with the verdict (ADR 0614 §4). + needs, _ := catalogue.ManifestNeeds(raws[name]) + if err := ix.carryVerdict(ctx, name, digests[name], v, needs); err != nil { ix.logf("judge: modules.%s does not carry its verdict: %v", name, err) } } @@ -257,9 +259,10 @@ func (ix *Indexer) ownBuildIsNewest(held map[string]delivery.Module) bool { } // carryVerdict puts a refusal into the module's value, and takes one out once the manifest is accepted, while the value -// still names the digest judged; an accepted module's value carries nothing, so judging writes no value of a module the +// still names the digest judged, and the reader capabilities the manifest needs beside it (needs, ADR 0614 §4); an +// accepted module's value carries no verdict, so judging writes no value of a module the // judge accepts (a reader takes a value without a verdict as accepted, delivery.TakeManifestVerdict). -func (ix *Indexer) carryVerdict(ctx context.Context, name, digest string, v delivery.ManifestVerdict) error { +func (ix *Indexer) carryVerdict(ctx context.Context, name, digest string, v delivery.ManifestVerdict, needs []string) error { key, err := delivery.ModulesKey(name) if err != nil { return err @@ -280,10 +283,11 @@ func (ix *Indexer) carryVerdict(ctx context.Context, name, digest string, v deli case v.Refused(): state = delivery.VerdictRefused } - if cur.Verdict == state && slices.Equal(cur.Problems, problems) && slices.Equal(cur.Unknown, v.Unknown) && slices.Equal(cur.Warnings, v.Warnings) { + if cur.Verdict == state && slices.Equal(cur.Problems, problems) && slices.Equal(cur.Unknown, v.Unknown) && slices.Equal(cur.Warnings, v.Warnings) && + slices.Equal(cur.Needs, needs) { return nil, false, nil } - cur.Verdict, cur.Problems, cur.Unknown, cur.Warnings = state, problems, v.Unknown, v.Warnings + cur.Verdict, cur.Problems, cur.Unknown, cur.Warnings, cur.Needs = state, problems, v.Unknown, v.Warnings, needs if state == "" { cur.Problems = nil } diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go index 051fe168..c19f5143 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go @@ -4,10 +4,12 @@ import ( "context" "errors" "fmt" + "slices" "strings" "testing" "time" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" ) @@ -113,6 +115,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { // A merge's write of a module whose digest it leaves as it is carries the verdict over, and nothing is fresh; a changed // digest is fresh, judged in full (review R1, R2). w := &mergeWrite{} + oldNeeds := module("old").Needs same := delivery.Module{FP2: "src2:" + strings.Repeat("c", 64), Manifest: changed, Repository: "novox/mesh-catalog", Commit: "c2"} if err := ix.writeOverlay(ctx, map[string]delivery.Module{"old": same}, nil, w); err != nil { t.Fatal(err) @@ -120,6 +123,9 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { if m := module("old"); m.Verdict != delivery.VerdictRefused || len(m.Problems) == 0 || len(w.fresh) != 0 { t.Fatalf("a merge rewrote a refused module as %+v, fresh %v", m, w.fresh) } + if got := module("old").Needs; len(oldNeeds) == 0 || !slices.Equal(got, oldNeeds) { + t.Fatalf("a merge leaving the digest dropped the reader capabilities the judge named: %v, held %v", got, oldNeeds) + } other := strings.Repeat("d", 64) same.Manifest = other if err := ix.writeOverlay(ctx, map[string]delivery.Module{"old": same}, nil, w); err != nil || !w.fresh[other] { @@ -137,6 +143,15 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { t.Fatal(err) } } + // ADR 0614 §4: the judge names, with the verdict, the reader capabilities the manifest's own words need: the bus-state + // words, which an engine stating nothing (the baseline) lacks; a manifest of the baseline's words lacks none. + if needs := module("moved").Needs; !slices.Contains(needs, "word:bus-state.storage") || + !slices.Contains(catalogue.MissingManifestReads(needs, nil), "word:bus-state") { + t.Fatalf("modules.moved needs %v", needs) + } + if needs := module("good").Needs; !slices.Contains(needs, "word:module") || len(catalogue.MissingManifestReads(needs, nil)) != 0 { + t.Fatalf("modules.good needs %v", needs) + } if len(accepted) != 1 || accepted[0].Module != "moved" || accepted[0].Digest != moved || accepted[0].Judge != "judge-2" || len(accepted[0].BusState) != 2 || accepted[0].BusState[0].Name != "cache" || accepted[0].BusState[1].Template != "log" || string(accepted[0].BusState[0].Options["history"]) != "8" {