From 4c1ad0ed45a148e535005cbfefde1b2a962cc994 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 15:18:22 +0200 Subject: [PATCH] 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 --- .../00-overview.md | 4 +- ...e-runtime-hands-it-to-that-bundle-alone.md | 109 ++++++++++++++++++ 02-DECISIONS/README.md | 1 + .../38-building-the-operators-machine.md | 24 +++- 4 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md diff --git a/01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md b/01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md index 07fa74a..38a91eb 100644 --- a/01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md +++ b/01-RESEARCH/020-what-a-bundled-tool-is-given/00-overview.md @@ -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 diff --git a/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md b/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md new file mode 100644 index 0000000..516b723 --- /dev/null +++ b/02-DECISIONS/0192-a-tools-bundle-declares-what-it-is-given-and-the-runtime-hands-it-to-that-bundle-alone.md @@ -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 diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 328a86b..5fa1bfd 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -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 diff --git a/03-DESIGN/01-to-be/38-building-the-operators-machine.md b/03-DESIGN/01-to-be/38-building-the-operators-machine.md index f49a943..21503ff 100644 --- a/03-DESIGN/01-to-be/38-building-the-operators-machine.md +++ b/03-DESIGN/01-to-be/38-building-the-operators-machine.md @@ -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 -- 2.54.0