From 3a789b7a6bc7e3f60c59e911830bfc5402eef872 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 12:37:33 +0100 Subject: [PATCH] =?UTF-8?q?DLV-O4:=20manifest-accepted=20and=20manifest-re?= =?UTF-8?q?fused=20carry=20the=20verdicts=20state's=20revision=20of=20the?= =?UTF-8?q?=20verdict=20they=20say=20(ADR=200622=20=C2=A72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The grants part follows verdicts by these events after its first view, so each must carry the revision it is at. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../internal/index/verdicts.go | 28 ++++++++++++++----- .../internal/index/verdicts_test.go | 13 ++++++--- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts.go b/modules/mesh-delivery-indexer/internal/index/verdicts.go index 30454f1e..7af32aad 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts.go @@ -152,7 +152,7 @@ func (ix *Indexer) judgeOver(ctx context.Context, fresh map[string]bool, over ma accepted = append(accepted, m) } } - wrote, err := ix.writeVerdict(ctx, digests[name], v) + wrote, revision, err := ix.writeVerdict(ctx, digests[name], v) if errors.Is(err, process.ErrNotYet) { return out, fmt.Errorf("the state %s is not on the bus yet: %w", VerdictsBucket, err) } @@ -171,12 +171,16 @@ func (ix *Indexer) judgeOver(ctx context.Context, fresh map[string]bool, over ma if v.Refused() { ix.logf("judge: %s", v.Why()) if ix.SayRefused != nil { - ix.SayRefused(ctx, delivery.RefusedOf(digests[name], v)) + r := delivery.RefusedOf(digests[name], v) + r.Revision = revision + ix.SayRefused(ctx, r) } } else if ix.SayAccepted != nil { // A manifest asking for bus state, newly accepted, is said whole to the bus module (issue 1966 D3, ADR 0595 §4). if m, problems := judge.Manifest(raws[name], predefined); len(problems) == 0 && len(m.BusState) > 0 { - ix.SayAccepted(ctx, delivery.AcceptedOf(digests[name], v, m)) + a := delivery.AcceptedOf(digests[name], v, m) + a.Revision = revision + ix.SayAccepted(ctx, a) } } } @@ -303,11 +307,13 @@ func (ix *Indexer) acceptedBefore(ctx context.Context, digest string, fresh map[ } // writeVerdict writes v under the digest's key unless the verdict held there says the same (ManifestVerdict.Same); wrote -// says it was written. -func (ix *Indexer) writeVerdict(ctx context.Context, digest string, v delivery.ManifestVerdict) (wrote bool, err error) { +// says it was written, and revision is the verdicts state's revision of that write, which the event saying it carries +// (novox/hq ADR 0622 §2). It is read back after the write: when another write came between, the revision read holds +// another verdict and revision is 0, so a reader takes the later write's own event and never pairs v with its revision. +func (ix *Indexer) writeVerdict(ctx context.Context, digest string, v delivery.ManifestVerdict) (wrote bool, revision uint64, err error) { key, err := delivery.VerdictsKey(digest) if err != nil { - return false, err + return false, 0, err } err = write(ctx, ix.Verdicts, key, func(value []byte, found bool) ([]byte, bool, error) { wrote = false @@ -320,7 +326,15 @@ func (ix *Indexer) writeVerdict(ctx context.Context, digest string, v delivery.M next, err := delivery.EncodeManifestVerdict(v) return next, false, err }) - return wrote, err + if err != nil || !wrote { + return wrote, 0, err + } + if value, rev, found, err := ix.Verdicts.Get(ctx, key); err == nil && found { + if cur, err := delivery.DecodeManifestVerdict(value); err == nil && cur.Same(v) { + revision = rev + } + } + return wrote, revision, nil } // learnOwnBuild asks where the indexer's program is told which build it runs (OwnBuild: the node-engine's seat, never the diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go index ee2caab7..e7920d5e 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go @@ -72,8 +72,10 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { if m := module("good"); m.Verdict != "" { t.Fatalf("modules.good carries %q", m.Verdict) } - if len(said) != 1 || said[0].Module != "bad" || said[0].Digest != bad || said[0].Judge != "judge-1" { - t.Fatalf("said %+v", said) + // ADR 0622 §2: the event carries the verdicts state's revision of the verdict it says. + if _, rev, _, _ := ix.Verdicts.Get(ctx, bad); len(said) != 1 || said[0].Module != "bad" || said[0].Digest != bad || + said[0].Judge != "judge-1" || said[0].Revision == 0 || said[0].Revision != rev { + t.Fatalf("said %+v (the verdict's revision %d)", said, rev) } // A new judge that judges alike writes nothing and says nothing again. ix.JudgeID = "judge-2" @@ -154,6 +156,9 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { if needs := module("good").Needs; !slices.Contains(needs, "word:module") || len(catalogue.MissingManifestReads(needs, nil)) != 0 { t.Fatalf("modules.good needs %v", needs) } + if _, rev, _, _ := ix.Verdicts.Get(ctx, moved); len(accepted) != 1 || accepted[0].Revision == 0 || accepted[0].Revision != rev { + t.Fatalf("said accepted %+v, want the verdict's revision %d (ADR 0622 §2)", accepted, rev) + } 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" { @@ -328,7 +333,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err := ix.writeVerdict(ctx, digest, delivery.ManifestVerdict{Module: module, State: delivery.VerdictRefused, + if _, _, err := ix.writeVerdict(ctx, digest, delivery.ManifestVerdict{Module: module, State: delivery.VerdictRefused, Problems: []string{module + " says tool-runner, which this judge does not know"}, Judge: "judge-0", At: time.Now()}); err != nil { t.Fatal(err) } @@ -336,7 +341,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { } atStart := stale("tools", `{"module":"tools","version":"7"}`) ownRefusal := stale("client", `{"module":"client","version":"7"}`) - if _, err := ix.writeVerdict(ctx, ownRefusal, delivery.ManifestVerdict{Module: "client", State: delivery.VerdictRefused, + if _, _, err := ix.writeVerdict(ctx, ownRefusal, delivery.ManifestVerdict{Module: "client", State: delivery.VerdictRefused, Problems: []string{"x"}, Judge: ix.judgeID(), At: time.Now()}); err != nil { t.Fatal(err) }