ADR 0192: a tools bundle declares what it is given, and the runtime hands it to that bundle alone; research 020 graduated; design 38 WP4b

This commit is contained in:
jochen
2026-10-03 15:18:22 +02:00
parent 9873e951a9
commit 4c1ad0ed45
4 changed files with 134 additions and 4 deletions
@@ -1,8 +1,8 @@
---
status: active
status: graduated
initiated: 2026-10-03
touches: [the tool runtime, the catalogue's tool bundles, the controller's declaration composer, settings, own secrets, 03-DESIGN/01-to-be/38-building-the-operators-machine.md]
became: []
became: [02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md, 03-DESIGN/01-to-be/38-building-the-operators-machine.md]
---
# 020 — What a bundled tool is given
@@ -0,0 +1,109 @@
---
topic: what runs on it
status: accepted
date: 2026-10-03
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md
---
# 192. A tools bundle declares what it is given, and the runtime hands it to that bundle alone
## Context
[ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md) put one
runtime on every node serving every module's tools from a bundle, and
[ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)
said a module's own code is bundles and never an image. The two holders that moved first
([to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) WP4) needed nothing a
bundle does not have: a fixed path, and root. Research
[020](../01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md) measured the rest before
they move: thirty-three modules still run their tools as a container on the runtime's image, and
thirty-one of them are handed, through the container's environment and mounts, things a bundle
has no way to receive — the module's configuration file, its own secret as a file, the service's
address with the port the mesh chose, a provision's address, a directory of grants. Every one of
those is a file the mesh already places on the machine or a value the controller already composes
for the container, per module per machine, from references the manifest writes: a placed
directory, a chosen port. And the SDK's tool contributor is a function of an environment that the
runtime calls without one, so every bundle reads the process's four words.
Without a rule, each of the thirty-one would answer the question its own way, and the runtime's
process would be the one place where every module's paths meet.
## Considered Options
1. **The bundle declares its environment on its artifact, and the mesh composes it as a
container's.** Chosen. The tools artifact gains `env`: names to values, the values written with
the references the composer already resolves for a container — `${dir:…}`, `${port:…}` — and
what was a mount target becomes the host path itself. The controller composes one environment
per bundle per machine into the runtime's declaration. The runtime hands it to that bundle's
contributor, or to the child it launches, and to nothing else. The tool code reads the names it
read before.
2. **The runtime derives it from the module's placed manifest** — a conventional word per
directory and port, no new field. Rejected: a convention the thirty-one tools must be rewritten
to, the runtime learning the composer's job, and a module that names its file one way and a
module that names it another needing different words regardless.
3. **The tool asks the controller over the bus.** Rejected: a tool that cannot start until the
bus answers fails in the one case tools exist for, and a secret crossing the bus to reach a file
already on the machine is a disclosure for nothing.
4. **Leave each module to its own device.** Rejected by the measurement: thirty-one modules, one
question.
## Decision
**1. A tools bundle says what it is given, on its artifact.** `build.artifacts[].env` names the
words the bundle reads and their values. A value is a path or a constant, composed with the
references a container's environment may use; **never a secret's content.** A secret reaches a
tool the way it reaches a container: as a file the mesh places, whose path the environment names.
A bundle that declares no `env` is given nothing beyond the runtime's own words, which is what the
two holders that moved have.
**2. The mesh composes it, per bundle per machine, as it composes a container's.** The same
references, resolved the same way, to the host's own paths. The composed environment travels in
the node's declaration beside the bundle's archive; a change to it is a change to the bundle for
the purpose of `restart-on`.
**3. The runtime hands each bundle its own environment, and nothing of another's.** A bundle
imported into the runtime's process receives it as the argument its contributor is written to
take; a bundle launched as a child ([ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md) §2)
receives it as the child's environment, over the runtime's own words. The runtime's process
environment is not where a module's words go, and a tool that reads the process's environment
rather than the one it was handed finds the runtime's four words and no module's.
**4. The remaining tool containers move in one change** after this is built, each proven by its
tools answering from the runtime, and the registration gate of
[to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) WP2 then refuses the
container shape for every module, as ADR 0188 already provides.
## Consequences
- The manifest gains one field on one artifact kind; the composer gains one more thing to resolve
with references it has; the runtime gains the hand-off and the separation. The thirty-one modules'
tool code does not change, and their conversion is the move of a container's `env` with its
mounts folded into host paths.
- A tool's inputs become legible in the manifest where its container hid them in mounts: what a
module's tools read is declared beside what the module writes.
- What got harder: the runtime must keep thirty-one environments apart in one process, and a
bundle's author must not reach for the process's environment. The separation is a rule the
runtime's test holds, not a property of the language.
- `MESH_BROKER_FILE` is not a bundle's to declare: the runtime speaks with the node's credential
([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)), and
a module's own bus credential went with its container.
## How it is checked
| Rule | Checked by |
|---|---|
| A value in a bundle's `env` is a path or a constant, never a secret's content | the catalogue's manifest check refuses a `${secret:…}` reference in a bundle's `env`, naming this record |
| The composer resolves a bundle's `env` as a container's | the controller's composition test: one module, one bundle with `${dir:…}` and `${port:…}` in its `env`, the declaration carrying the host paths and the chosen port |
| Each bundle sees its own environment and no other's | the runtime's test: two bundles with different `env`, loaded in one runtime, each answering with its own words and none of the other's; the same for a launched bundle |
| A change to a bundle's environment restarts the runtime | the composition test above, with `restart-on` naming the bundle |
| Live | a module whose tools read a configuration file and a token file answers from the runtime on one machine with no container |
## References
- [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md),
[ADR 0188](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)
- Research [020](../01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md) — the measurement
and the options
- [to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md) — where the work is listed
+1
View File
@@ -291,6 +291,7 @@ python3 00-META/checks/index.py fail if stale
- **0182** — [Inside a home, the mesh owns the directory and the files it places, writes into the tool's own files, and holds everything else as found](0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md)
- **0183** — [The Anthropic licence manager is a module holding a seat; it hands each node's agent its token over the bus, sealed; the controller and the host have no part](0183-the-anthropic-licence-manager-is-a-module-and-hands-tokens-to-the-agent-over-the-bus.md)
- **0188** — [A module's own code is bundles in any language, and a tools bundle speaks MCP to the runtime](0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md)
- **0192** — [A tools bundle declares what it is given, and the runtime hands it to that bundle alone](0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md)
### How it is built
@@ -12,6 +12,7 @@ decisions:
- 02-DECISIONS/0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md
- 02-DECISIONS/0149-the-live-mesh-is-the-test-bed.md
- 02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md
- 02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md
---
# 38. Building the operator's machine
@@ -222,8 +223,27 @@ proven on all four machines — `status`, `banned` and the module's own `fail2ba
the runtime, no `mesh-fail2ban` container, the runtime serving both bundles. Two holders moved; of the
thirty-three tool containers the catalogue held, thirty-one remain, and all but these two carried their
module's configuration and secrets in the container's environment, which a bundle does not have — the
question research [020](../../01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md) opens
before the rest move.
question research [020](../../01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md) opened
and [ADR 0192](../../02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md)
settled the same day: a tools bundle declares `env` on its artifact, the composer resolves it as a
container's, the runtime hands each bundle its own. That is WP4b below.
## WP4b — Every tool container moves
*mesh-controller, mesh-tools, mesh-catalog. One day. The rest of ADR 0175, under ADR 0192.*
**What changes**, in order: the manifest's tools artifact gains `env` and the catalogue check
refuses a secret's content in it; the composer resolves a bundle's `env` per machine and carries it
beside the bundle's archive, `restart-on` included; the runtime hands each bundle its own
environment — the contributor's argument for an imported bundle, the child's environment for a
launched one — and a test holds two bundles apart. Then the thirty-one remaining tool containers
move in one change: each container's `env` becomes its tools artifact's, mount targets folded into
the host paths they came from, the container, its base images, its Dockerfile and its own bus
credential gone. Last, the registration gate refuses the container shape for every module.
**Proof.** The controller's and the runtime's tests named in ADR 0192; live, every module's tools
answer from the runtime on the machines that run it, `docker ps` shows no tool container on any of
the four, and `status` is well.
## WP5 — The shell, on a server first