Files
hq/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md
T

8.2 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 2026-10-03 jochen false 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 put one runtime on every node serving every module's tools from a bundle, and ADR 0188 said a module's own code is bundles and never an image. The two holders that moved first (to-be 38 WP4) needed nothing a bundle does not have: a fixed path, and root. Research 020 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.

Progressive insight — 2026-10-03. The context above calls the thirty-one remaining containers tool containers handed what a bundle cannot receive. Measured the same day while building this record: nine of them run only tools; three run a main of their own; twenty import, beside their tools, the module's own long-running code — event handlers that subscribe on the bus and provisioners that act on grants — under the module's own bus identity, and some reach their service by a container network name or need a package the image installed. That code is not a tool and is not this record's to move: under ADR 0188 §3 it is a process bundle, and how it is given its credential, its words and its reach is the open question of design 38 WP4c. Decision 4 applies to these containers' tools; the containers themselves go when their other code has moved. The decision and its options stand.

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 §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 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), 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