--- topic: what runs on it status: accepted date: 2026-09-30 deciders: jochen reconstructed: false extends: 02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md --- # 150. A module's own code runs as supervised processes under the module's one account ## Context The repository answers "what runs a module's own code" two ways and reconciles them nowhere ([issue 117](../04-ISSUES/117-a-modules-own-code-is-a-container-and-a-process/00-report.md)). [ADR 0047](0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md) is accepted and says **a container** — "the tool runtime carrying that module's compiled code" — and "one module, one process, one account". Two `proposed` design documents say a **`process`** resource running an argv, supervised by the machine, and one of them declares *four* of them for a single module and presents four as the point. Neither design document names 0047 in its `decisions:`, and the string `process` as a resource type appears in no decision record at all. The thing as built is the container. Two things have happened since 0047 was written that bear on it directly. [**ADR 0142**](0142-the-mesh-delivers-its-own-components-as-binaries.md) decided that the mesh's own components are binaries on the machine rather than container images, and [issue 114](../04-ISSUES/114-should-the-controller-be-a-container-or-a-process/00-report.md) was closed by it. That settled the mesh's components and deliberately said nothing about a module's. And the standing definition of a module hardened: **a module is software that delivers one or more services, and a module is not a container.** It may deliver them as a container, an installed package with a unit, a binary, or configuration files; 61 of 73 happen to use a container and 11 do not, including the resolver, sshd and fail2ban. A rule that a module's *own code* must be a container makes the one kind of module the mesh writes itself the only kind that has no choice. ## Considered Options **1. Hold 0047 as written: a container.** Rejected. Its own reasoning does not require one. What 0047 argued for was a runtime **per module** rather than one for the whole node, because a node-wide runtime could not hold a per-module broker account and per-module runtimes competing on one tool key would each be handed calls for tools they do not have. A supervised unit per module satisfies that argument exactly — it is per module, and a unit runs as an account. The container was the mechanism to hand, not the conclusion. **2. Let each design document choose.** Rejected; that is the present state and it is what issue 117 reports. A module author reading the guide writes four processes; a module author reading the record writes a container; nothing tells either that the other exists. **3. Settle the hosting form as a supervised process, and settle the count separately.** Chosen. ## Decision **A module's own code runs as one or more supervised processes on the machine, under the module's single account.** Where [ADR 0047](0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md) says "a container, the tool runtime carrying that module's compiled code", read this record. Everything else 0047 decided stands untouched: a tool is served on its own key, only the module that serves it answers, and the module's account is scoped to exactly its tool keys. **The invariant is the account, not the process count.** 0047's "one module, one process, one account" carried its weight in the last clause. Its stated worry about a second process was "not a second one to scope and seal" — a second *identity* to grant, seal a secret to, and scope on the bus. Several processes sharing the module's one account create no second identity, so nothing further is scoped or sealed, and a module may therefore declare as many as its work has shapes: events, tools, a provisioner, a scheduled ingest. **What a module may not have is two accounts.** **A module that delivers its service as a container still does.** This record is about the code the module itself carries — its tools, its events, its provisioner — and not about the software it delivers. A module wrapping a third-party image wraps a third-party image. **Why supervised by the machine rather than by the mesh:** it is the same answer ADR 0142 gave for the mesh's own components, for the same reason. A unit the machine restarts needs no image, no registry pull and no runtime to be up before the mesh's own code can run — which matters most for exactly the modules whose code the mesh cannot start any other way. ## How this is checked - **No design document describes a hosting form for a module's own code without citing this record.** Designs 18 and 20 name it in `decisions:`; this is the gap issue 117's third point reports, and `cycle.py` already enforces that a to-be design names its decisions. - **A module declaring several processes resolves to one account.** A test composes a module with more than one process resource and asserts the mesh mints exactly one broker account for it, scoped to that module's tool keys and nothing else — which is 0047's invariant stated as an assertion rather than a sentence. - **A module's own code does not require the container runtime.** A machine with no container runtime can still run a module whose code is its own, which is the claim that separates this from option 1 and is checkable on a machine that has one by asserting the declaration names no image for it. ## Consequences - **The sidecar port stops being needed.** [ADR 0029](0029-a-network-is-a-shape-because-an-action-cannot-be-undone.md) records that "anything that is a service plus a sidecar currently has to publish a port to talk to itself", and the host's `network` shape exists partly for it. A process beside the service on the same machine reaches it without publishing anything, so that pressure goes. - **Something must supervise, and it is the machine.** This adds a unit per module's code to what the host writes and owns. The mesh already writes and owns units — `nftables` proves a module can write one and run it — so the mechanism exists; the count grows. - **A module's code is delivered, not pulled**, which puts it behind the same gap as the host's own delivery ([ADR 0141](0141-the-host-delivers-its-own-successor.md), not built): nothing yet delivers a version of a module's binary to a machine. A container's code arrives by `docker pull`, and this does not. **This is the cost of the decision and it is not paid**; until delivery exists, a module whose code is its own is a module somebody places by hand. - **Issue 117 is answered and its three disagreements close differently:** container-or-unit is decided here; one-process-or-several is decided here as several under one account; and whether the record was consulted is fixed by designs 18 and 20 naming this one. ## References - [issue 117](../04-ISSUES/117-a-modules-own-code-is-a-container-and-a-process/00-report.md) — the contradiction this answers - [ADR 0047](0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md) — extended; its "a container" clause is settled here - [ADR 0142](0142-the-mesh-delivers-its-own-components-as-binaries.md) — the same answer for the mesh's own components - [ADR 0141](0141-the-host-delivers-its-own-successor.md) — the delivery this depends on and which is not built - [ADR 0029](0029-a-network-is-a-shape-because-an-action-cannot-be-undone.md) — the sidecar port this relieves