Files
hq/01-RESEARCH/020-what-a-bundled-tool-is-given/01-what-the-containers-are-given.md
T

5.8 KiB

What the tool containers are given, measured

Counted 2026-10-03 in the catalogue, after the two holders moved.

modules whose tools still run as a container on the runtime's image 33
tool containers among them (two modules run two) 36
modules whose container's environment carries only the bus credential 1 (the intrusion prevention, now moved)
modules whose container's environment carries more 32 — 31 still containers

What "more" is

Every value a container is given is one of five shapes. The reference kinds the composer resolves in those values, over the 36 containers: a module directory (${dir:…}) in all 36, a mesh-chosen port (${port:…}) in 12, a seat and an access grant once each.

  1. A file the mesh already places on the host, mounted in. The module's configuration as JSON (…_CONFIG_FILE), its own secret (…_TOKEN_FILE, …_PASSWORD_FILE, MESH_BROKER_FILE), a provision's address and secret written for it. Every one is a path under one of the module's directories — its mesh state, its state, its grants, what it has written — mounted at a path of the container's choosing and named to the tool through the environment. The file is on the host already; only the name under which the tool finds it is the container's.
  2. The service's address, with the port the mesh chose: http://127.0.0.1:${port:3000}. The port is the composer's; the rest is the manifest's constant.
  3. A provision's address as a constant string (a database's URL on the module's own network name), paired with a mounted secret file from shape 1.
  4. A directory of grants (MESH_RECEIVES): shape 1 again, a directory rather than a file.
  5. Literals the image needs: a time zone, a user id, a memory limit. These belong to the service's container where one exists; a tool bundle needs none of them.

So the whole of what a bundled tool needs is: the paths of its module's directories on this machine, the ports the mesh chose for its module here, and the constants its own manifest wrote. Nothing a container had that a bundle cannot have; the mesh composes all three for the container today, per module per machine.

What the runtime already has for it

  • The SDK's tool contributor is (env) => tools, and collectTools(env) takes the environment to hand each contributor. The runtime calls it without one, so every contributor reads the process's — the four words. The hook for a per-module environment exists and is unused.
  • A launched bundle (ADR 0188) is spawned with the runtime's environment; the launch takes an environment argument.
  • The composer resolves ${dir:…} and ${port:…} for a container's env and volumes; the same resolution over a bundle's declaration is the same code.

Options

A. The bundle declares its environment on its artifact, and the mesh composes it as a container's. The manifest's tools artifact gains env, resolved with the same references; values that were mount targets become the host-side paths directly (${dir:mesh-state}/config.json rather than /run/config/config.json). The controller composes one environment per bundle per machine into the runtime's declaration; the runtime hands it to the bundle's contributor and to a launched child, and to nothing else. For: the tool code does not change — it reads the same names; the conversion of the thirty-one is a mechanical move of the container's env with the mounts folded in; one rule, one place. Against: the runtime's process carries thirty-one environments in its declaration, and a bundle's environment is visible to the other bundles in the process unless the runtime keeps them apart, which it must — a tool that reads process.env instead of the environment it was handed would see its neighbours' paths.

B. The runtime derives the environment from the module's placed manifest. No new field: the runtime reads, for each module it serves, where that module's directories and ports are, and hands a conventional set of words. For: nothing to declare. Against: a convention the tool code must be rewritten to, thirty-one times; the runtime learns the composer's job; a module that names its file config.json and one that names it settings.json need different words anyway.

C. Tools read their module's files through the bus — ask the controller. Against: a tool that cannot start without the bus answering a question is a tool that fails in the one case the tools exist for, and a secret crossing the bus to reach a file already on the machine is a disclosure for nothing.

A is the one that keeps the tool code and the composer's vocabulary as they are, and names the one thing the runtime must add: an environment per bundle, kept apart. The thing to decide beside it: whether a bundle's environment may name a secret file at all, or whether secrets stay mounts in spirit — a path the tool reads, never a value in the environment — which is what every container does today and what A keeps if the rule says paths, not values.

What a decision would have to say

  • Where a bundle says what it is given (the artifact, option A), and that values are paths and constants, never a secret's content.
  • That the composer resolves it with the references it already has, per module per machine.
  • That the runtime hands each bundle its own environment and nothing of another's, and how that is checked: a test loading two bundles whose environments differ and asserting each sees only its own.
  • That the thirty-one move in one mechanical change after the rule lands, each proven by its tools answering from the runtime, and the registration gate then refuses the container shape for all.