Files
hq/04-ISSUES/117-a-modules-own-code-is-a-container-and-a-process/00-report.md
T
jochen 35db2aaa41 Issue 117: a module's own code is a container in one record and a process in another
Asked what the "sidecar" is and whether a supervised process would do instead. The repository
answers both ways. ADR 0047 (accepted, unsuperseded) says a module with tools or events runs a
container carrying its compiled code. To-be 18 and 20 (both proposed) define a `process` resource
type — the module's own code, a unit the machine's supervisor keeps up — and the worked guide says
plainly "it is why these are `process` rather than four containers." Neither design doc names 0047,
and no decision record mentions a `process` shape at all.

Diagnosed rather than left open, because the ground truth settles what the report could not.
The shape is real: mesh-host defines TypeProcess, applies it, and tests it, and the host's
vocabulary is twelve shapes rather than the nine ADR 0029 counted. So the alternative the report
offered — that two proposed documents describe a type that does not exist — is disproven.

ADR 0029's mechanism is intact and was not enough. The vocabulary-count test names the decision
behind each addition: network 0029, access 0051, opening 0100. The eleventh names a *proposed
design document*, and TypeProcess is the only shape in the vocabulary whose doc comment cites no
ADR. Requiring every addition to name something does not require it to name a decision.

The argument this issue asked for already exists — as a Go test comment. "It is a full-host shape
rather than a portable one: it needs a process supervisor to install into. It does NOT need a
container runtime, which is the point — only software that genuinely needs isolation asks for a
container." That is a decision's context and consequences, in another repository.

What the catalogue does is a third thing: 115 container declarations against 3 process, all three
in showcase — the module to-be 20 documents. There the tools resource is a container running
`sleep infinity` on a bare upstream base with the broker credential mounted, and the tools and
provisioner entrypoints are run by nothing. That is the condition 0047 was written to end, back
in a new shape.

Where the isolation argument leaks is narrower than expected and worth having precisely: the
serving key and the credential shape both conform. But serveTools serves every registered module
over one broker connection, the runtime takes its modules from a comma-separated list, and
x-source is stamped from the single credential — so two modules in one runtime means the second's
events are attributed to the first. Nothing refuses it and no test asserts against it.

Located on hq rather than on a code repository: the implementation and the design layer agree,
and the missing thing is the record. Which shape is right is left open, deliberately — this
establishes that the question was answered in practice and never written down, not which answer
is correct.

One correction kept in the trail: the first search here was for len(Vocabulary()), found nothing,
and was two steps from being written up as "the mechanism ADR 0029 relied on is gone." The test
binds the slice to a local first. A negative search result read as a fact about the world is the
same error issue 113 recorded.
2026-09-25 16:15:00 +02:00

102 lines
7.3 KiB
Markdown

