DLV-O1, DLV-O3, DLV-O4: the indexer answers its first views and says its views family

The indexer's tools bundle runs in a runtime of its own on its own account
(runs-as) and serves the first-view tools manifests, modules and verdicts,
each answering only the callers its manifest writes (ADR 0621 §1-§2, ADR
0622 §1), and the nine read tools of design 25 § 12.2. The process says each
change of its ten read states on the family indexer-views in its old streams
section (7 days, 256 MiB), with a mark per state the first view answers its
revision from, so a reader that takes the answer misses no event after it
(ADR 0615 §4-§5). The shared shape lives in sdk/views: event, answer, mark,
the emitter, the tools and the reader's follower.

Issue 1966.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 12:22:26 +01:00
parent ae31b66a9e
commit 5d7fff91ce
15 changed files with 1582 additions and 19 deletions
@@ -192,7 +192,7 @@ func heldCounts(s stdio.KeptState) func() (index.OldSectionsCount, error) {
}
func main() {
if err := stdio.Serve("", append(Tools(askTakeIn(stdio.State(index.TakeInsState), 25*time.Second), time.Now, stdio.State(index.VerdictsState), heldCounts(stdio.State(index.OldSectionsState))), catalogTools()...)); err != nil {
if err := stdio.Serve("", append(Tools(askTakeIn(stdio.State(index.TakeInsState), 25*time.Second), time.Now, stdio.State(index.VerdictsState), heldCounts(stdio.State(index.OldSectionsState))), append(catalogTools(), viewTools(os.Getenv, keptSource, time.Now)...)...)); err != nil {
fmt.Fprintf(os.Stderr, "[mesh-delivery-indexer-tools] %v\n", err)
os.Exit(1)
}
@@ -0,0 +1,31 @@
package main
import (
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/sdk/views"
"mesh-delivery-indexer/internal/index"
)
// viewTools are the indexer's first-view tools (ADR 0621 §1, ADR 0622 §1), each answering only the callers its manifest
// writes into its env value (views.CallersEnv), and its ordinary read tools (design 25 § 12.2). Every one reads only the
// indexer's own states, so it answers as soon as the runtime of its own is connected (ADR 0621 §3).
func viewTools(getenv func(string) string, state func(string) views.Source, now func() time.Time) []stdio.Tool {
marks := state(views.MarksState)
var out []stdio.Tool
for _, t := range index.FirstViewTools {
out = append(out, views.FirstViewTool(delivery.IndexerModule, t.Tool, t.State, t.Words,
views.ParseCallers(getenv(views.CallersEnv(t.Tool))), state(t.State), marks, now))
}
for _, t := range index.ReadTools {
out = append(out, views.ReadTool(delivery.IndexerModule, t.Tool, t.State, t.Words, state(t.State), marks, now))
}
return out
}
// keptSource is one of the indexer's states as its runtime reads it.
func keptSource(name string) views.Source { return views.KeptSource{State: stdio.State(name)} }
@@ -5,11 +5,13 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"
"time"
@@ -20,6 +22,10 @@ import (
"git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-delivery/sdk/views"
"mesh-delivery-indexer/internal/index"
)
// busConf is the shape of the composed accounts file (mesh-controller internal/broker): one account with JetStream, the
@@ -83,8 +89,115 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10
t.Errorf("%s#%d modules = %v, want [%s]", p.Repository, p.Number, p.Modules, want)
}
}
// ADR 0615 §5 (DLV-O4): each change of the candidates is said on the indexer's views family, its value whole at its
// revision; a first view taken at the mark, followed by the events after it, holds the candidate as the bucket does;
// and a waiter on manifest-accepted never wakes on it.
views0 := waitViews(t, ctx, js, "candidates", "feat/x")
cur, err := candidates.Get(ctx, "feat/x")
if err != nil || views0.Revision > cur.Revision() || views0.Deleted || len(views0.Value) == 0 || !views0.Created.Equal(bucketCreated(t, ctx, candidates)) {
t.Errorf("views event = %+v, bucket at %d (%v)", views0, cur.Revision(), err)
}
marks, _ := js.KeyValue(ctx, index.MarksBucket)
f := &views.Follower{Owner: delivery.IndexerModule, Tool: "indexer_read_candidates", State: "candidates",
Ask: func(context.Context, string) (views.Answer, error) {
return views.FirstView(delivery.IndexerModule, "candidates", jsSource{ctx, candidates}, jsSource{ctx, marks}, "", time.Now())
}}
if err := f.Start(ctx); err != nil {
t.Fatal(err)
}
for _, e := range viewsEvents(t, ctx, js, "candidates") {
if gap, why := f.Apply(e); gap {
t.Errorf("a gap after the first view: %s", why)
}
}
if v, ok := f.Get("feat/x"); !ok || string(v) != string(cur.Value()) {
t.Errorf("the follower holds %s, the bucket %s", v, cur.Value())
}
if n := acceptedHeard.Load(); n != 0 {
t.Errorf("a waiter on manifest-accepted woke %d times on the views family", n)
}
}
// jsSource is a bucket as a views.Source.
type jsSource struct {
ctx context.Context
kv jetstream.KeyValue
}
func (s jsSource) Keys() ([]string, error) {
k, err := s.kv.Keys(s.ctx)
if errors.Is(err, jetstream.ErrNoKeysFound) {
return nil, nil
}
return k, err
}
func (s jsSource) Get(key string) (*views.Entry, bool, error) {
e, err := s.kv.Get(s.ctx, key)
if errors.Is(err, jetstream.ErrKeyNotFound) {
return nil, false, nil
}
if err != nil {
return nil, false, err
}
return &views.Entry{Value: e.Value(), Revision: e.Revision()}, true, nil
}
func bucketCreated(t *testing.T, ctx context.Context, kv jetstream.KeyValue) time.Time {
t.Helper()
st, err := kv.Status(ctx)
if err != nil {
t.Fatal(err)
}
return st.(*jetstream.KeyValueBucketStatus).StreamInfo().Created.UTC()
}
// viewsEvents are every event the views family holds for state, in order, read message by message.
func viewsEvents(t *testing.T, ctx context.Context, js jetstream.JetStream, state string) []views.Event {
t.Helper()
st, err := js.Stream(ctx, views0Stream)
if err != nil {
t.Fatal(err)
}
info, err := st.Info(ctx)
if err != nil {
t.Fatal(err)
}
var out []views.Event
for seq := info.State.FirstSeq; seq > 0 && seq <= info.State.LastSeq; seq++ {
m, err := st.GetMsg(ctx, seq)
if err != nil || m.Subject != "mesh.stream.mesh-delivery-indexer.event."+views.EventName(state) {
continue
}
e, err := views.DecodeEvent(m.Data)
if err != nil {
t.Fatal(err)
}
out = append(out, e)
}
return out
}
// waitViews waits for the views family's event of state's key.
func waitViews(t *testing.T, ctx context.Context, js jetstream.JetStream, state, key string) views.Event {
t.Helper()
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
for _, e := range viewsEvents(t, ctx, js, state) {
if e.Key == key {
return e
}
}
}
t.Fatalf("no views event of %s.%s within 20 s", state, key)
return views.Event{}
}
const views0Stream = "INDEXER_VIEWS"
// acceptedHeard counts what a waiter on manifest-accepted heard.
var acceptedHeard atomic.Int64
// startIndexer starts the bus, the stream FORGE with the indexer's durable consumer, the buckets, lets put fill
// mesh-controller_builds, and runs the program. mesh-delivery-gate_source is never created.
func startIndexer(t *testing.T, put func(context.Context, buildLog), before ...func(context.Context, jetstream.JetStream)) (jetstream.JetStream, context.Context, jetstream.KeyValue, jetstream.KeyValue) {
@@ -155,6 +268,20 @@ func startIndexer(t *testing.T, put func(context.Context, buildLog), before ...f
Subjects: []string{buscompose.LogSubject(delivery.BuilderModule, delivery.BuilderLog) + ".>"}, AllowDirect: true}); err != nil {
t.Fatal(err)
}
// The indexer's views family and its marks (ADR 0615 §5), as the grants part makes them from the old sections, and a
// waiter on manifest-accepted, which the views family never wakes.
if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: views0Stream, Subjects: []string{"mesh.stream.mesh-delivery-indexer.event.views.>"}}); err != nil {
t.Fatal(err)
}
if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: index.MarksBucket, History: 1}); err != nil {
t.Fatal(err)
}
acceptedHeard.Store(0)
waiter, err := ctl.Subscribe("mesh.mod.mesh-delivery-indexer.event.manifest-accepted", func(*nats.Msg) { acceptedHeard.Add(1) })
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = waiter.Unsubscribe() })
put(ctx, buildLog{ctx: ctx, js: js})
for _, b := range before {
b(ctx, js)
@@ -469,6 +469,14 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
// What the indexer heard of FORGE (issue 1126 note 76) is bound when its state appears, as every state a delivery adds
// (issue 1790): until then no message is written there, and the rest of the indexer runs.
heard := awaitedStore{process.AwaitBucket(ctx, js, conn, index.HeardBucket, "no FORGE message the indexer handles is written for indexer_heard", logger.Printf)}
// Each change of the states its readers read is said on its views family (ADR 0615 §5), from the mark each state was
// said up to; a first view (the tools bundle) answers at that mark.
startViews(ctx, js, cred, process.AwaitBucket(ctx, js, conn, index.MarksBucket, "no change is said on the views family, so every reader waits on its first view", logger.Printf),
process.AwaitBucket(ctx, js, conn, findingsBucket, "no finding of the indexer is written", logger.Printf),
map[string]readyOf{"manifests": boundNow(manifests), "modules": boundNow(modules), "candidates": boundNow(candidates),
index.VerdictsState: awaited(verdicts), delivery.IndexerPredefinedState: awaited(predefined),
delivery.IndexerConfigCandidatesState: awaited(configCandidates), delivery.PackagesState: awaited(packages),
graphState: awaited(graph), index.HeardState: awaited(heard.b), "taken": awaited(taken)}, errs, logger.Printf)
cc, err := process.HearEvents(cons, func(m jetstream.Msg) { handle(ctx, m, ix, heard, logger); ix.KickFill(ctx) }, logger.Printf)
if err != nil {
return err
@@ -0,0 +1,73 @@
package main
import (
"context"
"time"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-delivery/sdk/graph"
"git.novox.be/novox/mesh-delivery/sdk/views"
)
// findingsBucket is the indexer's own findings, which the condition keeper raises and clears (ADR 0564 §2): a value too
// large for its views event, and its first view of the assigner's placement unanswered past its bound.
var findingsBucket = health.FindingsBucket(delivery.IndexerModule)
// readyOf binds a state's bucket: now for one bound at the start, once it appears for one awaited.
type readyOf func(func(jetstream.KeyValue))
func boundNow(kv jetstream.KeyValue) readyOf { return func(f func(jetstream.KeyValue)) { f(kv) } }
func awaited(b *process.AwaitedBucket) readyOf { return b.Ready }
// startViews says each change of the indexer's viewed states on its views family (ADR 0615 §5), once its marks state
// is bound: each state from where its mark says, as soon as its own bucket is bound. A watch that ends is sent on errs.
func startViews(ctx context.Context, js jetstream.JetStream, cred process.Account, marks, findings *process.AwaitedBucket, states map[string]readyOf, errs chan<- error, logf func(string, ...any)) {
tooLarge := func(state, key string, size int) {
kv := findings.KV()
if kv == nil {
return
}
k, v, err := views.TooLargeFinding(delivery.IndexerModule, state, key, size, time.Now())
if err == nil {
_, err = kv.Put(ctx, k, v)
}
if err != nil {
logf("views: the finding of %s.%s too large is not written: %v", state, key, err)
}
}
marks.Ready(func(mk jetstream.KeyValue) {
em := views.Emitter{Owner: delivery.IndexerModule, Subject: cred.StreamSubject, Publish: views.JetStreamPublish(js),
Marks: mk, TooLarge: tooLarge, Logf: logf}
for _, state := range sortedStates(states) {
states[state](func(kv jetstream.KeyValue) {
if err := em.Start(ctx, state, kv, errs); err != nil {
logf("%v: its readers ask its first view at each change until the indexer starts again", err)
}
})
}
})
}
func sortedStates(states map[string]readyOf) []string {
out := make([]string, 0, len(states))
for s := range states {
out = append(out, s)
}
for i := range out {
for j := i + 1; j < len(out); j++ {
if out[j] < out[i] {
out[i], out[j] = out[j], out[i]
}
}
}
return out
}
// graphState is the module graph's state, named apart from run's bucket of the same name.
const graphState = graph.State
@@ -0,0 +1,55 @@
package index
import (
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/sdk/graph"
"git.novox.be/novox/mesh-delivery/sdk/views"
)
// ViewsStream is the indexer's views family on its old streams section (ADR 0615 §5): one event per changed key of
// each state in ViewedStates.
const ViewsStream = views.IndexerStream
// 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
// 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."},
}
// ReadTools are the indexer's ordinary read tools, one per state its readers read (design 25 § 12.2).
var ReadTools = []ViewedTool{
{Tool: "indexer_read_manifests", State: "manifests", Words: "every manifest the indexer keeps, by its digest."},
{Tool: "indexer_read_modules", State: "modules", Words: "every module the indexer holds, its fingerprint, inputs and manifest digest."},
{Tool: "indexer_read_predefined", State: delivery.IndexerPredefinedState, Words: "the predefined config values of each module on the trunk."},
{Tool: "indexer_read_config_candidates", State: delivery.IndexerConfigCandidatesState, Words: "the config declarations of each open pull request."},
{Tool: "indexer_read_candidates", State: "candidates", Words: "the candidate groups: the pull requests sharing a branch, their heads and the modules each touches."},
{Tool: "indexer_read_packages", State: delivery.PackagesState, Words: "the published packages per series."},
{Tool: "indexer_read_graph", State: graph.State, Words: "the module graph: each module's build and provision edges."},
{Tool: "indexer_read_heard", State: HeardState, Words: "the forge messages the indexer heard and what it did with each."},
{Tool: "indexer_read_taken", State: "taken", Words: "each repository's merge the indexer took in last."},
}
// ViewedTool is one tool over one state of the indexer's.
type ViewedTool struct {
Tool, State, Words string
}
// ViewedStates are the states the indexer's views family says, each by its bucket: the ten its readers read.
var ViewedStates = map[string]string{
"manifests": delivery.ManifestsBucket,
"modules": delivery.ModulesBucket,
VerdictsState: VerdictsBucket,
delivery.IndexerPredefinedState: PredefinedBucket,
delivery.IndexerConfigCandidatesState: ConfigCandidatesBucket,
"candidates": delivery.CandidatesBucket,
delivery.PackagesState: delivery.PackagesBucket,
graph.State: graph.Bucket,
HeardState: HeardBucket,
"taken": delivery.TakenBucket,
}
+62 -14
View File
@@ -16,6 +16,10 @@ import (
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/sdk/graph"
"git.novox.be/novox/mesh-delivery/sdk/views"
"git.novox.be/novox/mesh-sdk/go/health"
"mesh-delivery-indexer/internal/index"
)
// The controller's manifest reader is mesh-controller internal/catalogue (Manifest, manifest.go: Consumes :521,
@@ -29,19 +33,29 @@ type state struct {
}
type artifact struct {
Name string `json:"name"`
Kind string `json:"kind"`
Language string `json:"language"`
System string `json:"system"`
From string `json:"from"`
Binary string `json:"binary"`
Loads []string `json:"loads"`
Name string `json:"name"`
Kind string `json:"kind"`
Language string `json:"language"`
System string `json:"system"`
From string `json:"from"`
Binary string `json:"binary"`
Loads []string `json:"loads"`
Env map[string]string `json:"env"`
}
type stream struct {
Name string `json:"name"`
Events []string `json:"events"`
MaxAgeDays int `json:"max-age-days"`
MaxMiB int `json:"max-mib"`
}
type manifest struct {
Module string `json:"module"`
Version string `json:"version"`
Slug string `json:"slug"`
Module string `json:"module"`
Version string `json:"version"`
Slug string `json:"slug"`
RunsAs string `json:"runs-as"`
Streams []stream `json:"streams"`
Claims []struct {
Name string `json:"name"`
Scope string `json:"scope"`
@@ -99,7 +113,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
}
// 1126 note 76: heard keeps the last 64 FORGE messages the indexer handled, for indexer_heard.
if want := []state{{Name: "candidates", History: 10}, {Name: "heard", History: 64}, {Name: "manifests", History: 1}, {Name: "modules", History: 20}, {Name: "predefined", History: 5}, {Name: "config-candidates", History: 5}, {Name: "taken", History: 1}, {Name: "takeins", History: 1}, {Name: "verdicts", History: 5}, {Name: "oldsections", History: 1}, {Name: delivery.PackagesState, History: delivery.PackagesHistory},
{Name: graph.State, History: graph.History}}; !reflect.DeepEqual(m.State, want) {
{Name: graph.State, History: graph.History}, {Name: views.MarksState, History: 1}, {Name: health.FindingsState, History: 5}}; !reflect.DeepEqual(m.State, want) {
t.Errorf("state = %+v, want %+v", m.State, want)
}
if want := []string{"mesh-build-go.toolchains", "mesh-build-node.toolchains", "mesh-delivery-assigner.placement", "mesh-delivery-builder.build-log", "mesh-delivery-checker.previews", "mesh-delivery-gate.source"}; !reflect.DeepEqual(sorted(m.Reads), want) {
@@ -125,17 +139,51 @@ 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).
if want := []string{"indexer_take_in", "indexer_old_sections", "indexer_verdicts", "indexer_judge"}; !reflect.DeepEqual(m.Tools, want) {
// ADR 0621 §1, ADR 0622 §1: the first-view tools manifests, modules and verdicts, and design 25 § 12.2's nine 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...) {
want = append(want, v.Tool)
if _, ok := index.ViewedStates[v.State]; !ok {
t.Errorf("%s answers the state %s, which the views family does not say", v.Tool, v.State)
}
}
if !reflect.DeepEqual(m.Tools, want) || len(want) != 16 {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
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"}
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)
}
for tool, list := range callers {
if got := a.Env[views.CallersEnv(tool)]; got != list {
t.Errorf("%s callers = %q, want %q", tool, got, list)
}
if views.ParseCallers(a.Env[views.CallersEnv(tool)]).Allows("n1." + m.Module) {
t.Errorf("%s answers the indexer itself", tool)
}
}
}
// ADR 0615 §5, ADR 0619 §2: its views family on its old streams section, 7 days and 256 MiB, dropping its oldest.
if want := []stream{{Name: index.ViewsStream, Events: []string{views.EventPattern}, MaxAgeDays: views.StreamDays, MaxMiB: views.StreamMiB}}; !reflect.DeepEqual(m.Streams, want) {
t.Errorf("streams = %+v, want %+v", m.Streams, want)
}
// ADR 0604 §2: it holds the seat mesh-catalog, serving its four verbs from its tools bundle (which stays without a
// request subject).
if len(m.Claims) != 1 || m.Claims[0].Name != catalogue.CatalogSeat || m.Claims[0].Scope != catalogue.ScopeMesh ||
!slices.Equal(m.Claims[0].Serves, []string{"modules", "module", "provides", "dependents"}) {
t.Errorf("claims = %+v, want mesh-catalog with its four verbs", m.Claims)
}
// No streams, no bus, no other seat (ADR 0437 §4); it says no event.
for _, k := range []string{"streams", "logs", "provides", "requires", "uses", "defines-seats", "seat", "seats", "bus"} {
// No bus, no other seat (ADR 0437 §4).
for _, k := range []string{"logs", "provides", "requires", "uses", "defines-seats", "seat", "seats", "bus"} {
if _, ok := top[k]; ok {
t.Errorf("the manifest declares %q: the indexer has none", k)
}
+26 -3
View File
@@ -2,6 +2,7 @@
"module": "mesh-delivery-indexer",
"version": "1",
"slug": "indexer",
"runs-as": "mesh-delivery-indexer",
"claims": [
{
"name": "mesh-catalog",
@@ -18,7 +19,19 @@
"indexer_take_in",
"indexer_old_sections",
"indexer_verdicts",
"indexer_judge"
"indexer_judge",
"manifests",
"modules",
"verdicts",
"indexer_read_manifests",
"indexer_read_modules",
"indexer_read_predefined",
"indexer_read_config_candidates",
"indexer_read_candidates",
"indexer_read_packages",
"indexer_read_graph",
"indexer_read_heard",
"indexer_read_taken"
],
"consumes": [
"mesh-build-go.toolchain.reported",
@@ -38,7 +51,12 @@
{ "name": "verdicts", "history": 5 },
{ "name": "oldsections", "history": 1 },
{ "name": "packages", "history": 5 },
{ "name": "graph", "history": 5 }
{ "name": "graph", "history": 5 },
{ "name": "view-marks", "history": 1 },
{ "name": "findings", "history": 5 }
],
"streams": [
{ "name": "indexer-views", "events": ["views.**"], "max-age-days": 7, "max-mib": 256 }
],
"emits": [
"manifest-refused",
@@ -115,7 +133,12 @@
"binary": "mesh-delivery-indexer-tools",
"loads": [
"mesh-delivery-indexer-tools"
]
],
"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"
}
}
]
}