Issue 009 (fixed) + Issue 008 (resolved via ADR 0053) — module-runtime config & provider contract #21
@@ -0,0 +1,86 @@
|
||||
---
|
||||
status: proposed
|
||||
date: 2026-09-04
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0048-a-module-broker-account-is-scoped-by-emits-and-consumes.md
|
||||
---
|
||||
|
||||
# 52. A module runs its code as its own process, with its own account
|
||||
|
||||
## Context
|
||||
|
||||
A module is one self-contained thing ([ADR 0045](0045-what-a-module-is.md)), and it gets a broker
|
||||
account scoped to what it emits and consumes ([ADR 0048](0048-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 0044](0044-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 0048 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 0048). 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 0048 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 0047](0047-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 0048) 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 0048'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 0045](0045-what-a-module-is.md) — a module is one self-contained thing; its code runs as one
|
||||
process.
|
||||
- [ADR 0048](0048-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 0047](0047-the-shape-of-an-event-on-the-wire.md) — the events this process runs, and the
|
||||
`serve.<key>` queue tools now use.
|
||||
- [ADR 0044](0044-what-the-sdk-holds-and-refuses.md) — the code lives in the module; this runs it.
|
||||
Reference in New Issue
Block a user