From d662c63db5c7d121b369505f39b879d24eddfd04 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 12:17:55 +0100 Subject: [PATCH] DLV-J1, DLV-J2, DLV-J3: the foundation is the nine parts, and indexer_old_sections counts the reads left on the owners' state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The judge's foundation holds exactly the nine parts of ADR 0619 §1 and §3, left out of the old sections' count, and refuses a foundation manifest asking bus state in the new form. The indexer also counts, across the accepted manifests, the reads entries still declared on the indexer's and the assigner's state, by owner and by entry; indexer_old_sections answers both, so design 25 § 12.7's live row (0 on each owner) has its tool. Issue 1966. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../cmd/mesh-delivery-indexer-tools/main.go | 10 +++-- .../internal/index/oldsections.go | 9 ++-- .../internal/index/verdicts.go | 2 +- .../internal/index/verdicts_test.go | 25 +++++++++++ .../judge/oldsections.go | 42 +++++++++++++++++-- 5 files changed, 77 insertions(+), 11 deletions(-) diff --git a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer-tools/main.go b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer-tools/main.go index 7361c41b..6eb57494 100644 --- a/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer-tools/main.go +++ b/modules/mesh-delivery-indexer/cmd/mesh-delivery-indexer-tools/main.go @@ -27,9 +27,11 @@ func Tools(ask asker, now func() time.Time, read stdio.KeptState, counts func() str := func(args map[string]any, k string) string { s, _ := args[k].(string); return strings.TrimSpace(s) } return []stdio.Tool{ {Name: index.OldSectionsTool, - Description: "How many accepted modules use each old section of a manifest (state, logs, streams, objects), the foundation " + - "(nats, mesh-grants, the indexer) left out: the count the judge refuses a use of a section at 0 by, as modules move " + - "their bus state into `requires` by template. Read from the indexer's oldsections state, kept as the indexer judges " + + Description: "How many accepted modules use each old section of a manifest (state, logs, streams, objects), the nine parts of " + + "the foundation left out (ADR 0619): the count the judge refuses a use of a section at 0 by, as modules move " + + "their bus state into `requires` by template; and, under reads, the `reads` entries the accepted manifests still declare " + + "on the indexer's and the assigner's state, by owner and by entry, which reads 0 for each owner once every reader asks the " + + "owners' tools instead (ADR 0615 §1). Read from the indexer's oldsections state, kept as the indexer judges " + "the manifests; an empty answer means the indexer has not judged since it started.", Input: map[string]any{"type": "object", "properties": map[string]any{}}, Run: func(map[string]any) (any, error) { @@ -37,7 +39,7 @@ func Tools(ask asker, now func() time.Time, read stdio.KeptState, counts func() if err != nil { return nil, err } - return c.Counts, nil + return map[string]any{"sections": c.Counts, "reads": c.Reads}, nil }}, {Name: index.TakeInTool, Description: "Take a registered module in now: the indexer reads its manifest from the repository and commit the controller's " + diff --git a/modules/mesh-delivery-indexer/internal/index/oldsections.go b/modules/mesh-delivery-indexer/internal/index/oldsections.go index 91d16b98..111f49f1 100644 --- a/modules/mesh-delivery-indexer/internal/index/oldsections.go +++ b/modules/mesh-delivery-indexer/internal/index/oldsections.go @@ -28,6 +28,9 @@ const ( type OldSectionsCount struct { V int `json:"v"` Counts map[string]int `json:"counts"` + // Reads are the `reads` entries left on the indexer's and the assigner's state, by owner and by entry, across the + // accepted manifests (judge.CountOwnerReads; ADR 0615 §1): 0 for each owner once every reader moved. + Reads map[string]int `json:"reads,omitempty"` } // DecodeOldSections reads the oldsections value. @@ -60,17 +63,17 @@ func (ix *Indexer) heldOldSections(ctx context.Context) judge.OldSectionCounts { } // keepOldSections writes the counts under OldSectionsKey when they differ from the ones held. -func (ix *Indexer) keepOldSections(ctx context.Context, counts judge.OldSectionCounts) error { +func (ix *Indexer) keepOldSections(ctx context.Context, counts judge.OldSectionCounts, reads map[string]int) error { if ix.OldSections == nil { return nil } return write(ctx, ix.OldSections, OldSectionsKey, func(value []byte, found bool) ([]byte, bool, error) { if found { - if cur, err := DecodeOldSections(value); err == nil && maps.Equal(cur.Counts, counts) { + if cur, err := DecodeOldSections(value); err == nil && maps.Equal(cur.Counts, counts) && maps.Equal(cur.Reads, reads) { return nil, false, nil } } - next, err := json.Marshal(OldSectionsCount{V: 1, Counts: counts}) + next, err := json.Marshal(OldSectionsCount{V: 1, Counts: counts, Reads: reads}) return next, false, err }) } diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts.go b/modules/mesh-delivery-indexer/internal/index/verdicts.go index 1263a8e1..30454f1e 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts.go @@ -189,7 +189,7 @@ func (ix *Indexer) judgeOver(ctx context.Context, fresh map[string]bool, over ma if over != nil { return out, nil } - if err := ix.keepOldSections(ctx, judge.CountOldSections(accepted)); err != nil { + if err := ix.keepOldSections(ctx, judge.CountOldSections(accepted), judge.CountOwnerReads(accepted)); err != nil { ix.logf("judge: the count of the old sections is not kept: %v", err) } return out, nil diff --git a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go index b8ce4a49..ee2caab7 100644 --- a/modules/mesh-delivery-indexer/internal/index/verdicts_test.go +++ b/modules/mesh-delivery-indexer/internal/index/verdicts_test.go @@ -11,6 +11,8 @@ import ( "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" + + "mesh-delivery-indexer/judge" ) // The indexer judges every manifest it holds and writes each verdict only when it differs; a module's value carries a @@ -179,6 +181,29 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) { if c := counts(); c["state"] != 1 || c["logs"] != 0 || c["streams"] != 0 || c["objects"] != 0 { t.Fatalf("the old sections count %v, want state 1 and the rest 0", c) } + // DLV-J2, DLV-J3 (ADR 0615 §1): one reads entry on the assigner's state counts 1 for the assigner and 0 for the indexer. + hold("reader", `{"module":"reader","version":"1","state":["x"],"reads":["mesh-delivery-assigner.wants"]}`) + if _, err := ix.JudgeAll(ctx, nil); err != nil { + t.Fatal(err) + } + if value, _, _, _ := ix.OldSections.Get(ctx, OldSectionsKey); true { + c, _ := DecodeOldSections(value) + if c.Reads["mesh-delivery-assigner"] != 1 || c.Reads["mesh-delivery-assigner.wants"] != 1 || c.Reads["mesh-delivery-indexer"] != 0 { + t.Fatalf("the reads on the owners' state count %v", c.Reads) + } + } + // DLV-J1 (ADR 0619 §1, §3): a foundation part asking bus state in the new form is refused; the nine are the foundation. + cfg := hold("mesh-config", `{"module":"mesh-config","version":"1","requires":[{"name":"cache","storage":"key-value","template":"state"}]}`) + if _, err := ix.JudgeAll(ctx, map[string]bool{cfg: true}); err != nil { + t.Fatal(err) + } + if v := verdict(cfg); !v.Refused() || !strings.Contains(strings.Join(v.Problems, " "), "part of the foundation") { + t.Fatalf("mesh-config asking new-form bus state: %+v", v) + } + if !slices.Equal(judge.Foundation, []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools", + "mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault"}) { + t.Fatalf("the foundation is %v, want exactly the nine of ADR 0619", judge.Foundation) + } late := hold("late", `{"module":"late","version":"1","logs":[{"name":"l"}]}`) grants := hold("mesh-grants", `{"module":"mesh-grants","version":"2","logs":[{"name":"l"},{"name":"m"}]}`) if _, err := ix.JudgeAll(ctx, map[string]bool{late: true, grants: true}); err != nil { diff --git a/modules/mesh-delivery-indexer/judge/oldsections.go b/modules/mesh-delivery-indexer/judge/oldsections.go index 47181176..b3aa5709 100644 --- a/modules/mesh-delivery-indexer/judge/oldsections.go +++ b/modules/mesh-delivery-indexer/judge/oldsections.go @@ -3,6 +3,7 @@ package judge import ( "fmt" "slices" + "strings" "git.novox.be/novox/mesh-sdk/go/catalogue" ) @@ -15,8 +16,36 @@ import ( // OldSections are the sections of a manifest that keep bus state in the old forms. var OldSections = []string{"state", "logs", "streams", "objects"} -// Foundation are the modules that keep using the old sections for good. -var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer"} +// Foundation are the modules that keep using the old sections for good: exactly the nine parts the bus module's own +// composition, placement, build or delivery needs before it can answer (novox/hq ADR 0619 §1, §3): the bus module, the +// grants part, the indexer, the assigner, the tool runner, the builder, the checker, mesh-config and the vault. Their new +// state goes on their old sections too, so a foundation manifest asking bus state in the new form is refused. +var Foundation = []string{"nats", "mesh-grants", "mesh-delivery-indexer", "mesh-delivery-assigner", "node-tools", + "mesh-delivery-builder", "mesh-delivery-checker", "mesh-config", "mesh-vault"} + +// ViewOwners are the two owners whose state every reader reads through their first views and read tools, never by a +// `reads` entry (novox/hq ADR 0615 §1): CountOwnerReads counts what is left of those entries. +var ViewOwners = []string{"mesh-delivery-indexer", "mesh-delivery-assigner"} + +// CountOwnerReads counts the `reads` entries on each view owner's state, by owner and by entry, across manifests: what +// the live row of the move reads 0 of (novox/hq ADR 0615 How it is checked). Every owner is in the answer, 0 where none. +func CountOwnerReads(manifests []catalogue.Manifest) map[string]int { + counts := map[string]int{} + for _, o := range ViewOwners { + counts[o] = 0 + } + for _, m := range manifests { + for _, r := range m.Reads { + for _, o := range ViewOwners { + if strings.HasPrefix(r, o+".") { + counts[o]++ + counts[r]++ + } + } + } + } + return counts +} // OldSectionCounts is, per old section, how many accepted modules outside the foundation use it. Nil is no count: the // judge then refuses no use of a section. @@ -54,7 +83,14 @@ func CountOldSections(manifests []catalogue.Manifest) OldSectionCounts { // OldSectionProblems are the old sections a manifest uses that no accepted module outside the foundation uses any more. func OldSectionProblems(m catalogue.Manifest, counts OldSectionCounts) []string { - if counts == nil || slices.Contains(Foundation, m.Module) { + if slices.Contains(Foundation, m.Module) { + if len(m.BusState) > 0 { + return []string{fmt.Sprintf("%s is a part of the foundation, which keeps its bus state in the old sections, new "+ + "entries included: it asks none in `requires` by template (novox/hq ADR 0619 §1)", m.Module)} + } + return nil + } + if counts == nil { return nil } var problems []string