--- status: resolved opened: 2026-09-14 located-in: [mesh-controller cmd/mesh-controller (ask), mesh-controller internal/link] fixed-by: ADR 0095; mesh-controller multiple-fixes (ask: the control plane publishes the request with a private reply queue and prints the answer); proven by the confluence bed amended-design: 03-DESIGN/01-to-be/19-the-module-protocol.md --- # 049 — A module can serve tools, and nothing is allowed to call them ## Symptom A module serves tools over the broker. Asking it one, from inside that same module's own container, using its own credential, is refused: ``` Error: Channel closed by server: 403 (ACCESS-REFUSED) with message "ACCESS_REFUSED - User '-' doesn't have permissions to queue 'amq.gen-...'" ``` A module's broker account is scoped to what it declares it emits and consumes. A tool call is a request and a reply, and the reply arrives on a temporary queue the caller creates — which that scope does not cover, and should not: a module that only publishes events has no business declaring queues. So the account is right and the request is reasonable, and there is no account that can make it. ## Why this matters **A module's tools are its operator-facing surface, and nothing can reach it.** The catalogue serves five: what this mesh holds, what a module is made of, what provides a given provision, what depends on a module, and what is stale. Those are the questions the catalogue exists to answer, and today the answer to "how does anyone ask one" is that they run a container joined to the broker's network namespace and authenticate as the substrate's bootstrap admin. **It is why a running module gets mistaken for a working one.** A test that cannot ask a module anything checks that its container is up, and a container being up is not a claim about function. That substitution has now hidden two separate faults in one week — a crash-looping runtime beside a healthy container of a similar name, and a catalogue that was never installed at all. **The mesh already has the shape for this.** An account scoped to a purpose, minted by the mesh and sealed to a holder, is exactly what provisioning does. What is missing is the recognition that *asking* is a use of a module, the way pulling is a use of the artifact store ([042](../042-nothing-gives-a-node-an-account-for-a-registry/00-report.md)) rather than something that happens beneath it. ## Evidence The refusal above, from a module invoking its own tool with the credential the mesh gave it. The working alternative, which is the measure of the gap: ``` docker run --rm --network container: \ -e MESH_BROKER_URL=amqp://@127.0.0.1:/ \ invoke '{}' ``` That is the bootstrap credential, host-local, with no scope at all. It works, and nothing about it should be how a mesh is asked a question. ## Open questions - **Who is the caller?** An operator at a terminal, an agent acting for one, and another module are three different holders with three different scopes, and only the last resembles anything the mesh mints today. - **Should calling be a grant like any other** — a module declares it may be asked, and a consumer is issued an account that may create a reply queue and publish to that module's request queue? - **Should the control plane be the way in?** It holds an admin connection already, and a `mesh-control` subcommand for asking a module a question would need no new account — at the cost of routing every question through one process and its privileges. - **What does a module declare about being asked?** Serving a tool is already declared. Whether it may be asked by anyone who can reach the broker, or only by named holders, is not.