---
status: located
opened: 2026-09-25
located-in: [hq, mesh-catalog modules/showcase, mesh-sdk src/tools/index.ts, mesh-tools]
fixed-by:
amended-design:
---
# 117 — A module's own code is a container in one record and a process in another
## What was observed
Asked what the "sidecar" is — the second container a code-carrying module runs beside its
service — and whether a supervised process would do instead. Reading the records to answer it,
the repository answers both ways, and nothing reconciles them.
| record | status | what runs a module's own code |
|---|---|---|
| [ADR 0047](../../02-DECISIONS/0047-a-module-runs-its-code-as-its-own-process-with-its-own-account.md) | **accepted**, 2026-09-04 | "a **container**, the tool runtime carrying that module's compiled code" — one module, one process, one account; events and tools in that same process, "not a second one to scope and seal" |
| [`01-to-be/18-building-a-module.md`](../../03-DESIGN/01-to-be/18-building-a-module.md) | proposed, 2026-09-21 | a resource type table in which `container` is "an image" and **`process`** is "**its own code**, in three modes", whose default mode is "a unit restarted when it exits", supervised by the machine |
| [`01-to-be/20-writing-a-module.md`](../../03-DESIGN/01-to-be/20-writing-a-module.md) | proposed, 2026-09-21 | one module declaring **four** `process` resources — events, tools, provisioner, a scheduled ingest — each with its own `run` argv, and the sentence "it is why these are `process` rather than four containers" |
Three disagreements, not one:
1. **Container or unit.** ADR 0047 chose a container and said why: a node-wide runtime loading
every module's code could not hold a per-module account, so the runtime is per-module. The
design docs choose a supervised unit running an argv and give no reason, because they do not
record that they are choosing.
2. **One process or several.** ADR 0047's "one module, one process, one account" is the whole
content of its second and third sections. The worked guide declares four for one module and
presents four as the point.
3. **Whether the record was consulted at all.** Neither design doc names ADR 0047 in
`decisions:`. No record supersedes or extends it on this. **The string `process` as a resource
type appears in no decision record** — the shape exists only in two `proposed` design docs.
Meanwhile the thing as built is the container. [ADR 0029](../../02-DECISIONS/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," which is one of the things the host's `network` shape was added for.
[Issue 113's diagnosis](../113-the-object-stores-images-were-withdrawn-upstream/01-diagnosis.md)
found a catalogue module declaring "two container resources," the second a runtime sidecar
"pinned at an all-zeros digest, meaning nothing was ever published for it."
[Issue 095](../095-a-module-assigned-after-genesis-has-no-broker-account/00-report.md) is a
sidecar crash-looping on a credential while its service served correctly.
[ADR 0093](../../02-DECISIONS/0093-a-fixture-that-runs-a-modules-runtime-carries-its-name.md)
records that a bed wanting "a sidecar without its server raises the server."
### And the word is in no glossary
"Sidecar" appears sixteen times across five records — two decisions and three issues. It is
absent from [`00-META/glossary.md`](../../00-META/glossary.md), and absent from every document
under [`03-DESIGN/`](../../03-DESIGN/), in both layers. ADR 0047, which creates the thing, never
uses the word; it says "runtime process" and "runtime container". The glossary's own rule is that
"a new name for an existing thing lands here first, in the same change that introduces it in
code," and the page exists because "the terms kept drifting in conversation." A reader asking
what the sidecar is has nowhere in the design layer to look, which is how this was found.
## Why it matters beyond this instance
- **A module author reading the current guide writes a `process`; the catalogue as built declares
a `container`.** [`20-writing-a-module.md`](../../03-DESIGN/01-to-be/20-writing-a-module.md) is
a worked guide with a manifest in it. Whichever of the two is wrong, somebody follows it.
- **The cost of the container shape is paid in four places and totalled in none.** A published
image per code-carrying module, a network so a module can reach itself, a bed that cannot run a
runtime without raising the server it manages, and a credential failure that presents as the
module's own bug. Each record argues its own piece is worth paying. No record puts them beside
the alternative.
- **Both shapes carry a cost the other does not, and neither is written down.** A container
carries its own interpreter; a `process` declaring `run: ["node", "index.js"]` needs an
interpreter present on the machine, which is the machine dependency the statically linked host
([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) exists to avoid. And `run` is an argv,
where [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) refuses `action` because the link may
not carry a command — a refusal [`18-building-a-module.md`](../../03-DESIGN/01-to-be/18-building-a-module.md)
restates on the same page that it introduces `process`.
- **This is the repository's own named failure mode, in its own records.** `cycle.py` enforces
that a to-be doc names *at least one* decision. Both docs do, so both pass, while introducing a
resource type no decision records and contradicting an accepted one. The rule is "no design
without a decision"; the check is "no design without *a* decision." An unenforced rule is
indistinguishable from a wrong one, and these two documents are what that gap looks like when
something walks through it.
## Open questions
- Which is the decision — container or supervised unit? If the design docs are right, ADR 0047
needs superseding rather than quietly outliving. If ADR 0047 is right, two proposed documents
and a worked manifest describe a resource type that does not exist.
- Is one account per module satisfied by a per-module *unit* as well as a per-module *container*?
ADR 0047's argument rules out a node-wide runtime sharing one account. It does not appear to
rule out a unit holding one scoped credential, and nothing has said so either way.
- If several processes for one module are right, what holds the accounts? ADR 0047 refused "a
second one to scope and seal" for events beside tools. Four processes are four somethings.
- How does a `process` get its interpreter, and does declaring one reintroduce the machine
dependency the host is built to avoid?
- Is `run` an argv the link may carry, given `action` is refused for being one? If the answer is
that a `process` reconciles and an `action` does not, that distinction is not written down.
- What is the thing called, and where does the design layer describe it? Whichever shape wins, no
document in either layer currently says a code-carrying module runs a second thing beside its
service.
- **How would this have been caught?** A decision and a design doc disagreeing on a resource type
is mechanically checkable: the resource types a design doc names are a closed set, and every
member of it either appears in a decision or does not. Whether that check is worth writing is
part of this issue, not settled by it.