package broker import ( "encoding/json" "slices" "sort" "strings" "testing" ) // The delivery's forge bridge, indexer and teller are processes on their module's own broker credential, granted from // the manifest alone (novox/hq ADR 0437 ยง4, issue 1243): `state` grants writing and watching the module's own buckets // and streams, `reads` watching another module's bucket and never writing it, `consumes` hearing the named events, // `invokes` calling the named tools, `uses` the seat operator-channel's by-caller subjects under the module's own name. // // The four manifests (the watchman's is the fourth, issue 1329): the bridge's and the indexer's are copied whole from // mesh-delivery's branch feat/r4c2-new-keys at b7870ac9a6386467318454e9d5d4790987eb708d (modules//module.json, // read 2026-10-20; issues 1448 and 1449 add the bridge's gitea_get_file and performed bucket and the indexer's manifests // bucket), the teller's and the watchman's from mesh-delivery's main at 9c15112d935a, whole, so a change to one of them // there that changes its grant is a change to read here. const bridgeManifest = `{ "module": "mesh-delivery-forge-bridge", "version": "1", "slug": "forge-bridge", "consumes": [ "gitea.pull.updated", "gitea.pull.merged", "gitea.pull.closed" ], "streams": [ { "name": "forge", "events": ["forge.**"] } ], "state": [ { "name": "heads", "history": 1 }, { "name": "performed", "history": 1 } ], "reads": [ "mesh-delivery-gate.merges", "mesh-delivery-gate.owed" ], "invokes": [ "gitea.gitea_merge_pull_request", "gitea.gitea_commit_status", "gitea.gitea_delivery_view", "gitea.gitea_note_append", "gitea.gitea_add_comment", "gitea.gitea_open_pulls", "gitea.gitea_commit_trees", "gitea.gitea_get_file" ], "own-secrets": { "broker": "${dir:state}/broker" }, "secrets-owner": "mesh-delivery-forge-bridge", "resources": [ { "id": "account", "type": "user", "name": "mesh-delivery-forge-bridge", "shell": "/usr/bin/nologin", "home": "/var/lib/mesh-delivery-forge-bridge" }, { "id": "state", "type": "directory", "mode": "0700", "owner": "mesh-delivery-forge-bridge", "place": ".", "unknown": "program" }, { "id": "bridge", "type": "process", "name": "mesh-delivery-forge-bridge", "artifact": "program", "run": [ "./mesh-delivery-forge-bridge" ], "env": { "MESH_BROKER_FILE": "${dir:state}/broker" }, "user": "mesh-delivery-forge-bridge", "health": { "kind": "unit" } } ], "build": { "artifacts": [ { "name": "program", "kind": "bundle", "language": "go", "system": "arch", "from": "cmd/mesh-delivery-forge-bridge", "binary": "mesh-delivery-forge-bridge" } ] } }` const indexerManifest = `{ "module": "mesh-delivery-indexer", "version": "1", "slug": "indexer", "consumes": [ "mesh-delivery-forge-bridge.forge.**" ], "state": [ { "name": "candidates", "history": 10 }, { "name": "manifests", "history": 1 }, { "name": "modules", "history": 20 } ], "reads": [ "mesh-controller.builds", "mesh-delivery-gate.source" ], "own-secrets": { "broker": "${dir:state}/broker" }, "secrets-owner": "mesh-delivery-indexer", "resources": [ { "id": "account", "type": "user", "name": "mesh-delivery-indexer", "shell": "/usr/bin/nologin", "home": "/var/lib/mesh-delivery-indexer" }, { "id": "state", "type": "directory", "mode": "0700", "owner": "mesh-delivery-indexer", "place": ".", "unknown": "program" }, { "id": "indexer", "type": "process", "name": "mesh-delivery-indexer", "artifact": "program", "run": [ "./mesh-delivery-indexer" ], "env": { "MESH_BROKER_FILE": "${dir:state}/broker" }, "user": "mesh-delivery-indexer", "health": { "kind": "unit" } } ], "build": { "artifacts": [ { "name": "program", "kind": "bundle", "language": "go", "system": "arch", "from": "cmd/mesh-delivery-indexer", "binary": "mesh-delivery-indexer" } ] } }` const watchmanManifest = `{ "module": "mesh-delivery-watchman", "version": "1", "slug": "watchman", "reads": [ "mesh-controller.checks-running" ], "state": [ { "name": "presence", "history": 20 }, { "name": "pulse", "key-ttl": true }, { "name": "stuck", "history": 20 } ], "own-secrets": { "broker": "${dir:state}/broker" }, "secrets-owner": "mesh-delivery-watchman", "resources": [ { "id": "account", "type": "user", "name": "mesh-delivery-watchman", "shell": "/usr/bin/nologin", "home": "/var/lib/mesh-delivery-watchman" }, { "id": "state", "type": "directory", "mode": "0700", "owner": "mesh-delivery-watchman", "place": ".", "unknown": "program" }, { "id": "watchman", "type": "process", "name": "mesh-delivery-watchman", "artifact": "program", "run": [ "./mesh-delivery-watchman" ], "env": { "MESH_BROKER_FILE": "${dir:state}/broker" }, "user": "mesh-delivery-watchman", "health": { "kind": "unit" } } ], "build": { "artifacts": [ { "name": "program", "kind": "bundle", "language": "go", "system": "arch", "from": "cmd/mesh-delivery-watchman", "binary": "mesh-delivery-watchman" } ] } }` const tellerManifest = `{ "module": "mesh-delivery-teller", "version": "1", "slug": "teller", "state": [ { "name": "answers", "history": 5 } ], "reads": [ "mesh-delivery-gate.asks" ], "uses": [ "operator-channel" ], "own-secrets": { "broker": "${dir:state}/broker" }, "secrets-owner": "mesh-delivery-teller", "resources": [ { "id": "account", "type": "user", "name": "mesh-delivery-teller", "shell": "/usr/bin/nologin", "home": "/var/lib/mesh-delivery-teller" }, { "id": "state", "type": "directory", "mode": "0700", "owner": "mesh-delivery-teller", "place": ".", "unknown": "program" }, { "id": "teller", "type": "process", "name": "mesh-delivery-teller", "artifact": "program", "run": [ "./mesh-delivery-teller" ], "env": { "MESH_BROKER_FILE": "${dir:state}/broker" }, "user": "mesh-delivery-teller", "health": { "kind": "unit" } } ], "build": { "artifacts": [ { "name": "program", "kind": "bundle", "language": "go", "system": "arch", "from": "cmd/mesh-delivery-teller", "binary": "mesh-delivery-teller" } ] } }` // manifestOf is the part of a module's manifest that composes a bus account, as the controller reads it. type manifestOf struct { Module string `json:"module"` Consumes []string `json:"consumes"` Streams []struct { Name string `json:"name"` Events []string `json:"events"` } `json:"streams"` State []struct { Name string `json:"name"` History int `json:"history"` } `json:"state"` Reads []string `json:"reads"` Invokes []string `json:"invokes"` Uses []string `json:"uses"` OwnSecrets map[string]json.RawMessage `json:"own-secrets"` Claims []struct { Name string `json:"name"` Scope string `json:"scope"` Serves []string `json:"serves"` } `json:"claims"` } // declaredOf is what the controller declares of a manifest to the broker (inventory's busrecords): its state as // buckets, its streams under the module's name in the bus's spelling, its claims as held seats, the module streams // its consumes name as heard, and no account when it declares no own secret `broker`. streamsOf are the module // streams the other manifests of the test own, by module. func declaredOf(t *testing.T, raw string, streamsOf map[string]ModuleStream) Declared { t.Helper() var m manifestOf if err := json.Unmarshal([]byte(raw), &m); err != nil { t.Fatal(err) } d := Declared{Module: m.Module, Consumes: m.Consumes, Reads: m.Reads, Invokes: m.Invokes} if _, reads := m.OwnSecrets["broker"]; !reads { d.NoAccount = true } for _, s := range m.State { d.State = append(d.State, Bucket{Module: m.Module, Name: s.Name, History: s.History}) } for _, s := range m.Streams { var events []string for _, e := range s.Events { events = append(events, WirePattern(e)) } d.Streams = append(d.Streams, ModuleStream{Module: m.Module, Name: strings.ToUpper(s.Name), Events: events, MaxAgeDays: 7, MaxMiB: 64}) } for _, c := range m.Claims { d.Holds = append(d.Holds, Seat{Name: c.Name, Scope: c.Scope, Serves: c.Serves}) } for _, name := range m.Uses { if name == "operator-channel" { d.Uses = append(d.Uses, operatorChannelWithCondition()) } } for _, c := range m.Consumes { emitter, event, _ := strings.Cut(c, ".") if s, owned := streamsOf[emitter]; owned && strings.HasPrefix(event, strings.ToLower(s.Name)+".") { d.Hears = append(d.Hears, Heard{Stream: s.Name, Emitter: emitter, Filters: []string{ModuleStreamSubject(emitter, WirePattern(event))}}) d.HearsAStream = true } } return d } // canWrite says a principal may publish to a bucket's key; canRead says it may get one directly. func canWrite(p Permissions, bucket, key string) bool { return allowed(p.Publish, "$KV."+bucket+"."+key) } func canRead(p Permissions, bucket, key string) bool { return allowed(p.Publish, "$JS.API.DIRECT.GET.KV_"+bucket+".$KV."+bucket+"."+key) } // composed is every module's and every machine's tool runner's grants the broker composes for these records, // as `@` and `runner@`. func composed(t *testing.T, r Records) map[string]Permissions { t.Helper() users, err := Users(r) if err != nil { t.Fatal(err) } out := map[string]Permissions{} for _, u := range users { switch u.Kind { case KindNodeTools: out["runner@"+u.Node] = perms(t, u) case KindModule: out[u.Module+"@"+u.Node] = perms(t, u) } } return out } // withPrefix is the patterns that start with any of the prefixes, sorted. func withPrefix(patterns []string, prefixes ...string) []string { var out []string for _, p := range patterns { for _, prefix := range prefixes { if strings.HasPrefix(p, prefix) { out = append(out, p) break } } } sort.Strings(out) return out } // eventsSaid is the events a principal may publish, under a module's name or on a module's stream. func eventsSaid(p Permissions) []string { var out []string for _, s := range withPrefix(p.Publish, "mesh.mod.", "mesh.stream.") { if strings.Contains(s, ".event.") { out = append(out, s) } } return out } // toolsCalled is the tools of one module a principal may call, by name. func toolsCalled(p Permissions, module string) []string { var out []string for _, s := range withPrefix(p.Publish, "mesh.mod."+module+".tool.") { if name := strings.TrimPrefix(s, "mesh.mod."+module+".tool."); !strings.Contains(name, ".") { out = append(out, name) } } return out } // partsRecords is the three parts on one machine, beside the machine's tool runner. func partsRecords(t *testing.T) Records { t.Helper() streams := map[string]ModuleStream{"mesh-delivery-forge-bridge": {Module: "mesh-delivery-forge-bridge", Name: "FORGE"}} return Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": { {Module: RuntimeModule}, declaredOf(t, bridgeManifest, streams), declaredOf(t, indexerManifest, streams), declaredOf(t, tellerManifest, streams), }}} } // 1243/AC1: each part's credential, composed from its manifest, may do exactly what its declaration names: it writes // its own buckets and stream, reads the buckets it reads (and cannot write them), hears what it consumes, calls what it // invokes, and says what the seat it uses lets it say under its own name. func TestEachPartsCredentialGrantsExactlyItsDeclaration(t *testing.T) { all := composed(t, partsRecords(t)) bridge, indexer, teller := all["mesh-delivery-forge-bridge@novox"], all["mesh-delivery-indexer@novox"], all["mesh-delivery-teller@novox"] // The forge bridge: state heads and performed, the stream FORGE, reads merges and owed, invokes eight tools of the forge. if got, want := withPrefix(bridge.Publish, "$KV."), []string{"$KV.mesh-delivery-forge-bridge_heads.>", "$KV.mesh-delivery-forge-bridge_performed.>"}; !slices.Equal(got, want) { t.Errorf("the bridge writes %v; want exactly %v", got, want) } if got, want := eventsSaid(bridge), []string{"mesh.stream.mesh-delivery-forge-bridge.event.forge.>"}; !slices.Equal(got, want) { t.Errorf("the bridge says %v; want exactly %v", got, want) } for _, b := range []string{"merges", "owed"} { if !canRead(bridge, "mesh-delivery-gate_"+b, "g.1") || canWrite(bridge, "mesh-delivery-gate_"+b, "g.1") { t.Errorf("the bridge must read and not write the gate's %s", b) } } for _, event := range []string{"pull.updated", "pull.merged", "pull.closed"} { if !allowed(bridge.Subscribe, "mesh.mod.gitea.event."+event) { t.Errorf("the bridge does not hear gitea's %s", event) } } if allowed(bridge.Subscribe, "mesh.mod.gitea.event.pull.opened") { t.Error("the bridge hears an event of gitea's it does not consume") } called := []string{"gitea_add_comment", "gitea_commit_status", "gitea_commit_trees", "gitea_delivery_view", "gitea_get_file", "gitea_merge_pull_request", "gitea_note_append", "gitea_open_pulls"} if got := toolsCalled(bridge, "gitea"); !slices.Equal(got, called) { t.Errorf("the bridge calls %v; want exactly %v", got, called) } if allowed(bridge.Publish, "mesh.mod.gitea.tool.gitea_delete_repo") { t.Error("the bridge may call a tool of gitea's it does not invoke") } // The indexer: state candidates, manifests and modules, reads the controller's builds and the gate's source, hears FORGE. if got, want := withPrefix(indexer.Publish, "$KV."), []string{"$KV.mesh-delivery-indexer_candidates.>", "$KV.mesh-delivery-indexer_manifests.>", "$KV.mesh-delivery-indexer_modules.>"}; !slices.Equal(got, want) { t.Errorf("the indexer writes %v; want exactly %v", got, want) } if got := eventsSaid(indexer); len(got) != 0 { t.Errorf("the indexer says %v; it declares no event", got) } for _, read := range [][2]string{{"mesh-controller_builds", "b.1"}, {"mesh-delivery-gate_source", "main"}} { if !canRead(indexer, read[0], read[1]) || canWrite(indexer, read[0], read[1]) { t.Errorf("the indexer must read and not write %s", read[0]) } } if !allowed(indexer.Subscribe, "mesh.mod.mesh-delivery-forge-bridge.event.forge.merged") || !allowed(indexer.Publish, "$JS.API.CONSUMER.MSG.NEXT.FORGE.novox_mesh-delivery-indexer") { t.Errorf("the indexer does not hear FORGE through its own consumer: %v / %v", indexer.Subscribe, indexer.Publish) } if got := withPrefix(indexer.Publish, "mesh.seat."); len(got) != 0 { t.Errorf("the indexer reaches the seats %v; it uses none", got) } // The teller: state answers, reads the gate's asks, uses operator-channel under its own name. if got, want := withPrefix(teller.Publish, "$KV."), []string{"$KV.mesh-delivery-teller_answers.>"}; !slices.Equal(got, want) { t.Errorf("the teller writes %v; want exactly %v", got, want) } if !canRead(teller, "mesh-delivery-gate_asks", "merge.g.h") || canWrite(teller, "mesh-delivery-gate_asks", "merge.g.h") { t.Error("the teller must read and not write the gate's asks") } for _, verb := range []string{"ask", "cancel", "condition"} { if !allowed(teller.Publish, "mesh.seat.operator-channel.accept."+verb+".mesh-delivery-teller") { t.Errorf("the teller may not say %s as itself", verb) } } if !allowed(teller.Subscribe, "mesh.seat.operator-channel.event.decided.mesh-delivery-teller") { t.Errorf("the teller does not hear its own decided events: %v", teller.Subscribe) } } // 1243/AC2: each part's credential is refused writing another module's bucket, publishing under another module's name // and answering a verb subject of a seat. func TestNoPartsCredentialReachesBeyondItsDeclaration(t *testing.T) { all := composed(t, partsRecords(t)) parts := []string{"mesh-delivery-forge-bridge", "mesh-delivery-indexer", "mesh-delivery-teller"} buckets := map[string][]string{ "mesh-delivery-forge-bridge": {"heads", "performed"}, "mesh-delivery-indexer": {"candidates", "manifests", "modules"}, "mesh-delivery-teller": {"answers"}, "mesh-delivery-gate": {"merges", "owed", "source", "asks", "deliveries"}, "mesh-controller": {"builds", "plans"}, } for _, part := range parts { p := all[part+"@novox"] for owner, names := range buckets { if owner == part { continue } for _, name := range names { if canWrite(p, owner+"_"+name, "some.key") { t.Errorf("%s may write %s_%s", part, owner, name) } } } for _, other := range append(append([]string(nil), parts...), "mesh-delivery-gate", "mesh-controller", "gitea", "mesh-delivery") { if other == part { continue } for _, subject := range []string{"mesh.mod." + other + ".event.merged", "mesh.stream." + other + ".event.forge.merged"} { if allowed(p.Publish, subject) { t.Errorf("%s may publish %s, under %s's name", part, subject, other) } } } for _, verb := range []string{"mesh.seat.mesh-delivery.tool.deliveries", "mesh.seat.mesh-delivery.tool.release", "mesh.seat.mesh-delivery.tool.stop", "mesh.seat.operator-channel.tool.tell"} { if allowed(p.Subscribe, verb) { t.Errorf("%s answers the verb subject %s", part, verb) } } } // The teller says what it says as itself alone. teller := all["mesh-delivery-teller@novox"] for _, subject := range []string{"mesh.seat.operator-channel.accept.ask.mesh-delivery-gate", "mesh.seat.operator-channel.accept.condition.mesh-delivery-indexer", "mesh.seat.operator-channel.accept.ask.*", "mesh.seat.operator-channel.accept.ask.>"} { if allowed(teller.Publish, subject) { t.Errorf("the teller may publish %s", subject) } } } // toolServer is a module that serves a tool on its own broker credential: the runner carries it (ADR 0175). func toolServer() Declared { return Declared{Module: "some-tools", Serves: []string{"some_tool"}, State: []Bucket{{Module: "some-tools", Name: "kept", History: 1}}} } // 1329/AC1: composed from the manifests of mesh-delivery's main, the machine's tool runner may write no bucket of the // forge bridge, the indexer, the teller or the watchman, publish none of their stream events nor the teller's // operator-channel accept subjects, and pull none of their consumers; each part's own credential still may. func TestTheToolRunnerCarriesNoneOfTheDeliverysParts(t *testing.T) { streams := map[string]ModuleStream{"mesh-delivery-forge-bridge": {Module: "mesh-delivery-forge-bridge", Name: "FORGE"}} all := composed(t, Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": { {Module: RuntimeModule}, declaredOf(t, bridgeManifest, streams), declaredOf(t, indexerManifest, streams), declaredOf(t, tellerManifest, streams), declaredOf(t, watchmanManifest, streams), }}}) runner := all["runner@novox"] buckets := map[string][]string{ "mesh-delivery-forge-bridge": {"heads", "performed"}, "mesh-delivery-indexer": {"candidates", "manifests", "modules"}, "mesh-delivery-teller": {"answers"}, "mesh-delivery-watchman": {"presence", "pulse", "stuck"}, } for part, names := range buckets { own, there := all[part+"@novox"] if !there { t.Errorf("%s has no credential of its own", part) continue } for _, name := range names { if canWrite(runner, part+"_"+name, "some.key") { t.Errorf("novox's tool runner may write %s_%s", part, name) } if !canWrite(own, part+"_"+name, "some.key") { t.Errorf("%s may not write its own bucket %s", part, name) } } } for _, subject := range []string{ "mesh.stream.mesh-delivery-forge-bridge.event.forge.merged", "mesh.seat.operator-channel.accept.ask.mesh-delivery-teller", "mesh.seat.operator-channel.accept.cancel.mesh-delivery-teller", "mesh.seat.operator-channel.accept.condition.mesh-delivery-teller", "$JS.API.CONSUMER.MSG.NEXT.FORGE.novox_mesh-delivery-indexer", } { if allowed(runner.Publish, subject) { t.Errorf("novox's tool runner may publish %s", subject) } } if !allowed(all["mesh-delivery-forge-bridge@novox"].Publish, "mesh.stream.mesh-delivery-forge-bridge.event.forge.merged") || !allowed(all["mesh-delivery-teller@novox"].Publish, "mesh.seat.operator-channel.accept.ask.mesh-delivery-teller") || !allowed(all["mesh-delivery-indexer@novox"].Publish, "$JS.API.CONSUMER.MSG.NEXT.FORGE.novox_mesh-delivery-indexer") { t.Error("a part's own credential lost what its manifest grants") } } // 1329/AC2: a module with tools and a broker secret of its own stays carried by the tool runner, beside its own // credential (ADR 0175); a module with neither tools, a seat, waits nor a check is not. func TestTheToolRunnerStillCarriesAModuleWithToolsAndItsOwnCredential(t *testing.T) { process := Declared{Module: "only-process", State: []Bucket{{Module: "only-process", Name: "mine", History: 1}}} all := composed(t, Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": { {Module: RuntimeModule}, toolServer(), process, }}}) runner := all["runner@novox"] if !canWrite(runner, "some-tools_kept", "k") { t.Error("novox's tool runner does not carry a module with tools") } if !canWrite(all["some-tools@novox"], "some-tools_kept", "k") { t.Error("the module with tools lost its own credential") } if canWrite(runner, "only-process_mine", "k") { t.Error("novox's tool runner carries a module that has nothing to serve") } if !canWrite(all["only-process@novox"], "only-process_mine", "k") { t.Error("the process module lost its own credential") } for name, d := range map[string]Declared{ "a held seat": {Module: "m", Holds: []Seat{{Name: "some-seat"}}}, "a wait": {Module: "m", Waits: []Wait{{}}}, "a check": {Module: "m", Checks: []string{"m.check"}}, } { if !runnerServes(d) { t.Errorf("the tool runner does not serve a module with %s", name) } } } // routeProxyExcerpt is the part of mesh-control's modules/route-proxy/module.json at 70cb217601f9 (read 2026-10-20) // that decides its grants: its state, what it reads, its own broker secret, and the bundle `provider` that the machine's // runtime loads (`loads`) though the module declares no tools. const routeProxyExcerpt = `{ "module": "route-proxy", "state": ["bound"], "reads": ["mesh-controller.assignments"], "own-secrets": { "broker": "${dir:mesh-state}/broker" }, "build": { "artifacts": [ { "name": "provider", "kind": "bundle", "language": "go", "from": "cmd/route-provider", "binary": "route-provider", "loads": ["route-provider"] } ] } }` // 1329 (review): a module with no tools whose bundle the runtime loads stays carried, because its helper writes its state // and reads on the runner's account; the delivery's five parts are still dropped. func TestTheToolRunnerStillCarriesAModuleWhoseBundleItLoads(t *testing.T) { var m struct { Module string `json:"module"` State []string `json:"state"` Reads []string `json:"reads"` Build struct { Artifacts []struct { Loads []string `json:"loads"` } `json:"artifacts"` } `json:"build"` } if err := json.Unmarshal([]byte(routeProxyExcerpt), &m); err != nil { t.Fatal(err) } proxy := Declared{Module: m.Module, Reads: m.Reads} for _, s := range m.State { proxy.State = append(proxy.State, Bucket{Module: m.Module, Name: s, History: 1}) } for _, a := range m.Build.Artifacts { proxy.Loads = proxy.Loads || len(a.Loads) > 0 } streams := map[string]ModuleStream{"mesh-delivery-forge-bridge": {Module: "mesh-delivery-forge-bridge", Name: "FORGE"}} all := composed(t, Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": { {Module: RuntimeModule}, proxy, declaredOf(t, bridgeManifest, streams), declaredOf(t, indexerManifest, streams), declaredOf(t, tellerManifest, streams), declaredOf(t, watchmanManifest, streams), }}}) runner := all["runner@novox"] if !canWrite(runner, "route-proxy_bound", "k") { t.Error("novox's tool runner does not carry route-proxy, whose bundle it loads") } if !canWrite(all["route-proxy@novox"], "route-proxy_bound", "k") { t.Error("route-proxy lost its own credential") } for _, b := range []string{"mesh-delivery-teller_answers", "mesh-delivery-forge-bridge_heads", "mesh-delivery-indexer_modules", "mesh-delivery-watchman_stuck"} { if canWrite(runner, b, "k") { t.Errorf("novox's tool runner may write %s", b) } } }