From 2a32721b8022c0ab679fa2a563ec0d7d4204542c Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 16:27:07 +0100 Subject: [PATCH] F-delivery 2: the assigner takes the module graph by the indexer's first view (ADR 0631) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The assigner read the indexer's graph state itself, the one reader left after ADR 0615 §1. The indexer serves graph as a seventh first-view tool to the assigner alone; the assigner asks it, follows views.graph, raises the first-view condition past the bound while the indexer is down, and its reads of graph goes. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../integration_test.go | 43 +++++++++++++++---- .../cmd/mesh-delivery-assigner/main.go | 14 ++---- .../cmd/mesh-delivery-assigner/verbs.go | 4 +- .../cmd/mesh-delivery-assigner/views.go | 24 ++++++++--- .../mesh-delivery-assigner/manifest_test.go | 14 +++--- modules/mesh-delivery-assigner/module.json | 3 +- .../internal/index/views.go | 3 +- .../mesh-delivery-indexer/manifest_test.go | 19 +++++--- modules/mesh-delivery-indexer/module.json | 4 +- 9 files changed, 85 insertions(+), 43 deletions(-) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go index 2441d816..525135b3 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/integration_test.go @@ -35,6 +35,7 @@ import ( "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" "git.novox.be/novox/mesh-delivery/sdk/enginereads" + "git.novox.be/novox/mesh-delivery/sdk/graph" "git.novox.be/novox/mesh-sdk/go/views" "mesh-delivery-assigner/internal/assign" @@ -369,7 +370,15 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { // after the assigner asked; within the bound the assigner writes the wants from what they answer. up := make(chan struct{}) indexerLate = up - t.Cleanup(func() { indexerLate = nil }) + // ADR 0631 §2: while the indexer does not answer, past the first-view bound the assigner raises the condition naming + // the indexer and graph, and reads nothing of the indexer's state; the answer clears it. + firstViewBound, firstViewWait = 300*time.Millisecond, 50*time.Millisecond + t.Cleanup(func() { indexerLate, firstViewBound, firstViewWait = nil, 0, 0 }) + for _, name := range append(append([]string{}, readBuckets...), watched...) { + if strings.HasPrefix(name, delivery.IndexerModule+"_") { + t.Errorf("the assigner reads the indexer's state %s itself", name) + } + } b := startBed(t, "") fNew, fOld := delivery.SourcePrefix2+strings.Repeat("b", 64), delivery.SourcePrefix2+strings.Repeat("a", 64) digest := b.manifest(t, "web", `{"module":"web","version":"1"}`) @@ -406,7 +415,22 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { "seat-holders": {"n1.node-tools"}, "wants": {"n1.mesh-grants"}, }) + graphKey, _, _ := views.UnansweredFinding(delivery.AssignerModule, views.Status{Owner: delivery.IndexerModule, State: graph.State}, time.Now()) + graphRaised := func() bool { + e, err := b.buckets[assign.FindingsBucket].Get(b.ctx, graphKey) + return err == nil && strings.Contains(string(e.Value()), delivery.IndexerModule+".graph") + } + for deadline := time.Now().Add(10 * time.Second); !graphRaised(); time.Sleep(50 * time.Millisecond) { + if time.Now().After(deadline) { + t.Fatalf("with the indexer down, no condition names its graph (finding %s)", graphKey) + } + } close(up) + for deadline := time.Now().Add(10 * time.Second); graphRaised(); time.Sleep(50 * time.Millisecond) { + if time.Now().After(deadline) { + t.Fatalf("the indexer answered, and the condition naming its graph stands (finding %s)", graphKey) + } + } wkey, _ := delivery.WantKey("ace", "web") want, err := delivery.DecodeWant(b.get(t, delivery.AssignerWantsBucket, wkey)) @@ -1407,13 +1431,13 @@ func placementSubjects() []string { return out } -// indexerLate, when set before the bed is made, holds every answer of the indexer's first views until it is closed: the -// indexer's runtime started together with the assigner's, answering after the assigner asked (DLV-F5r). +// indexerLate, when set before the bed is made, fails every ask of the indexer's first views until it is closed: the +// indexer down, its runtime coming up after the assigner asked (DLV-F5r, ADR 0631 §2). var indexerLate chan struct{} -// serveIndexer is the indexer as the owner of its manifests and modules (ADR 0615, ADR 0621): its buckets, which a test -// fills as the indexer writes them; its views family said by the views emitter onto INDEXER_VIEWS, read by the -// assigner's durable consumer; and its first-view tools manifests and modules answered from the buckets at their marks. +// serveIndexer is the indexer as the owner of its manifests, modules and module graph (ADR 0615, ADR 0621, ADR 0631): its +// buckets, which a test fills as the indexer writes them; its views family said by the views emitter onto INDEXER_VIEWS, +// read by the assigner's durable consumer; and its first-view tools of the three answered from the buckets at their marks. func serveIndexer(t *testing.T, b *bed, nc *nats.Conn) { t.Helper() marks, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: delivery.IndexerModule + "_" + views.MarksState, History: 1}) @@ -1428,7 +1452,7 @@ func serveIndexer(t *testing.T, b *bed, nc *nats.Conn) { } em := views.Emitter{Owner: delivery.IndexerModule, Subject: func(e string) string { return process.ModuleStreamSubject(delivery.IndexerModule, e) }, Publish: views.JetStreamPublish(b.js), Marks: marks} - for state, bucket := range map[string]string{"manifests": delivery.ManifestsBucket, "modules": delivery.ModulesBucket} { + for state, bucket := range indexerFirstViews { kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: bucket, History: 5}) if err != nil { t.Fatal(err) @@ -1439,10 +1463,11 @@ func serveIndexer(t *testing.T, b *bed, nc *nats.Conn) { } late := indexerLate sub, err := nc.Subscribe("mesh.mod.mesh-delivery-indexer.call."+state+".>", func(m *nats.Msg) { - if late != nil { // its runtime comes up after the assigner started (DLV-F5r) + if late != nil { // its runtime comes up after the assigner started (DLV-F5r): until then every ask fails, as on a down indexer select { case <-late: - case <-b.ctx.Done(): + default: + _ = m.Respond([]byte(`{"error":"the indexer's runtime is not up"}`)) return } } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go index ca7b8012..e40a2169 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/main.go @@ -30,7 +30,6 @@ import ( "git.novox.be/novox/mesh-sdk/go/provisions" "git.novox.be/novox/mesh-delivery/sdk/enginereads" - "git.novox.be/novox/mesh-delivery/sdk/graph" "git.novox.be/novox/mesh-sdk/go/views" "mesh-delivery-assigner/internal/assign" @@ -65,7 +64,6 @@ var readBuckets = []string{ provisions.ProvisionsBucket, delivery.SourceBucket, delivery.MergesBucket, - graph.Bucket, } // readFamilies are the states kept one bucket per node the assigner reads, each named in its manifest's reads: every @@ -163,11 +161,6 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any logf("the bucket %s cannot be opened (%v): no release's Approve answers a record ask until it is bound", name, err) continue } - if name == graph.Bucket && err != nil { - // The indexer's module graph (ADR 0614 §2, §3): without it no want waits for a verb it invokes until it is bound. - logf("the bucket %s cannot be opened (%v): no want waits for a verb it invokes until it is bound once the bus says it was made", name, err) - continue - } if name == engineReadsBucket && err != nil { // New with ADR 0618 §3: without it the statements the verb answers are kept in memory only, and // assigner_engine_reads lists nothing. @@ -192,7 +185,7 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any func startsWithout(name string) bool { switch name { case delivery.SeatHoldersBucket, provisions.ProvisionsBucket, assign.PoliciesBucket, assign.FindingsBucket, - delivery.AssignerPutsBucket, delivery.SourceBucket, delivery.MergesBucket, graph.Bucket, engineReadsBucket: + delivery.AssignerPutsBucket, delivery.SourceBucket, delivery.MergesBucket, engineReadsBucket: return true } return slices.Contains(seatBuckets, name) || name == delivery.PartGrantsBucket @@ -290,7 +283,6 @@ var watched = []string{ delivery.SeatHoldersBucket, delivery.SourceBucket, delivery.MergesBucket, - graph.Bucket, engineReadsBucket, } @@ -409,8 +401,8 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber, return err } } - // The indexer's manifests and modules, through its first views and views family, never its buckets (DLV-F2r): the - // first pass waits for both answers, as it waits for every watch to hand what stands. + // The indexer's manifests, modules and module graph, through its first views and views family, never its buckets + // (DLV-F2r, ADR 0631): the first pass waits for the three answers, as it waits for every watch to hand what stands. if iv != nil { started += iv.start(ctx, w, wake, settled) } diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/verbs.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/verbs.go index 28fc87b9..a67bec9b 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/verbs.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/verbs.go @@ -17,8 +17,8 @@ import ( // each kept edge until a build serving its verb runs healthy, read from the haves, the builder's builds and the indexer's // manifests, as a provider is counted (ADR 0603 §5). -// moduleGraph is the module graph as the indexer last wrote it; a key that does not decode is left out, so its module -// waits on no verb. +// moduleGraph is the module graph as the indexer's first view and views family last said it (ADR 0631); a key that +// does not decode is left out, so its module waits on no verb. func (p *passer) moduleGraph() graph.Graph { g := graph.Graph{} for key, raw := range p.w.read(graph.Bucket) { diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go index 32da2932..14197f0c 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go @@ -16,6 +16,8 @@ import ( "git.novox.be/novox/mesh-sdk/go/views" + "git.novox.be/novox/mesh-delivery/sdk/graph" + "mesh-delivery-assigner/internal/assign" ) @@ -71,7 +73,7 @@ func viewTools(getenv func(string) string, state func(string) views.Source, now for _, t := range readTools { out = append(out, views.ReadTool(delivery.AssignerModule, t.tool, t.state, t.words, state(t.state), marks, now)) } - // Its own first views, of the indexer's manifests and modules (DLV-F3r): read, and asked again by hand. + // Its own first views, of the indexer's manifests, modules and graph (DLV-F3r, ADR 0631 §2): read, and asked again by hand. return append(out, views.StatusTools(delivery.AssignerModule, "assigner_first_views", "assigner_first_view_again", stdio.State(views.StatusState), handacts.Recorder{Ask: stdio.Ask}, now)...) } @@ -113,8 +115,8 @@ func startViews(ctx context.Context, js jetstream.JetStream, nc process.Subscrib // firstViewsBucket is the assigner's own state of its first views (views.StatusState). var firstViewsBucket = delivery.AssignerModule + "_" + views.StatusState -// indexerViews are the assigner's first views of the indexer's manifests and modules (ADR 0615 §1, ADR 0621 §2; -// DLV-F2r): asked at the start through the indexer's tools, then followed on indexer-views, asking once more on a gap; +// indexerViews are the assigner's first views of the indexer's manifests, modules and module graph (ADR 0615 §1, +// ADR 0621 §2, ADR 0631; DLV-F2r): asked at the start through the indexer's tools, then followed on indexer-views, asking once more on a gap; // past the bound its finding names the indexer and the tool, cleared by the answer. type indexerViews struct { js jetstream.JetStream @@ -125,7 +127,16 @@ type indexerViews struct { logf func(string, ...any) } -// start asks both first views, each answer taken into w under the indexer's bucket names and settled once (a pass waits +// indexerFirstViews are the indexer's states the assigner takes by first view and the views family, each by the name of +// the indexer's bucket the world keeps it under: manifests and modules (ADR 0621 §2), and the module graph, whose verb +// edges a want waits on (ADR 0614 §2, §3; ADR 0631 §1). +var indexerFirstViews = map[string]string{"manifests": delivery.ManifestsBucket, "modules": delivery.ModulesBucket, graph.State: graph.Bucket} + +// firstViewBound and firstViewWait are each first view's bound before its condition is raised, and its first wait +// between two failed asks; zero is the views package's (views.Bound, five seconds). A test shortens them. +var firstViewBound, firstViewWait time.Duration + +// start asks the three first views, each answer taken into w under the indexer's bucket names and settled once (a pass waits // for it), and hears the views family; it answers how many settles to wait for. func (iv *indexerViews) start(ctx context.Context, w *world, wake func(), settled chan<- struct{}) int { finding := func(s views.Status, raised bool) { @@ -144,10 +155,11 @@ func (iv *indexerViews) start(ctx context.Context, w *world, wake func(), settle } followers := map[string]*views.Follower{} var all []*views.Follower - for state, bucket := range map[string]string{"manifests": delivery.ManifestsBucket, "modules": delivery.ModulesBucket} { + for state, bucket := range indexerFirstViews { bucket := bucket tool := delivery.IndexerModule + "." + state f := &views.Follower{Owner: delivery.IndexerModule, Tool: tool, State: state, Ask: views.AskVia(iv.call, tool), Logf: iv.logf, + Bound: firstViewBound, Wait: firstViewWait, Raise: func(s views.Status) { finding(s, true) }, Clear: func(s views.Status) { finding(s, false) }, Changed: func(key string, value json.RawMessage, deleted bool, at time.Time) { if deleted { @@ -176,7 +188,7 @@ func (iv *indexerViews) start(ctx context.Context, w *world, wake func(), settle } stream := catalogue.StreamBusName(indexerViewsStream) if cons, err := iv.account.EventConsumer(ctx, iv.js, stream); err != nil { - iv.logf("the consumer %s on %s does not exist yet (%v): the indexer's manifests and modules are the first views' alone until the assigner starts again", + iv.logf("the consumer %s on %s does not exist yet (%v): the indexer's manifests, modules and graph are the first views' alone until the assigner starts again", iv.account.EventConsumerName(), stream, err) } else if _, err := process.HearEvents(cons, views.Hear(ctx, followers, iv.logf), iv.logf); err != nil { iv.logf("the indexer's views family is not heard (%v) until the assigner starts again", err) diff --git a/modules/mesh-delivery-assigner/manifest_test.go b/modules/mesh-delivery-assigner/manifest_test.go index 1fdb4d29..0b616aa5 100644 --- a/modules/mesh-delivery-assigner/manifest_test.go +++ b/modules/mesh-delivery-assigner/manifest_test.go @@ -36,10 +36,10 @@ func TestManifest(t *testing.T) { reads := slices.Clone(m.Reads) slices.Sort(reads) // ADR 0593 §8: it composes nothing, so it reads no candidate, held or resolved value, compose step or build log; it reads - // every node's published compositions to compare them, and the gate's source and merges, for the release that carries a build (ADR 0601 §1); - // and the indexer's module graph, whose verb edges a want waits on (ADR 0614 §2, §3). - // DLV-F2r (ADR 0615 §1): the indexer's manifests and modules are read through its first views and views family. - want := []string{"mesh-delivery-builder.builds", "mesh-delivery-gate.merges", "mesh-delivery-gate.source", "mesh-delivery-indexer.graph", + // every node's published compositions to compare them, and the gate's source and merges, for the release that carries a build (ADR 0601 §1). + // DLV-F2r (ADR 0615 §1, ADR 0631 §1): the indexer's manifests, modules and module graph (whose verb edges a want waits + // on, ADR 0614 §2, §3) are read through its first views and views family, none of the indexer's states by reads. + want := []string{"mesh-delivery-builder.builds", "mesh-delivery-gate.merges", "mesh-delivery-gate.source", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-grants.grants", "mesh-host.composed", "mesh-provisioner.provisions", "node-engine.have"} if !reflect.DeepEqual(reads, want) { @@ -111,10 +111,10 @@ func TestManifest(t *testing.T) { t.Errorf("streams = %+v, want placement carrying the seat's four events for %d days", m.Streams, delivery.PlacementStreamDays) } // ADR 0618 §4: and it hears each node's engine say its reads changed. - if !slices.Equal(m.Consumes, []string{"mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", "node-engine.manifest-reads-changed"}) { - t.Errorf("consumes = %v, want the indexer's views of manifests and modules and the engines' reads changed", m.Consumes) + if !slices.Equal(m.Consumes, []string{"mesh-delivery-indexer.views.graph", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", "node-engine.manifest-reads-changed"}) { + t.Errorf("consumes = %v, want the indexer's views of graph, manifests and modules and the engines' reads changed", m.Consumes) } - if want := []string{"mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules", + if want := []string{"mesh-delivery-indexer.graph", "mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules", "seat:mesh-hand-acts.hand-act", "seat:mesh-hand-acts.hand-acts", "seat:mesh-hand-acts.warranted", // ADR 0565 §9, §11: the seat alone "seat:node-backup.backed-up", "seat:node-backup.now", "seat:node-engine.manifest-reads"}; !slices.Equal(slices.Sorted(slices.Values(m.Invokes)), want) { // ADR 0618 §3 diff --git a/modules/mesh-delivery-assigner/module.json b/modules/mesh-delivery-assigner/module.json index 08ac20df..423a565a 100644 --- a/modules/mesh-delivery-assigner/module.json +++ b/modules/mesh-delivery-assigner/module.json @@ -28,7 +28,6 @@ "mesh-delivery-builder.builds", "mesh-delivery-gate.merges", "mesh-delivery-gate.source", - "mesh-delivery-indexer.graph", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-grants.grants", @@ -40,11 +39,13 @@ "operator-channel" ], "consumes": [ + "mesh-delivery-indexer.views.graph", "mesh-delivery-indexer.views.manifests", "mesh-delivery-indexer.views.modules", "node-engine.manifest-reads-changed" ], "invokes": [ + "mesh-delivery-indexer.graph", "mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules", "seat:mesh-hand-acts.hand-act", diff --git a/modules/mesh-delivery-indexer/internal/index/views.go b/modules/mesh-delivery-indexer/internal/index/views.go index 777d28f2..8533033c 100644 --- a/modules/mesh-delivery-indexer/internal/index/views.go +++ b/modules/mesh-delivery-indexer/internal/index/views.go @@ -15,12 +15,13 @@ const ViewsStream = "indexer-views" // MarksBucket is the indexer's state of what its views family said each state up to. const MarksBucket = delivery.IndexerModule + "_" + views.MarksState -// FirstViewTools are the indexer's first-view tools (ADR 0621 §1, ADR 0622 §1): the tool, the state it answers, and +// FirstViewTools are the indexer's first-view tools (ADR 0621 §1, ADR 0622 §1, ADR 0631 §1): the tool, the state it answers, and // the words of what that state holds. Each answers only its callers list (CallersEnv), which the manifest writes. var FirstViewTools = []ViewedTool{ {Tool: "manifests", State: "manifests", Words: "every manifest the indexer keeps, by its digest."}, {Tool: "modules", State: "modules", Words: "every module the indexer holds, its fingerprint, inputs and manifest digest."}, {Tool: "verdicts", State: VerdictsState, Words: "the judge's verdict on each manifest: accepted, waiting or refused."}, + {Tool: "graph", State: graph.State, Words: "the module graph: each module's build, provision and verb edges, whose verb edges a want waits on."}, } // ReadTools are the indexer's ordinary read tools, one per state its readers read (design 25 § 12.2). diff --git a/modules/mesh-delivery-indexer/manifest_test.go b/modules/mesh-delivery-indexer/manifest_test.go index f64a1b30..e4ff7b19 100644 --- a/modules/mesh-delivery-indexer/manifest_test.go +++ b/modules/mesh-delivery-indexer/manifest_test.go @@ -143,7 +143,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build // 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle // reads the verdicts state (indexer_verdicts) and asks the indexer to judge again through the takeins state // (indexer_judge; issue 774, clause 3). 1966/AC11: indexer_old_sections reads the count of the old sections (state oldsections). - // ADR 0621 §1, ADR 0622 §1: the first-view tools manifests, modules and verdicts, and design 25 § 12.2's read + // ADR 0621 §1, ADR 0622 §1, ADR 0631 §1: the first-view tools manifests, modules, verdicts and graph, and design 25 § 12.2's read // tools, all of the tools bundle, which runs in a runtime of its own on the indexer's account (runs-as). want := []string{"indexer_take_in", "indexer_old_sections", "indexer_verdicts", "indexer_judge"} for _, v := range append(append([]index.ViewedTool{}, index.FirstViewTools...), index.ReadTools...) { @@ -154,7 +154,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build } want = append(want, "indexer_first_views", "indexer_first_view_again", index.ConfigReadersTool, index.BusStateTool, index.RequirementsTool, index.PackageUsersTool, index.SayAcceptedTool, // DLV-F3r, DLV-I10, DLV-I11, DLV-I7, DLV-I8, DLV-I9c index.SeatDefinitionTool, index.SeatDefinitionsAgainTool, index.OutOfStepTool) // DLV-I19 - if !reflect.DeepEqual(m.Tools, want) || len(want) != 28 { + if !reflect.DeepEqual(m.Tools, want) || len(want) != 29 { t.Errorf("tools = %v, want %v", m.Tools, want) } // Every read tool of the indexer a reader of this repository invokes is one it serves (the checker's seventh first @@ -180,14 +180,16 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build if m.RunsAs != m.Module || m.SecretsOwner != m.RunsAs { t.Errorf("runs-as = %q, secrets-owner %q: the tools bundle runs on the indexer's own account (ADR 0621 §1)", m.RunsAs, m.SecretsOwner) } - // ADR 0621 §2, ADR 0622 §1: each first-view tool answers exactly its readers; the indexer is on none. - callers := map[string]string{"manifests": "mesh-grants,node-tools,mesh-delivery-assigner", "modules": "mesh-grants,node-tools,mesh-delivery-assigner", "verdicts": "mesh-grants"} + // ADR 0621 §2, ADR 0622 §1, ADR 0631 §1: each first-view tool answers exactly its readers; the indexer is on none, and + // graph answers the assigner alone. + callers := map[string]string{"manifests": "mesh-grants,node-tools,mesh-delivery-assigner", "modules": "mesh-grants,node-tools,mesh-delivery-assigner", "verdicts": "mesh-grants", + "graph": "mesh-delivery-assigner"} for _, a := range m.Build.Artifacts { if a.Name != "tools" { continue } if len(a.Env) != len(callers) { - t.Errorf("tools env = %v, want only the three callers lists", a.Env) + t.Errorf("tools env = %v, want only the four callers lists", a.Env) } for tool, list := range callers { if got := a.Env[views.CallersEnv(tool)]; got != list { @@ -197,6 +199,13 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build t.Errorf("%s answers the indexer itself", tool) } } + // ADR 0631 How it is checked: graph refuses every caller but the assigner, the indexer itself included. + graphCallers := views.ParseCallers(a.Env[views.CallersEnv("graph")]) + for caller, refused := range map[string]bool{"n1.mesh-delivery-assigner": false, "n1.mesh-grants": true, "n1.node-tools": true, "n1." + m.Module: true} { + if err := views.Refusal("graph", graphCallers, caller); (err != nil) != refused { + t.Errorf("graph to %s: refused %v, want refused %v", caller, err, refused) + } + } } // ADR 0615 §5, ADR 0619 §2: its views family on its old streams section, 7 days and 256 MiB, dropping its oldest. // Design 26: seat-definition-changed is kept seven days (DLV-I17). diff --git a/modules/mesh-delivery-indexer/module.json b/modules/mesh-delivery-indexer/module.json index ce436d7a..dc97a71f 100644 --- a/modules/mesh-delivery-indexer/module.json +++ b/modules/mesh-delivery-indexer/module.json @@ -23,6 +23,7 @@ "manifests", "modules", "verdicts", + "graph", "indexer_read_manifests", "indexer_read_modules", "indexer_read_predefined", @@ -159,7 +160,8 @@ "env": { "MESH_VIEW_CALLERS_MANIFESTS": "mesh-grants,node-tools,mesh-delivery-assigner", "MESH_VIEW_CALLERS_MODULES": "mesh-grants,node-tools,mesh-delivery-assigner", - "MESH_VIEW_CALLERS_VERDICTS": "mesh-grants" + "MESH_VIEW_CALLERS_VERDICTS": "mesh-grants", + "MESH_VIEW_CALLERS_GRAPH": "mesh-delivery-assigner" } } ]