Files
hq/02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md
T
jochen bf39baf104 Research 018 graduates: ADRs 0173–0177 and to-be 37, the operator's machine
Every configurable thing on a node is a module, the home included, and a
module is whatever it declares (0173, extending 0040). A node varies a module
only through a setting rendered into the file or a kept region, never an edit
(0174, extending 0011; issue 168 first). One tool runtime per node serves every
module's tools on the host side, never in a container; the console is its
serving mode, renamed node-tools (0175, extending 0150; 0047/0150/0152 carry
dated notes). The login shell is a node seat held by one shell module with
`execute` as its contract (0176). A unit may be user-scoped and the service
manager is a node seat held by systemd (0177).

To-be 37 is handed off in-progress to mesh-host, mesh-controller, mesh-tools
and mesh-catalog, with the build in order: the account on every node, the
runtime, zsh, systemd, then the graphical stack. To-be 29 keeps ~/.ssh and
points at 37; 33 §6 and 34 are amended; the glossary gains node tools, bundle,
kept region, installed/holding, and retires flavor.
2026-10-02 16:34:57 +02:00

6.1 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 2026-09-04 jochen false 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

The mechanism changed — 2026-10-02, by ADR 0175. A module's tools are no longer served by the module's own process under its own account: one tool runtime per node, on the host side, serves every assigned module's bundle. A tool is still served on its own subject and only the module that serves it answers; what moved is the process and the account.

Context

A module is one self-contained thing (ADR 0040), and it gets a broker account scoped to what it emits and consumes (ADR 0043). The catalogue now gives modules tools and events — real code, in the module (ADR 0039) — 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: 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).

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) 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 — a module is one self-contained thing; its code runs as one process.
  • ADR 0043 — the scoped account this process holds, and the isolation that makes it per-module.
  • ADR 0042 — the events this process runs, and the serve.<key> queue tools now use.
  • ADR 0039 — the code lives in the module; this runs it.