Issue 209: a bundle's own SDK copy registers into a registry the runtime never reads; design 38 WP4 note #316

Merged
mesh-admin merged 5 commits from issues/209-a-bundles-own-sdk-copy-registers-nowhere into main 2026-10-03 11:20:39 +00:00
2 changed files with 86 additions and 0 deletions
@@ -191,6 +191,32 @@ tool where they need root, which they have, since the runtime runs as the node's
four machines; `docker ps` shows no `mesh-nftables`; `status` is well. Then the fail2ban holder four machines; `docker ps` shows no `mesh-nftables`; `status` is well. Then the fail2ban holder
proposed in an open change follows the same way when it lands. proposed in an open change follows the same way when it lands.
*Found 2026-10-03, before the step ran:* a bundle imported in-process brings its own copy of the SDK
(WP3's *carry its dependencies*), and the SDK's tool registry is the copy's own — the first module
loaded beside the runtime would have registered its tools where the runtime never looks, and served
nothing, silently. Issue
[209](../../04-ISSUES/209-a-bundles-own-sdk-copy-registers-into-a-registry-the-runtime-never-reads/00-report.md):
the runtime now resolves every bundle's import of the SDK to its own copy, one registry and one
broker per node. Two things the package did not say, settled in the module: the filter's commands
run through `sudo` without a prompt where the runtime is not root, since the operator's account may
escalate as the operator would; and a bundle has no environment of its own, so the tool reads the
filter from the path the manifest's `filtering` names rather than from a variable the container used
to carry, a test holding the two together. Three things a review of the change found: the module's
own bus credential and state directory went with the container, since nothing reads them once the
runtime speaks with the node's (the shell module of WP5 declares neither); the `iptables` package the
image used to carry is now declared on the host; and that the operator's account may escalate without
a prompt is a fact about the machine the mesh neither declares nor checks — true on all four today,
and when it is not, the tool names it by how it failed, which is the only check there is until a
record says where the fact belongs.
*Built 2026-10-03* (mesh-tools `7152148` for issue 209, mesh-catalog `db5e7c8`). *Proven live
2026-10-03, on all four machines*: `node-packet-filter.rules`, `reload` and `remove` answer from the
node's runtime on each — `rules` and the module's own tool list the mesh's table, `reload` loads the
file and answers with the table, `remove` refuses the mesh's own table by name — `docker ps` shows no
`mesh-nftables` on any, the container's credential is gone with it, and `status` is well. One thing
the step found is issue
[210](../../04-ISSUES/210-the-host-re-creates-the-nodes-runtime-on-every-reconcile/00-report.md):
the host re-creates the runtime's process on every reconcile.
## WP5 — The shell, on a server first ## WP5 — The shell, on a server first
*mesh-catalog #224, already written. Half a day to assign and prove.* *mesh-catalog #224, already written. Half a day to assign and prove.*
@@ -0,0 +1,60 @@
---
status: resolved
opened: 2026-10-03
located-in:
- mesh-tools
fixed-by: mesh-tools #32
amended-design:
---
# 209 — A bundle's own copy of the SDK registers into a registry the runtime never reads
## What was observed
2026-10-03, preparing [design 38](../../03-DESIGN/01-to-be/38-building-the-operators-machine.md)
WP4 — the first module whose tools bundle the node's runtime would load beside its own. Before the
manifest changed, a probe on the laptop did what the runtime does: a process holding one copy of
`@novox/mesh-sdk` imported a bundle in another directory that carried its own copy, and the bundle
called `registerModuleTools` as every tools entrypoint does.
```
registrations seen by host after importing bundle: 0
```
The import succeeds, the bundle registers, and the runtime's `collectTools` sees nothing. The
runtime would log the bundle as loaded and answer `tools` for the module with an empty list: silent,
and indistinguishable from a module that serves nothing by choice.
## Why it matters beyond this instance
WP3 found that *a TypeScript bundle must carry its dependencies*, and the toolchain copies them in
so a bundle starts anywhere. Among them is the SDK. The runtime imports a bundle in-process, and
the language resolves a bare import from the importing file's own tree — so every bundle brings a
second SDK into the process: its own registry of tools and its own broker handle. The SDK's registry
is a module-level list, and the runtime reads only the one it imported itself.
Every module from WP4 on is loaded this way. The runtime's tests never met it because their fixture
bundles sit under the runtime's own tree and resolve the same copy. Nothing in design 38 WP1 or WP3
says which SDK a loaded bundle speaks to, and the one-runtime record
([ADR 0175](../../02-DECISIONS/0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md))
assumes without saying that it is the runtime's.
## Diagnosis
Owner mesh-tools (`node-tools`): the runtime imports a bundle's entrypoint and counts the
registrations that appear after it, through the SDK it imported; the bundle's `import "@novox/mesh-sdk/tools"`
resolves to the copy under the bundle's own `node_modules`. **Fix direction:** the runtime resolves
every import of the SDK, from whichever bundle, as if the runtime had written it — one registry,
one broker — and leaves everything else a bundle carries to the bundle's own tree. A test loads a
bundle from a directory holding its own copy of the SDK and asserts its tools are served. The
toolchain keeps copying dependencies in: a bundle launched as a process ([ADR 0188](../../02-DECISIONS/0188-a-modules-own-code-is-bundles-in-any-language-and-a-tools-bundle-speaks-mcp-to-the-runtime.md))
needs them, and an imported one is simply not allowed to bring a second SDK.
## Resolution
2026-10-03, mesh-tools #32: the runtime installs a synchronous resolve hook before the first bundle is
imported, sending every import of the SDK, from whichever bundle, to its own copy; a bundle's other
dependencies still resolve from its own tree, and a bundle launched as a process is untouched. The test
loads a bundle from a directory holding its own copy of the SDK and its own dependency, and sees its
tool served with the dependency's answer. Proven live 2026-10-03 by WP4's proof in design 38: the packet filter's bundle, the first loaded beside
the runtime's own, serves its four tools on all four machines.