**0114 accepted.** The separation it draws — a consumer's resource and a credential that reaches it are different things — is a data-loss rule, and a record naming one should not sit unresolved while the code that could hit it is written. Not built, and accepting it schedules nothing; accepted-and-not-built is where 0141 and 0142 already are. **0068 superseded by 0149, the live mesh is the test bed.** Never built, and contradicted by practice that was written down nowhere but a handoff note. The faults that cost the most are faults of a mesh that already exists — bound consumers, containers made against an older roster, an adopted machine — and a bed is by construction a mesh that does not. Issue 156 settles it: that change was verified honestly, on the only path where it cannot fail. The lab is not retired; raising a mesh from bare is now its whole job. **117 answered by 0150.** A module's own code runs as supervised processes under the module's one account. 0047's argument was for a runtime per module, not for a container — a unit satisfies it and runs as an account. The invariant is the account, not the process count: its worry was a second identity to scope and seal, and processes sharing one account create none. Designs 18 and 20 now cite it and 0047 carries a dated pointer, which closes the third disagreement — that neither design knew the record existed.
96 lines
5.7 KiB
Markdown
96 lines
5.7 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.
|
|
|
|
> **The hosting form is settled elsewhere — 2026-09-30.** Where this record says "a container", read
|
|
> [ADR 0150](0150-a-modules-own-code-runs-as-supervised-processes-under-one-account.md): a module's own
|
|
> code runs as supervised processes under this record's one account. Nothing else here changes — the
|
|
> per-module runtime, the per-tool key and the single scoped account are the argument this record made
|
|
> and they are why 0150 goes the way it does. The note is here because two design documents chose the
|
|
> other form without knowing this record existed
|
|
> ([issue 117](../04-ISSUES/117-a-modules-own-code-is-a-container-and-a-process/00-report.md)).
|
|
|
|
## 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.
|