88 lines
5.0 KiB
Markdown
88 lines
5.0 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: accepted
|
|
date: 2026-09-04
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md
|
|
---
|
|
|
|
# 47. A module runs its code as its own process, with its own account
|
|
|
|
## Context
|
|
|
|
A module is one self-contained thing ([ADR 0040](0040-what-a-module-is.md)), and it gets a broker
|
|
account scoped to what it emits and consumes ([ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md)).
|
|
The catalogue now gives modules **tools** and **events** — real code, in the module ([ADR 0039](0039-what-the-sdk-holds-and-refuses.md)) —
|
|
but nothing has said what *runs* that code. The audit-logger showed one shape and was treated as an
|
|
exception: a container running the tool runtime carrying the module's compiled code, holding the
|
|
module's own scoped account. Every module with tools or events needs the same, and the tempting
|
|
alternative does not work.
|
|
|
|
**A node-wide runtime that loaded every assigned module's code cannot hold a per-module account.** It
|
|
would run under one account with the union of every module's permissions — able to emit as any of
|
|
them and read any of their queues — which is exactly the isolation ADR 0043 exists to draw. So the
|
|
runtime is per-module, not per-node, and treating the audit-logger as special left the other
|
|
modules' code with nothing to run it: the conversion produced tools and events that, as it stands,
|
|
never execute.
|
|
|
|
## Decision
|
|
|
|
### A module with tools or events runs a process of its own
|
|
|
|
A module that has tools or events runs a **runtime process** — a container, the tool runtime carrying
|
|
that module's compiled code — assigned and started like the module it is, holding the single broker
|
|
account the mesh scoped to it (ADR 0043). One module, one process, one account.
|
|
|
|
### It serves its tools, each on its own key
|
|
|
|
A tool is served on its own key (`serve.<tool>`), and a caller invokes a named tool. Only the module
|
|
that serves it answers, and the module's account is scoped to exactly its tool keys — so one module
|
|
cannot answer another's calls, the isolation ADR 0043 gives events extended to tools. This supersedes
|
|
a single `tools.invoke` endpoint that dispatched by name: that shape assumed one runtime for the
|
|
whole node, and per-module runtimes competing on one key would each be handed calls for tools they do
|
|
not have.
|
|
|
|
### It runs its events in the same process, under the same account
|
|
|
|
Emitting under the module's own origin and consuming its own queue ([ADR 0042](0042-the-shape-of-an-event-on-the-wire.md))
|
|
happen in that same process, with that same account — not a second one to scope and seal. A module's
|
|
tool code, its event code and, for a provider, its provisioner are the one module's code and run as
|
|
the one module's process.
|
|
|
|
### The runtime image is the tool runtime plus the module's code
|
|
|
|
Built from the module's source like any module image — the audit-logger's shape, made the rule, not
|
|
the exception. The module declares a `container` for it carrying `MESH_BROKER_FILE` (its sealed
|
|
credential, ADR 0043) and its compiled code. A module with **neither** tools nor events runs no such
|
|
process: a plain service module — the plex *server*, dnsmasq the resolver — is its service and files
|
|
and nothing more. A module that is both a service and code declares both containers: the service, and
|
|
the runtime beside it.
|
|
|
|
## Consequences
|
|
|
|
- The catalogue's tools and events become runnable: each tools-or-events module gains a runtime
|
|
container with its scoped credential, and the audit-logger stops being special. Until this, the
|
|
converted modules held code with nothing to execute it.
|
|
- A process, and a small image, per tools-or-events module. That is the cost of ADR 0043's isolation:
|
|
one account per module means one process per module. It is paid deliberately — a shared runtime is
|
|
cheaper and cannot be scoped, and a mesh where any module can emit as any other is not one worth the
|
|
saving.
|
|
- `serve.<tool>` per key replaces the single `tools.invoke` dispatch. The sdk's serving and a module's
|
|
account scope both come to name tools individually.
|
|
- **A provider's provisioner is a runtime process too.** It already runs as its own container; its
|
|
events (`bucket.created`, `database.provisioned`) belong to *that* process and need the same
|
|
credential. So a provisioner that emits carries `MESH_BROKER_FILE` and its scoped account like any
|
|
runtime — or it does not emit. (This is the fix for provisioners that emit today with no broker
|
|
bound: the emit is a runtime's, and the provisioner is a runtime.)
|
|
|
|
## References
|
|
|
|
- [ADR 0040](0040-what-a-module-is.md) — a module is one self-contained thing; its code runs as one
|
|
process.
|
|
- [ADR 0043](0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) — the scoped account
|
|
this process holds, and the isolation that makes it per-module.
|
|
- [ADR 0042](0042-the-shape-of-an-event-on-the-wire.md) — the events this process runs, and the
|
|
`serve.<key>` queue tools now use.
|
|
- [ADR 0039](0039-what-the-sdk-holds-and-refuses.md) — the code lives in the module; this runs it.
|