package broker import "testing" // A module's process with a bus declaration gets a bus account of its own (novox/hq ADR 0436 §1, §2, §3, // §5): named .., granted exactly its bus declaration and nothing of its // module's other grants or the tool runner's. The fixture is mesh-mail's future manifest shape (ADR 0436 // §7, §8): process "send", stream "mail-sends" (MAIL_SENDS), state "sends", using operator-channel as the // router of its own asks (novox/hq ADR 0259 §3, seattraffic_test.go's operatorChannel()). // mailSenderProcess is the process principal PermissionsFor composes a bus account from, as // inventory.processesOf would read it off mesh-mail's manifest (1179/AC1's fixture, 1178's queue owner). func mailSenderProcess() Principal { return Principal{Kind: KindProcess, Node: "novox", Module: "mesh-mail", Process: "send", Emits: []string{"mail.send.sent", "mail.send.failed"}, Queues: []QueueOwner{{Process: "send", Stream: "MAIL_SENDS", MaxDeliver: 5}}, State: []string{"sends"}, Asks: true, Uses: []Seat{operatorChannel()}, Invokes: []string{"seat:operator-channel.tell"}, } } // 1180/AC1: the process principal publishes its emits, pulls and acknowledges its one queue consumer, reads // and writes its own state bucket, reads the router's records of its module's own asks, publishes its // invoked seat verb in its own name and the plain form, and subscribes only its own inbox. func TestAProcessIsGrantedExactlyItsBusDeclaration(t *testing.T) { p := mailSenderProcess() if got := p.Username(); got != "novox.mesh-mail.send" { t.Fatalf("username: %q", got) } got := perms(t, p) for _, s := range []string{ "mesh.mod.mesh-mail.event.mail.send.sent", "mesh.mod.mesh-mail.event.mail.send.failed", "$JS.API.CONSUMER.INFO.MAIL_SENDS.novox_mesh-mail_send", "$JS.API.CONSUMER.MSG.NEXT.MAIL_SENDS.novox_mesh-mail_send", "$JS.ACK.MAIL_SENDS.novox_mesh-mail_send.x", "$KV.mesh-mail_sends.x", "$JS.API.DIRECT.GET.KV_mesh-mail_sends.$KV.mesh-mail_sends.x", "$JS.API.DIRECT.GET.KV_messenger_asks.$KV.messenger_asks.mesh-mail.a1", "mesh.seat.operator-channel.tool.tell", "mesh.seat.operator-channel.call.tell.novox~mesh-mail~send", } { if !allowed(got.Publish, s) { t.Errorf("the process may not publish %s", s) } } if !allowed(got.Subscribe, "_INBOX.novox.mesh-mail.send.x") { t.Error("the process does not hear its own inbox") } if len(got.Subscribe) != 1 { t.Errorf("the process subscribes %v; want only its own inbox", got.Subscribe) } } // 1180/AC3: a module with no bus process composes as main does, and adding one to mesh-mail changes // neither the tool runner's grants for a module it carries nor another module's own. func TestAddingAProcessLeavesOtherGrantsUnchanged(t *testing.T) { plain := Principal{Kind: KindModule, Node: "novox", Module: "other", PasswordHash: "x"} before := perms(t, plain) users, err := Users(Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": { {Module: "other"}, {Module: "mesh-mail", Processes: []ProcessPrincipal{{Process: "send", Emits: []string{"mail.send.sent"}}}, NoAccount: true}, }}}) if err != nil { t.Fatal(err) } var sawProcess, sawOther bool for _, u := range users { if u.Kind == KindProcess && u.Module == "mesh-mail" { sawProcess = true } if u.Module == "other" { sawOther = true if got := perms(t, u); !equalStringSlices(got.Publish, before.Publish) || !equalStringSlices(got.Subscribe, before.Subscribe) { t.Errorf("another module's grants changed: %+v, want %+v", got, before) } } if u.Kind == KindModule && u.Module == "mesh-mail" { t.Error("mesh-mail has no own secret broker (NoAccount), and should have no module principal of its own") } } if !sawProcess { t.Fatal("the process account was not composed at all") } if !sawOther { t.Fatal("the module with no process was not composed") } } // 1180/AC3: a process account is never carried by the machine's tool runner, so adding one to a module the // runtime carries changes nothing of the runtime's own grants for that module. func TestAddingAProcessLeavesTheToolRunnersCarryingUnchanged(t *testing.T) { assigned := func(processes []ProcessPrincipal) map[string][]Declared { return map[string][]Declared{"novox": { {Module: RuntimeModule}, {Module: "mesh-mail", Serves: []string{"status"}, Processes: processes}, }} } without, err := Users(Records{Nodes: []string{"novox"}, Assigned: assigned(nil)}) if err != nil { t.Fatal(err) } with, err := Users(Records{Nodes: []string{"novox"}, Assigned: assigned([]ProcessPrincipal{ {Process: "send", Emits: []string{"mail.send.sent"}}, })}) if err != nil { t.Fatal(err) } runtimeOf := func(users []Principal) Principal { for _, u := range users { if u.Kind == KindNodeTools { return u } } t.Fatal("the tool runner was not composed") return Principal{} } before, after := perms(t, runtimeOf(without)), perms(t, runtimeOf(with)) if !equalStringSlices(before.Publish, after.Publish) || !equalStringSlices(before.Subscribe, after.Subscribe) { t.Errorf("the tool runner's grants changed when mesh-mail gained a process: before %+v, after %+v", before, after) } } // 1180/AC2, the grant test of ADR 0436 clause 3: a process account answers no tool call or seat verb, // publishes no warrant, holds no seat (operator-channel included), and hears no restricted stream — however // wide its own grants (emits, its queue, its state, its asks and its invokes) are. func TestAProcessAccountAnswersNothing(t *testing.T) { p := mailSenderProcess() got := perms(t, p) for _, s := range []string{ // Every shape a tool call or a seat verb is answered on: a module's own namespace, a seat's, and // the caller-named `call` kind naming any caller — which calledSubscribe would add if a `tool` // grant ever reached Subscribe. "mesh.mod.mesh-mail.tool.send", "mesh.mod.mesh-mail.tool.>", "mesh.seat.operator-channel.tool.tell", "mesh.seat.operator-channel.call.tell.*", "mesh.seat.operator-channel.call.tell.mesh-controller", } { if allowed(got.Subscribe, s) { t.Errorf("a process account may subscribe %s, and could answer a call or a verb", s) } } // No warrant: only operator-channel's holder publishes the asker's decided event (ADR 0259 §3). if allowed(got.Publish, "mesh.seat.operator-channel.event.decided.mesh-mail") { t.Error("a process account may publish a warrant") } // No seat held: the holder-only subjects (its accept queue, its worker) are refused. for _, s := range []string{ "mesh.seat.operator-channel.accept.ask.mesh-mail", "$JS.API.CONSUMER.MSG.NEXT.SEAT_OPERATOR_CHANNEL.SEAT_OPERATOR_CHANNEL_worker", } { if allowed(got.Subscribe, s) || allowed(got.Publish, s) { t.Errorf("a process account holds operator-channel: %s", s) } } // No restricted stream: a process never hears a module stream at all (it has no Hears), restricted or // not — the generic rule ADR 0376 §1 holds for it as it does for the tool runner. if allowed(got.Subscribe, "mesh.stream.mesh-mail.event.mail.mesh.message.added") { t.Error("a process account hears a module's stream") } if got.Subscribe == nil || len(got.Subscribe) != 1 { t.Fatalf("a process account's whole subscribe list: %v; want only its own inbox", got.Subscribe) } } // Every process account composed for a mesh is held to the same rule (ADR 0436 clause 3): checked across // the whole shape Users() composes, not only the one fixture above. func TestEveryComposedProcessAccountAnswersNothing(t *testing.T) { users, err := Users(Records{Nodes: []string{"novox"}, Assigned: map[string][]Declared{"novox": { {Module: "messenger", Holds: []Seat{operatorChannel()}, RunsAs: "messenger"}, {Module: "mesh-mail", RunsAs: "mesh-mail", Uses: []Seat{operatorChannel()}, Processes: []ProcessPrincipal{{Process: "send", Emits: []string{"mail.send.sent"}, Asks: true, Invokes: []string{"seat:operator-channel.tell"}}}}, }}}) if err != nil { t.Fatal(err) } checked := 0 for _, u := range users { if u.Kind != KindProcess { continue } checked++ got := perms(t, u) for _, s := range []string{ "mesh.seat.operator-channel.call.tell.*", // answers any caller "mesh.seat.operator-channel.accept.ask." + u.Module, // a caller's own submit, never a process's "mesh.seat.operator-channel.event.decided." + u.Module, // the holder's warrant alone } { if allowed(got.Subscribe, s) || allowed(got.Publish, s) { t.Errorf("%s may reach %s, which only a holder or an asking module's own principal may", u.Username(), s) } } } if checked == 0 { t.Fatal("no process account was composed to check") } } func equalStringSlices(a, b []string) bool { if len(a) != len(b) { return false } seen := map[string]bool{} for _, x := range a { seen[x] = true } for _, x := range b { if !seen[x] { return false } } return true }