diff --git a/cmd/mesh-controller/modules.go b/cmd/mesh-controller/modules.go index 8d678f9..13389a0 100644 --- a/cmd/mesh-controller/modules.go +++ b/cmd/mesh-controller/modules.go @@ -584,16 +584,25 @@ func issueOnTheNewBus(ctx context.Context, inv *inventory.Inventory, m catalogue // (`rollout mint`, design 28 task 5.2) rather than the one in this process's environment. func issueWith(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest, node, busAddress string, known broker.Broker, reachable, user, password string) error { + // The seats this module claims, with the verbs each promises (novox/hq ADR 0159): the runtime + // serves a claimed seat's verbs with its tools of the same name, and the bus admits only the + // holder's subscription — so the runtime tries each claim and the grant decides. Written here + // because this file is the one thing the mesh writes that the runtime reads before it speaks. + claims, err := claimsFor(ctx, inv, m) + if err != nil { + return err + } held, err := json.Marshal(struct { - URL string `json:"url"` - Fingerprint string `json:"fingerprint,omitempty"` - Node string `json:"node"` - Module string `json:"module"` - User string `json:"user"` - Password string `json:"password"` + URL string `json:"url"` + Fingerprint string `json:"fingerprint,omitempty"` + Node string `json:"node"` + Module string `json:"module"` + User string `json:"user"` + Password string `json:"password"` + Claims []seatClaimed `json:"claims,omitempty"` }{ URL: "nats://" + reachable, Fingerprint: known.Fingerprint, - Node: node, Module: m.Module, User: user, Password: password, + Node: node, Module: m.Module, User: user, Password: password, Claims: claims, }) if err != nil { return err @@ -680,3 +689,36 @@ func namesNoInstallation(m catalogue.Manifest) error { "on purpose under %s with its reason, or take it out:\n - %s", m.Module, catalogue.NamesOnPurpose, strings.Join(named, "\n - ")) } + +// seatClaimed is one seat a module claims, as its runtime needs it: the name, the scope (a +// node-scoped seat's verb carries the machine, design 33 §4) and the verbs the seat promises. +type seatClaimed struct { + Seat string `json:"seat"` + Scope string `json:"scope"` + Serves []string `json:"serves,omitempty"` +} + +// claimsFor joins a module's claims with the seats' protocols from the mesh's records. +func claimsFor(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest) ([]seatClaimed, error) { + if len(m.Claims) == 0 { + return nil, nil + } + seats, err := inv.Seats(ctx) + if err != nil { + return nil, err + } + byName := map[string]catalogue.Seat{} + for _, s := range seats { + byName[s.Name] = s + } + var out []seatClaimed + for _, c := range m.Claims { + claimed := seatClaimed{Seat: c.Name, Scope: c.At()} + if s, known := byName[c.Name]; known { + claimed.Scope = s.Scope + claimed.Serves = catalogue.VerbNames(s.Serves) + } + out = append(out, claimed) + } + return out, nil +} diff --git a/internal/broker/invokes_test.go b/internal/broker/invokes_test.go index c2b76fd..3d1e86c 100644 --- a/internal/broker/invokes_test.go +++ b/internal/broker/invokes_test.go @@ -91,3 +91,16 @@ func TestADeclaredInvokeReachesTheComposedUser(t *testing.T) { } has(t, perms.Publish, "mesh.mod.*.tool.>") } + +// A module's tool is addressed two ways (novox/hq ADR 0159): to whichever instance answers, and to +// the instance on one machine. A grant for the tool covers both and nothing wider. +func TestInvokingAToolMayAddressTheMachineToo(t *testing.T) { + got, err := invokedSubjects([]string{"postgres.postgres_query"}) + if err != nil { + t.Fatal(err) + } + want := []string{"mesh.mod.postgres.tool.postgres_query", "mesh.mod.postgres.tool.postgres_query.*"} + if len(got) != 2 || got[0] != want[0] || got[1] != want[1] { + t.Fatalf("the grant is %v, want %v", got, want) + } +} diff --git a/internal/broker/nats.go b/internal/broker/nats.go index e4b8d09..9032888 100644 --- a/internal/broker/nats.go +++ b/internal/broker/nats.go @@ -660,7 +660,10 @@ func invokedSubjects(invokes []string) ([]string, error) { return nil, fmt.Errorf( "%q does not name a tool: one invokes ., seat:., or * for every one", t) } - out = append(out, "mesh.mod."+module+".tool."+tool) + // Both ways a module's tool is addressed (novox/hq ADR 0159): to whichever instance + // answers, and to the instance on one machine, which is the same subject with the machine + // as its last token. + out = append(out, "mesh.mod."+module+".tool."+tool, "mesh.mod."+module+".tool."+tool+".*") } return out, nil }